From e9f14d50c8502aa5662aed8a5a6da51624664c61 Mon Sep 17 00:00:00 2001 From: Christiane Heiligers Date: Tue, 4 Oct 2022 16:33:53 -0700 Subject: [PATCH 01/47] Adds extensions and factories for these Adds public types for extensions, temporarily omiting these from public mocks and saved objects service Adds extensions to saved objects start contract, temporarily omitting them from the saved objects service start implementation adding extensions to saved objects implementation Still wip adding extensions and factories to implementation more small changes Starts removing wrappers Updates saved objects service mock Updates more types around extensions and wrappers removal Adds saved objects extensions to plugin_context Adds extensions mocks to public saved objects server mocks Updates two more tests --- .../src/lib/extensions.test.mock.ts | 45 ++++ .../src/lib/scoped_client_provider.test.ts | 233 +++++++++++------- .../src/lib/scoped_client_provider.ts | 78 +++--- .../src/scoped_client_provider.mock.ts | 2 +- .../src/saved_objects_service.ts | 80 ++++-- .../src/saved_objects_service.mock.ts | 4 +- .../core-saved-objects-server/index.ts | 21 +- .../src/client_factory.ts | 57 +++-- .../src/contracts.ts | 35 ++- .../src/encryption.ts | 67 +++++ .../src/extensions.ts | 27 ++ .../core-saved-objects-server/src/security.ts | 208 ++++++++++++++++ .../core-saved-objects-server/src/spaces.ts | 27 ++ .../src/saved_objects_utils.ts | 2 +- src/core/server/index.ts | 5 +- src/core/server/plugins/plugin_context.ts | 4 +- 16 files changed, 705 insertions(+), 190 deletions(-) create mode 100644 packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts create mode 100644 packages/core/saved-objects/core-saved-objects-server/src/encryption.ts create mode 100644 packages/core/saved-objects/core-saved-objects-server/src/extensions.ts create mode 100644 packages/core/saved-objects/core-saved-objects-server/src/security.ts create mode 100644 packages/core/saved-objects/core-saved-objects-server/src/spaces.ts diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts new file mode 100644 index 00000000000000..9b411d0b4ef33e --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts @@ -0,0 +1,45 @@ +/* + * 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 type { + ISavedObjectsEncryptionExtension, + ISavedObjectsSecurityExtension, + ISavedObjectsSpacesExtension, + SavedObjectsExtensions, +} from '@kbn/core-saved-objects-server'; + +const createEncryptionExtension = (): jest.Mocked => ({ + isEncryptableType: jest.fn(), + decryptOrStripResponseAttributes: jest.fn(), + encryptAttributes: jest.fn(), +}); + +const createSecurityExtension = (): jest.Mocked => ({ + checkAuthorization: jest.fn(), + enforceAuthorization: jest.fn(), + addAuditEvent: jest.fn(), + redactNamespaces: jest.fn(), +}); + +const createSpacesExtension = (): jest.Mocked => ({ + getCurrentNamespace: jest.fn(), + getSearchableNamespaces: jest.fn(), +}); + +const create = (): jest.Mocked => ({ + encryptionExtension: createEncryptionExtension(), + securityExtension: createSecurityExtension(), + spacesExtension: createSpacesExtension(), +}); + +export const extensionsMock = { + create, + createEncryptionExtension, + createSecurityExtension, + createSpacesExtension, +}; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts index 2641ecfb1cec6f..eb72af9914d339 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts @@ -5,17 +5,54 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ - +import type { Optional } from 'utility-types'; import { httpServerMock } from '@kbn/core-http-server-mocks'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { SavedObjectsClientProvider } from './scoped_client_provider'; +import { + ISavedObjectTypeRegistry, + SavedObjectsClientFactory, + SavedObjectsEncryptionExtensionFactory, + SavedObjectsSecurityExtensionFactory, + SavedObjectsSpacesExtensionFactory, + ENCRYPTION_EXTENSION_ID, + SECURITY_EXTENSION_ID, + SPACES_EXTENSION_ID, +} from '@kbn/core-saved-objects-server'; +import { extensionsMock } from './extensions.test.mock'; +import { KibanaRequest } from '@kbn/core-http-server'; + +/** + * @internal only used for unit tests + */ +interface Params { + defaultClientFactory: SavedObjectsClientFactory; + typeRegistry: ISavedObjectTypeRegistry; + encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory | undefined; + securityExtensionFactory: SavedObjectsSecurityExtensionFactory | undefined; + spacesExtensionFactory: SavedObjectsSpacesExtensionFactory | undefined; +} + +function createClientProvider( + params: Optional< + Params, + 'encryptionExtensionFactory' | 'securityExtensionFactory' | 'spacesExtensionFactory' + > +) { + return new SavedObjectsClientProvider({ + encryptionExtensionFactory: undefined, + securityExtensionFactory: undefined, + spacesExtensionFactory: undefined, + ...params, + }); +} test(`uses default client factory when one isn't set`, () => { const returnValue = Symbol(); const defaultClientFactoryMock = jest.fn().mockReturnValue(returnValue); const request = httpServerMock.createKibanaRequest(); - const clientProvider = new SavedObjectsClientProvider({ + const clientProvider = createClientProvider({ defaultClientFactory: defaultClientFactoryMock, typeRegistry: typeRegistryMock.create(), }); @@ -24,6 +61,7 @@ test(`uses default client factory when one isn't set`, () => { expect(result).toBe(returnValue); expect(defaultClientFactoryMock).toHaveBeenCalledTimes(1); expect(defaultClientFactoryMock).toHaveBeenCalledWith({ + extensions: expect.any(Object), request, }); }); @@ -34,7 +72,7 @@ test(`uses custom client factory when one is set`, () => { const returnValue = Symbol(); const customClientFactoryMock = jest.fn().mockReturnValue(returnValue); - const clientProvider = new SavedObjectsClientProvider({ + const clientProvider = createClientProvider({ defaultClientFactory: defaultClientFactoryMock, typeRegistry: typeRegistryMock.create(), }); @@ -45,6 +83,7 @@ test(`uses custom client factory when one is set`, () => { expect(defaultClientFactoryMock).not.toHaveBeenCalled(); expect(customClientFactoryMock).toHaveBeenCalledTimes(1); expect(customClientFactoryMock).toHaveBeenCalledWith({ + extensions: expect.any(Object), request, }); }); @@ -53,7 +92,7 @@ test(`throws error when more than one scoped saved objects client factory is set const defaultClientFactory = jest.fn(); const clientFactory = jest.fn(); - const clientProvider = new SavedObjectsClientProvider({ + const clientProvider = createClientProvider({ defaultClientFactory, typeRegistry: typeRegistryMock.create(), }); @@ -66,111 +105,114 @@ test(`throws error when more than one scoped saved objects client factory is set ); }); -test(`throws error when registering a wrapper with a duplicate id`, () => { - const defaultClientFactoryMock = jest.fn(); - const clientProvider = new SavedObjectsClientProvider({ - defaultClientFactory: defaultClientFactoryMock, - typeRegistry: typeRegistryMock.create(), - }); - const firstClientWrapperFactoryMock = jest.fn(); - const secondClientWrapperFactoryMock = jest.fn(); - - clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock); - expect(() => - clientProvider.addClientWrapperFactory(0, 'foo', firstClientWrapperFactoryMock) - ).toThrowErrorMatchingInlineSnapshot(`"wrapper factory with id foo is already defined"`); -}); - -test(`invokes and uses wrappers in specified order`, () => { +describe(`allows extensions to be excluded`, () => { const defaultClient = Symbol(); const typeRegistry = typeRegistryMock.create(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const clientProvider = new SavedObjectsClientProvider({ - defaultClientFactory: defaultClientFactoryMock, - typeRegistry, - }); - const firstWrappedClient = Symbol('first client'); - const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient); - const secondWrapperClient = Symbol('second client'); - const secondClientWrapperFactoryMock = jest.fn().mockReturnValue(secondWrapperClient); - const request = httpServerMock.createKibanaRequest(); - - clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock); - clientProvider.addClientWrapperFactory(0, 'bar', firstClientWrapperFactoryMock); - const actualClient = clientProvider.getClient(request); - expect(actualClient).toBe(firstWrappedClient); - expect(firstClientWrapperFactoryMock).toHaveBeenCalledWith({ - request, - client: secondWrapperClient, - typeRegistry, - }); - expect(secondClientWrapperFactoryMock).toHaveBeenCalledWith({ - request, - client: defaultClient, - typeRegistry, - }); -}); - -test(`does not invoke or use excluded wrappers`, () => { - const defaultClient = Symbol(); - const typeRegistry = typeRegistryMock.create(); - const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const clientProvider = new SavedObjectsClientProvider({ + const mockEncryptionExt = extensionsMock.createEncryptionExtension(); + const encryptionExtFactory: SavedObjectsEncryptionExtensionFactory = (params: { + typeRegistry: ISavedObjectTypeRegistry; + request: KibanaRequest; + }) => mockEncryptionExt; + + const mockSpacesExt = extensionsMock.createSpacesExtension(); + const spacesExtFactory: SavedObjectsSpacesExtensionFactory = (params: { + typeRegistry: ISavedObjectTypeRegistry; + request: KibanaRequest; + }) => mockSpacesExt; + + const mockSecurityExt = extensionsMock.createSecurityExtension(); + const securityExtFactory: SavedObjectsSecurityExtensionFactory = (params: { + typeRegistry: ISavedObjectTypeRegistry; + request: KibanaRequest; + }) => mockSecurityExt; + + const clientProvider = createClientProvider({ defaultClientFactory: defaultClientFactoryMock, + encryptionExtensionFactory: encryptionExtFactory, + spacesExtensionFactory: spacesExtFactory, + securityExtensionFactory: securityExtFactory, typeRegistry, }); - const firstWrappedClient = Symbol('first client'); - const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient); - const secondWrapperClient = Symbol('second client'); - const secondClientWrapperFactoryMock = jest.fn().mockReturnValue(secondWrapperClient); - const request = httpServerMock.createKibanaRequest(); - - clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock); - clientProvider.addClientWrapperFactory(0, 'bar', firstClientWrapperFactoryMock); - - const actualClient = clientProvider.getClient(request, { - excludedWrappers: ['foo'], - }); - expect(actualClient).toBe(firstWrappedClient); - expect(firstClientWrapperFactoryMock).toHaveBeenCalledWith({ - request, - client: defaultClient, - typeRegistry, + test(`calls client factory with all extensions excluded`, async () => { + const request = httpServerMock.createKibanaRequest(); + + clientProvider.getClient(request, { + excludedExtensions: [ENCRYPTION_EXTENSION_ID, SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID], + }); + + expect(defaultClientFactoryMock).toHaveBeenCalledWith( + expect.objectContaining({ + extensions: { + encryptionExtension: undefined, + securityExtension: undefined, + spacesExtension: undefined, + }, + }) + ); + }); + + test(`calls client factory with some extensions excluded`, async () => { + const request = httpServerMock.createKibanaRequest(); + + clientProvider.getClient(request, { + excludedExtensions: [ENCRYPTION_EXTENSION_ID, SPACES_EXTENSION_ID], + }); + + expect(defaultClientFactoryMock).toHaveBeenCalledWith( + expect.objectContaining({ + extensions: { + encryptionExtension: undefined, + securityExtension: mockSecurityExt, + spacesExtension: undefined, + }, + }) + ); + }); + + test(`calls client factory with one extension excluded`, async () => { + const request = httpServerMock.createKibanaRequest(); + + clientProvider.getClient(request, { + excludedExtensions: [SECURITY_EXTENSION_ID], + }); + + expect(defaultClientFactoryMock).toHaveBeenCalledWith( + expect.objectContaining({ + extensions: { + encryptionExtension: mockEncryptionExt, + securityExtension: undefined, + spacesExtension: mockSpacesExt, + }, + }) + ); + }); + + test(`calls client factory with no extensions excluded`, async () => { + const request = httpServerMock.createKibanaRequest(); + + clientProvider.getClient(request, { + excludedExtensions: [], + }); + + expect(defaultClientFactoryMock).toHaveBeenCalledWith( + expect.objectContaining({ + extensions: { + encryptionExtension: mockEncryptionExt, + securityExtension: mockSecurityExt, + spacesExtension: mockSpacesExt, + }, + }) + ); }); - expect(secondClientWrapperFactoryMock).not.toHaveBeenCalled(); -}); - -test(`allows all wrappers to be excluded`, () => { - const defaultClient = Symbol(); - const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const clientProvider = new SavedObjectsClientProvider({ - defaultClientFactory: defaultClientFactoryMock, - typeRegistry: typeRegistryMock.create(), - }); - const firstWrappedClient = Symbol('first client'); - const firstClientWrapperFactoryMock = jest.fn().mockReturnValue(firstWrappedClient); - const secondWrapperClient = Symbol('second client'); - const secondClientWrapperFactoryMock = jest.fn().mockReturnValue(secondWrapperClient); - const request = httpServerMock.createKibanaRequest(); - - clientProvider.addClientWrapperFactory(1, 'foo', secondClientWrapperFactoryMock); - clientProvider.addClientWrapperFactory(0, 'bar', firstClientWrapperFactoryMock); - - const actualClient = clientProvider.getClient(request, { - excludedWrappers: ['foo', 'bar'], - }); - - expect(actualClient).toBe(defaultClient); - expect(firstClientWrapperFactoryMock).not.toHaveBeenCalled(); - expect(secondClientWrapperFactoryMock).not.toHaveBeenCalled(); }); test(`allows hidden typed to be included`, () => { const defaultClient = Symbol(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const clientProvider = new SavedObjectsClientProvider({ + const clientProvider = createClientProvider({ defaultClientFactory: defaultClientFactoryMock, typeRegistry: typeRegistryMock.create(), }); @@ -182,6 +224,7 @@ test(`allows hidden typed to be included`, () => { expect(actualClient).toBe(defaultClient); expect(defaultClientFactoryMock).toHaveBeenCalledWith({ + extensions: expect.any(Object), request, includedHiddenTypes: ['task'], }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index 54e690c3370e1b..3602ca8ce77d7b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -8,13 +8,18 @@ import type { KibanaRequest } from '@kbn/core-http-server'; import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { ISavedObjectTypeRegistry, - SavedObjectsClientWrapperFactory, SavedObjectsClientFactory, SavedObjectsClientProviderOptions, + type SavedObjectsEncryptionExtensionFactory, + type SavedObjectsSecurityExtensionFactory, + type SavedObjectsSpacesExtensionFactory, + type SavedObjectsExtensions, + ENCRYPTION_EXTENSION_ID, + SECURITY_EXTENSION_ID, + SPACES_EXTENSION_ID, } from '@kbn/core-saved-objects-server'; -import { PriorityCollection } from './priority_collection'; /** * @internal @@ -30,35 +35,31 @@ export type ISavedObjectsClientProvider = Pick< * @internal */ export class SavedObjectsClientProvider { - private readonly _wrapperFactories = new PriorityCollection<{ - id: string; - factory: SavedObjectsClientWrapperFactory; - }>(); private _clientFactory: SavedObjectsClientFactory; private readonly _originalClientFactory: SavedObjectsClientFactory; + private readonly encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory | undefined; + private readonly securityExtensionFactory: SavedObjectsSecurityExtensionFactory | undefined; + private readonly spacesExtensionFactory: SavedObjectsSpacesExtensionFactory | undefined; private readonly _typeRegistry: ISavedObjectTypeRegistry; constructor({ defaultClientFactory, typeRegistry, + encryptionExtensionFactory, + securityExtensionFactory, + spacesExtensionFactory, }: { defaultClientFactory: SavedObjectsClientFactory; typeRegistry: ISavedObjectTypeRegistry; + encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory | undefined; + securityExtensionFactory: SavedObjectsSecurityExtensionFactory | undefined; + spacesExtensionFactory: SavedObjectsSpacesExtensionFactory | undefined; }) { this._originalClientFactory = this._clientFactory = defaultClientFactory; this._typeRegistry = typeRegistry; - } - - addClientWrapperFactory( - priority: number, - id: string, - factory: SavedObjectsClientWrapperFactory - ): void { - if (this._wrapperFactories.has((entry) => entry.id === id)) { - throw new Error(`wrapper factory with id ${id} is already defined`); - } - - this._wrapperFactories.add(priority, { id, factory }); + this.encryptionExtensionFactory = encryptionExtensionFactory; + this.securityExtensionFactory = securityExtensionFactory; + this.spacesExtensionFactory = spacesExtensionFactory; } setClientFactory(customClientFactory: SavedObjectsClientFactory) { @@ -71,25 +72,36 @@ export class SavedObjectsClientProvider { getClient( request: KibanaRequest, - { includedHiddenTypes, excludedWrappers = [] }: SavedObjectsClientProviderOptions = {} + { includedHiddenTypes, excludedExtensions = [] }: SavedObjectsClientProviderOptions = {} ): SavedObjectsClientContract { - const client = this._clientFactory({ + return this._clientFactory({ request, includedHiddenTypes, + extensions: this.getExtensions(request, excludedExtensions), }); + } - return this._wrapperFactories - .toPrioritizedArray() - .reduceRight((clientToWrap, { id, factory }) => { - if (excludedWrappers.includes(id)) { - return clientToWrap; - } + getExtensions(request: KibanaRequest, excludedExtensions: string[]): SavedObjectsExtensions { + const isEncryptionExtensionIncluded = + !excludedExtensions.includes(ENCRYPTION_EXTENSION_ID) && !!this.encryptionExtensionFactory; + const encryptionExtension = isEncryptionExtensionIncluded + ? this.encryptionExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) + : undefined; + const isSecurityExtensionIncluded = + !excludedExtensions.includes(SECURITY_EXTENSION_ID) && !!this.securityExtensionFactory; + const securityExtension = isSecurityExtensionIncluded + ? this.securityExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) + : undefined; + const isSpacesExtensionIncluded = + !excludedExtensions.includes(SPACES_EXTENSION_ID) && !!this.spacesExtensionFactory; + const spacesExtension = isSpacesExtensionIncluded + ? this.spacesExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) + : undefined; - return factory({ - request, - client: clientToWrap, - typeRegistry: this._typeRegistry, - }); - }, client); + return { + encryptionExtension, + securityExtension, + spacesExtension, + }; } } diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts index dd617cdbf3d3c9..73efc414a634af 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts @@ -9,9 +9,9 @@ import type { ISavedObjectsClientProvider } from '@kbn/core-saved-objects-api-server-internal'; const create = (): jest.Mocked => ({ - addClientWrapperFactory: jest.fn(), getClient: jest.fn(), setClientFactory: jest.fn(), + getExtensions: jest.fn(), }); export const savedObjectsClientProviderMock = { 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 8036a997d0d516..a102ac01595f1c 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 @@ -25,8 +25,11 @@ import type { SavedObjectsRepositoryFactory, SavedObjectStatusMeta, SavedObjectsClientFactoryProvider, - SavedObjectsClientWrapperFactory, ISavedObjectTypeRegistry, + SavedObjectsEncryptionExtensionFactory, + SavedObjectsSecurityExtensionFactory, + SavedObjectsSpacesExtensionFactory, + SavedObjectsExtensions, } from '@kbn/core-saved-objects-server'; import { SavedObjectConfig, @@ -74,12 +77,6 @@ export interface SavedObjectsSetupDeps { deprecations: DeprecationRegistryProvider; } -interface WrappedClientFactoryWrapper { - priority: number; - id: string; - factory: SavedObjectsClientWrapperFactory; -} - /** @internal */ export interface SavedObjectsStartDeps { elasticsearch: InternalElasticsearchServiceStart; @@ -96,7 +93,9 @@ export class SavedObjectsService private setupDeps?: SavedObjectsSetupDeps; private config?: SavedObjectConfig; private clientFactoryProvider?: SavedObjectsClientFactoryProvider; - private clientFactoryWrappers: WrappedClientFactoryWrapper[] = []; + private encryptionExtensionFactory?: SavedObjectsEncryptionExtensionFactory; + private securityExtensionFactory?: SavedObjectsSecurityExtensionFactory; + private spacesExtensionFactory?: SavedObjectsSpacesExtensionFactory; private migrator$ = new Subject(); private typeRegistry = new SavedObjectTypeRegistry(); @@ -160,15 +159,32 @@ export class SavedObjectsService } this.clientFactoryProvider = provider; }, - addClientWrapper: (priority, id, factory) => { + addEncryptionExtension: (factory) => { if (this.started) { - throw new Error('cannot call `addClientWrapper` after service startup.'); + throw new Error('cannot call `addEncryptionExtension` after service startup.'); + } + if (this.encryptionExtensionFactory) { + throw new Error('encryption extension is already set, and can only be set once'); + } + this.encryptionExtensionFactory = factory; + }, + addSecurityExtension: (factory) => { + if (this.started) { + throw new Error('cannot call `addSecurityExtension` after service startup.'); + } + if (this.securityExtensionFactory) { + throw new Error('security extension is already set, and can only be set once'); } - this.clientFactoryWrappers.push({ - priority, - id, - factory, - }); + this.securityExtensionFactory = factory; + }, + addSpacesExtension: (factory) => { + if (this.started) { + throw new Error('cannot call `addSpacesExtension` after service startup.'); + } + if (this.spacesExtensionFactory) { + throw new Error('spaces extension is already set, and can only be set once'); + } + this.spacesExtensionFactory = factory; }, registerType: (type) => { if (this.started) { @@ -250,7 +266,8 @@ export class SavedObjectsService const createRepository = ( esClient: ElasticsearchClient, - includedHiddenTypes: string[] = [] + includedHiddenTypes: string[] = [], + extensions?: SavedObjectsExtensions ) => { return SavedObjectsRepository.createRepository( migrator, @@ -258,31 +275,42 @@ export class SavedObjectsService kibanaIndex, esClient, this.logger.get('repository'), - includedHiddenTypes + includedHiddenTypes, + extensions ); }; const repositoryFactory: SavedObjectsRepositoryFactory = { - createInternalRepository: (includedHiddenTypes?: string[]) => - createRepository(client.asInternalUser, includedHiddenTypes), - createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => - createRepository(client.asScoped(req).asCurrentUser, includedHiddenTypes), + createInternalRepository: ( + includedHiddenTypes?: string[], + extensions?: SavedObjectsExtensions | undefined + ) => createRepository(client.asInternalUser, includedHiddenTypes, extensions), + createScopedRepository: ( + req: KibanaRequest, + includedHiddenTypes?: string[], + extensions?: SavedObjectsExtensions + ) => createRepository(client.asScoped(req).asCurrentUser, includedHiddenTypes, extensions), }; const clientProvider = new SavedObjectsClientProvider({ - defaultClientFactory({ request, includedHiddenTypes }) { - const repository = repositoryFactory.createScopedRepository(request, includedHiddenTypes); + defaultClientFactory({ request, includedHiddenTypes, extensions }): SavedObjectsClient { + const repository = repositoryFactory.createScopedRepository( + request, + includedHiddenTypes, + extensions + ); return new SavedObjectsClient(repository); }, typeRegistry: this.typeRegistry, + encryptionExtensionFactory: this.encryptionExtensionFactory, + securityExtensionFactory: this.securityExtensionFactory, + spacesExtensionFactory: this.spacesExtensionFactory, }); + if (this.clientFactoryProvider) { const clientFactory = this.clientFactoryProvider(repositoryFactory); clientProvider.setClientFactory(clientFactory); } - this.clientFactoryWrappers.forEach(({ id, factory, priority }) => { - clientProvider.addClientWrapperFactory(priority, id, factory); - }); this.started = true; diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts index 9939e5d3240e97..2de6d7afcb6a1b 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts @@ -63,7 +63,9 @@ const createInternalStartContractMock = (typeRegistry?: jest.Mocked { const setupContract: jest.Mocked = { setClientFactoryProvider: jest.fn(), - addClientWrapper: jest.fn(), + addEncryptionExtension: jest.fn(), + addSecurityExtension: jest.fn(), + addSpacesExtension: jest.fn(), registerType: jest.fn(), getKibanaIndex: jest.fn(), }; diff --git a/packages/core/saved-objects/core-saved-objects-server/index.ts b/packages/core/saved-objects/core-saved-objects-server/index.ts index 38ee45b14c0199..dc865a4e83d987 100644 --- a/packages/core/saved-objects/core-saved-objects-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server/index.ts @@ -9,10 +9,8 @@ export type { SavedObjectsClientFactory, SavedObjectsClientFactoryProvider, - SavedObjectsClientWrapperFactory, SavedObjectsRepositoryFactory, SavedObjectsClientProviderOptions, - SavedObjectsClientWrapperOptions, } from './src/client_factory'; export type { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './src/contracts'; export type { @@ -64,3 +62,22 @@ export type { } from './src/serialization'; export type { ISavedObjectTypeRegistry } from './src/type_registry'; export type { SavedObjectsValidationMap, SavedObjectsValidationSpec } from './src/validation'; +export type { ISavedObjectsEncryptionExtension, EncryptedObjectDescriptor } from './src/encryption'; +export type { + CheckAuthorizationParams, + AuthorizationTypeEntry, + AuthorizationTypeMap, + CheckAuthorizationResult, + EnforceAuthorizationParams, + AddAuditEventParams, + RedactNamespacesParams, + ISavedObjectsSecurityExtension, +} from './src/security'; +export { AuditAction } from './src/security'; +export type { ISavedObjectsSpacesExtension } from './src/spaces'; +export type { SavedObjectsExtensions } from './src/extensions'; +export { + ENCRYPTION_EXTENSION_ID, + SECURITY_EXTENSION_ID, + SPACES_EXTENSION_ID, +} from './src/extensions'; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts index ae0e3a649ada9f..7a3547c598c34f 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts @@ -11,37 +11,52 @@ import type { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectsEncryptionExtension } from './encryption'; +import type { SavedObjectsExtensions } from './extensions'; +import type { ISavedObjectsSecurityExtension } from './security'; +import type { ISavedObjectsSpacesExtension } from './spaces'; import type { ISavedObjectTypeRegistry } from './type_registry'; /** - * Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. + * Describes the factory used to create instances of the Saved Objects Client. + * @public + */ +export type SavedObjectsClientFactory = ({ + request, + includedHiddenTypes, + extensions, +}: { + request: KibanaRequest; + includedHiddenTypes?: string[]; + extensions?: SavedObjectsExtensions; +}) => SavedObjectsClientContract; + +/** + * Describes the factory used to create instances of the Saved Objects Encryption Extension. * @public */ -export interface SavedObjectsClientWrapperOptions { - client: SavedObjectsClientContract; +export type SavedObjectsEncryptionExtensionFactory = (params: { typeRegistry: ISavedObjectTypeRegistry; request: KibanaRequest; -} +}) => ISavedObjectsEncryptionExtension; /** - * Describes the factory used to create instances of Saved Objects Client Wrappers. + * Describes the factory used to create instances of the Saved Objects Security Extension. * @public */ -export type SavedObjectsClientWrapperFactory = ( - options: SavedObjectsClientWrapperOptions -) => SavedObjectsClientContract; +export type SavedObjectsSecurityExtensionFactory = (params: { + typeRegistry: ISavedObjectTypeRegistry; + request: KibanaRequest; +}) => ISavedObjectsSecurityExtension | undefined; // May be undefined if RBAC is disabled /** - * Describes the factory used to create instances of the Saved Objects Client. + * Describes the factory used to create instances of the Saved Objects Spaces Extension. * @public */ -export type SavedObjectsClientFactory = ({ - request, - includedHiddenTypes, -}: { +export type SavedObjectsSpacesExtensionFactory = (params: { + typeRegistry: ISavedObjectTypeRegistry; request: KibanaRequest; - includedHiddenTypes?: string[]; -}) => SavedObjectsClientContract; +}) => ISavedObjectsSpacesExtension; /** * Provider to invoke to retrieve a {@link SavedObjectsClientFactory}. @@ -56,8 +71,8 @@ export type SavedObjectsClientFactoryProvider = ( * @public */ export interface SavedObjectsClientProviderOptions { - excludedWrappers?: string[]; includedHiddenTypes?: string[]; + excludedExtensions?: string[]; } /** @@ -73,16 +88,22 @@ export interface SavedObjectsRepositoryFactory { * Elasticsearch. * * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. + * @param extensions - Extensions that the repository should use (for encryption, security, and spaces). */ createScopedRepository: ( req: KibanaRequest, - includedHiddenTypes?: string[] + includedHiddenTypes?: string[], + extensions?: SavedObjectsExtensions ) => ISavedObjectsRepository; /** * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that * uses the internal Kibana user for authenticating with Elasticsearch. * * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. + * @param extensions - Extensions that the repository should use (for encryption, security, and spaces). */ - createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; + createInternalRepository: ( + includedHiddenTypes?: string[], + extensions?: SavedObjectsExtensions + ) => ISavedObjectsRepository; } diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index 20821cef46e7a1..0f2c3d5447c0dc 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -14,13 +14,16 @@ import type { import type { ISavedObjectsSerializer } from './serialization'; import type { SavedObjectsClientFactoryProvider, - SavedObjectsClientWrapperFactory, SavedObjectsClientProviderOptions, + SavedObjectsEncryptionExtensionFactory, + SavedObjectsSecurityExtensionFactory, + SavedObjectsSpacesExtensionFactory, } from './client_factory'; import type { SavedObjectsType } from './saved_objects_type'; import type { ISavedObjectTypeRegistry } from './type_registry'; import type { ISavedObjectsExporter } from './export'; import type { ISavedObjectsImporter } from './import'; +import { SavedObjectsExtensions } from './extensions'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to @@ -67,13 +70,19 @@ export interface SavedObjectsServiceSetup { setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void; /** - * Add a {@link SavedObjectsClientWrapperFactory | client wrapper factory} with the given priority. + * Add a {@link SavedObjectsEncryptionExtensionFactory encryption extension factory}. */ - addClientWrapper: ( - priority: number, - id: string, - factory: SavedObjectsClientWrapperFactory - ) => void; + addEncryptionExtension: (factory: SavedObjectsEncryptionExtensionFactory) => void; + + /** + * Add a {@link SavedObjectsSecurityExtensionFactory security extension factory}. + */ + addSecurityExtension: (factory: SavedObjectsSecurityExtensionFactory) => void; + + /** + * Add a {@link SavedObjectsSpacesExtensionFactory spaces extension factory}. + */ + addSpacesExtension: (factory: SavedObjectsSpacesExtensionFactory) => void; /** * Register a {@link SavedObjectsType | savedObjects type} definition. @@ -151,7 +160,7 @@ export interface SavedObjectsServiceStart { */ getScopedClient: ( req: KibanaRequest, - options?: SavedObjectsClientProviderOptions + options?: SavedObjectsClientProviderOptions // check these ) => SavedObjectsClientContract; /** * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that @@ -160,6 +169,7 @@ export interface SavedObjectsServiceStart { * * @param req - The request to create the scoped repository from. * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. + * @param extensions - Extensions that the repository should use (for encryption, security, and spaces). * * @remarks * Prefer using `getScopedClient`. This should only be used when using methods @@ -167,15 +177,20 @@ export interface SavedObjectsServiceStart { */ createScopedRepository: ( req: KibanaRequest, - includedHiddenTypes?: string[] + includedHiddenTypes?: string[], + extensions?: SavedObjectsExtensions ) => ISavedObjectsRepository; /** * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that * uses the internal Kibana user for authenticating with Elasticsearch. * * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. + * @param extensions - Extensions that the repository should use (for encryption, security, and spaces). */ - createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; + createInternalRepository: ( + includedHiddenTypes?: string[], + extensions?: SavedObjectsExtensions + ) => ISavedObjectsRepository; /** * Creates a {@link ISavedObjectsSerializer | serializer} that is aware of all registered types. */ diff --git a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts new file mode 100644 index 00000000000000..2508357d4ce8dc --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts @@ -0,0 +1,67 @@ +/* + * 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 type { SavedObject } from '@kbn/core-saved-objects-common'; + +/** + * The CheckAuthorizationParams interface contains settings for checking + * authorization via the ISavedObjectsSecurityExtension. + */ +export interface EncryptedObjectDescriptor { + /** + * A set of types to check. + */ + type: string; + /** + * A set of spaces to check. + */ + id: string; + /** + * An array of actions to check. + */ + namespace?: string; +} + +/** + * The ISavedObjectsEncryptionExtension interface defines the functions of a saved objects + * repository encryption extension. It contains functions for determining if a type is + * encryptable, encrypting object attributes, and decrypting or stripping object attributes. + */ +export interface ISavedObjectsEncryptionExtension { + /** + * Returns true if a type has been registered as encryptable. + * @param type - the string name of the object type + * @returns boolean, true if type is encryptable + */ + isEncryptableType: (type: string) => boolean; + + /** + * Given a saved object, will return a decrypted saved object or will strip + * attributes from the returned object if decryption fails. + * @param response - any object R that extends SavedObject with attributes T + * @param originalAttributes - optional, original attributes T from when the object was created (NOT encrypted). + * These are used to avoid decryption execution cost if they are supplied. + * @returns R with decrypted or stripped attributes + */ + decryptOrStripResponseAttributes: >( + response: R, + originalAttributes?: T + ) => Promise; + + /** + * Given a saved object descriptor and some attributes, returns an encrypted version + * of supplied attributes. + * @param descriptor - an object containing a saved object id, type, and optional namespace. + * @param attributes - T, attributes of the specified object, some of which to be encrypted. + * @returns T, encrypted attributes + */ + encryptAttributes: >( + descriptor: { id: string; type: string; namespace: string | undefined }, + attributes: T + ) => Promise; +} diff --git a/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts new file mode 100644 index 00000000000000..a51184b61a113a --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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 type { ISavedObjectsEncryptionExtension } from './encryption'; +import type { ISavedObjectsSecurityExtension } from './security'; +import type { ISavedObjectsSpacesExtension } from './spaces'; + +/** + * The SavedObjectsExtensions interface contains the intefaces for three + * extensions to the saved objects repository. These extensions augment + * the funtionality of the saved objects repository to provide encryption, + * security, and spaces features. + */ +export interface SavedObjectsExtensions { + encryptionExtension: ISavedObjectsEncryptionExtension | undefined; + securityExtension: ISavedObjectsSecurityExtension | undefined; + spacesExtension: ISavedObjectsSpacesExtension | undefined; +} + +export const ENCRYPTION_EXTENSION_ID = 'encryptedSavedObjects' as const; +export const SECURITY_EXTENSION_ID = 'security' as const; +export const SPACES_EXTENSION_ID = 'spaces' as const; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/security.ts b/packages/core/saved-objects/core-saved-objects-server/src/security.ts new file mode 100644 index 00000000000000..0623b5efb59d76 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-server/src/security.ts @@ -0,0 +1,208 @@ +/* + * 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 type { SavedObject } from '@kbn/core-saved-objects-common'; +import { EcsEventOutcome } from '@kbn/logging'; + +/** + * The CheckAuthorizationParams interface contains settings for checking + * authorization via the ISavedObjectsSecurityExtension. + */ +export interface CheckAuthorizationParams { + /** + * A set of types to check. + */ + types: Set; + /** + * A set of spaces to check. + */ + spaces: Set; + /** + * An array of actions to check. + */ + actions: A[]; + /** + * Authorization options - whether or not to allow global resources, false if options are undefined + */ + options?: { + allowGlobalResource: boolean; + }; +} + +/** + * The AuthorizationTypeEntry interface contains space-related details + * for CheckAuthorizationResults. + */ +export interface AuthorizationTypeEntry { + /** + * An array of authorized spaces for the associated type/action + * in the associated record/map. + */ + authorizedSpaces: string[]; + /** + * Is the associated type/action globally authorized? + */ + isGloballyAuthorized?: boolean; +} + +/** + * The AuthorizationTypeMap type is a map of saved object type + * to a record of action/AuthorizationTypeEntry, + */ +export type AuthorizationTypeMap = Map>; + +/** + * The CheckAuthorizationResult interface contains the overall status of an + * authorization check and the specific authorized privileges as an + * AuthorizationTypeMap. + */ +export interface CheckAuthorizationResult { + /** + * The overall status of the authorization check as a string: + * 'fully_authorized' | 'partially_authorized' | 'unauthorized' + */ + status: 'fully_authorized' | 'partially_authorized' | 'unauthorized'; + /** + * The specific authorized privileges: a map of type to record + * of action/AuthorizationTypeEntry (spaces/globallyAuthz'd) + */ + typeMap: AuthorizationTypeMap; +} + +/** + * The EnforceAuthorizationParams interface contains settings for + * enforcing a single action via the ISavedObjectsSecurityExtension. + */ +export interface EnforceAuthorizationParams { + /** + * A map of types to spaces that will be affected by the action + */ + typesAndSpaces: Map>; + /** + * The relevant action (create, update, etc.) + */ + action: A; + /** + * The authorization map from CheckAuthorizationResult: a + * map of type to record of action/AuthorizationTypeEntry + * (spaces/globallyAuthz'd) + */ + typeMap: AuthorizationTypeMap; + /** + * A callback intended to handle adding audit events in + * both error (unauthorized), or success (authorized) + * cases + */ + auditCallback?: (error?: Error) => void; +} + +/** + * The AuditAction enumeration contains values for all + * valid audit actions for use in AddAuditEventParams. + */ +export enum AuditAction { + CREATE = 'saved_object_create', + GET = 'saved_object_get', + RESOLVE = 'saved_object_resolve', + UPDATE = 'saved_object_update', + DELETE = 'saved_object_delete', + FIND = 'saved_object_find', + REMOVE_REFERENCES = 'saved_object_remove_references', + OPEN_POINT_IN_TIME = 'saved_object_open_point_in_time', + CLOSE_POINT_IN_TIME = 'saved_object_close_point_in_time', + COLLECT_MULTINAMESPACE_REFERENCES = 'saved_object_collect_multinamespace_references', // this is separate from 'saved_object_get' because the user is only accessing an object's metadata + UPDATE_OBJECTS_SPACES = 'saved_object_update_objects_spaces', // this is separate from 'saved_object_update' because the user is only updating an object's metadata +} + +/** + * The AddAuditEventParams interface contains settings for adding + * audit events via the ISavedObjectsSecurityExtension. + */ +export interface AddAuditEventParams { + /** + * The relevant action + */ + action: AuditAction; + /** + * The outcome of the operation + * 'failure' | 'success' | 'unknown' + */ + outcome?: EcsEventOutcome; + /** + * relevant saved object information + * object containing type & id strings + */ + savedObject?: { type: string; id: string }; + /** + * Array of spaces being added. For + * UPDATE_OBJECTS_SPACES action only + */ + addToSpaces?: readonly string[]; + /** + * Array of spaces being removed. For + * UPDATE_OBJECTS_SPACES action only + */ + deleteFromSpaces?: readonly string[]; + /** + * relevant error information to add to + * the audit event + */ + error?: Error; +} + +/** + * The RedactNamespacesParams interface contains settings for filtering + * namespace access via the ISavedObjectsSecurityExtension. + */ +export interface RedactNamespacesParams { + /** + * relevant saved object + */ + savedObject: SavedObject; + /** + * The authorization map from CheckAuthorizationResult: a map of + * type to record of action/AuthorizationTypeEntry + * (spaces/globallyAuthz'd) + */ + typeMap: AuthorizationTypeMap; +} + +/** + * The ISavedObjectsSecurityExtension interface defines the functions of a saved objects repository security extension. + * It contains functions for checking & enforcing authorization, adding audit events, and redacting namespaces. + */ +export interface ISavedObjectsSecurityExtension { + /** + * Checks authorization of actions on specified types in specified spaces. + * @param params - types, spaces, and actions to check + * @returns CheckAuthorizationResult - the resulting authorization level and authorization map + */ + checkAuthorization: ( + params: CheckAuthorizationParams + ) => Promise>; + + /** + * Enforces authorization of a single action on specified types in specified spaces. + * Throws error if authorization map does not cover specified parameters. + * @param params - map of types/spaces, action to check, and authz map (from CheckAuthorizationResult) + */ + enforceAuthorization: (params: EnforceAuthorizationParams) => void; + + /** + * Adds an audit event for the specified action with relevant information + * @param params - the action, outcome, error, and relevant object/space information + */ + addAuditEvent: (params: AddAuditEventParams) => void; + + /** + * Filters a saved object's spaces based on an authorization map (from CheckAuthorizationResult) + * @param params - the saved object and an authorization map + * @returns SavedObject - saved object with filtered spaces + */ + redactNamespaces: (params: RedactNamespacesParams) => SavedObject; +} diff --git a/packages/core/saved-objects/core-saved-objects-server/src/spaces.ts b/packages/core/saved-objects/core-saved-objects-server/src/spaces.ts new file mode 100644 index 00000000000000..dd5ab0e3f56d22 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-server/src/spaces.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 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. + */ + +/** + * The ISavedObjectsSpacesExtension interface defines the functions of a saved objects repository spaces extension. + * It contains functions for getting the current namespace & getting and array of searchable spaces. + */ +export interface ISavedObjectsSpacesExtension { + /** + * Retrieves the active namespace ID. This is *not* the same as a namespace string. See also: `namespaceIdToString` and + * `namespaceStringToId`. + * + * This takes the saved objects repository's namespace option as a parameter, and doubles as a validation function; if the namespace + * option has already been set some other way, this will throw an error. + */ + getCurrentNamespace: (namespace: string | undefined) => string | undefined; + /** + * Given a list of namespace strings, returns a subset that the user is authorized to search in. + * If a wildcard '*' is used, it is expanded to an explicit list of namespace strings. + */ + getSearchableNamespaces: (namespaces: string[] | undefined) => Promise; +} diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts index 46f562d17aec3e..240abb53db289a 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts @@ -53,7 +53,7 @@ export class SavedObjectsUtils { }; /** - * Creates an empty response for a find operation. This is only intended to be used by saved objects client wrappers. + * Creates an empty response for a find operation. */ public static createEmptyFindResponse = ({ page = FIND_DEFAULT_PAGE, diff --git a/src/core/server/index.ts b/src/core/server/index.ts index dafd53e374fe89..2b7ae3830c69e9 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -343,10 +343,11 @@ export type { SavedObjectsServiceSetup, SavedObjectsServiceStart, SavedObjectsClientProviderOptions, - SavedObjectsClientWrapperFactory, - SavedObjectsClientWrapperOptions, SavedObjectsClientFactory, SavedObjectsClientFactoryProvider, + SavedObjectsEncryptionExtensionFactory, + SavedObjectsSecurityExtensionFactory, + SavedObjectsSpacesExtensionFactory, SavedObjectTypeExcludeFromUpgradeFilterHook, SavedObjectsExportResultDetails, SavedObjectsExportExcludedObject, diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 97b165617b68e8..95eee026cfae92 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -234,7 +234,9 @@ export function createPluginSetupContext( }, savedObjects: { setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider, - addClientWrapper: deps.savedObjects.addClientWrapper, + addEncryptionExtension: deps.savedObjects.addEncryptionExtension, + addSecurityExtension: deps.savedObjects.addSecurityExtension, + addSpacesExtension: deps.savedObjects.addSpacesExtension, registerType: deps.savedObjects.registerType, getKibanaIndex: deps.savedObjects.getKibanaIndex, }, From 9386be3e4a9db62779f7becfb7911f64b2a13605 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Mon, 10 Oct 2022 12:02:24 -0400 Subject: [PATCH 02/47] Adds SO repo extensions, continues wrapper removal --- .../src/lib/scoped_client_provider.ts | 8 +- .../src/lib/update_objects_spaces.test.ts | 2 +- .../src/saved_objects_service.test.ts | 34 -- .../core-saved-objects-server/index.ts | 3 + .../src/encryption.ts | 10 +- .../server/saved_objects/index.ts | 27 +- ...objects_encryption_extension.test.mocks.ts | 18 + ...saved_objects_encryption_extension.test.ts | 204 +++++++ .../saved_objects_encryption_extension.ts | 89 +++ .../security/server/audit/audit_events.ts | 18 +- .../saved_objects/authorization_utils.test.ts | 88 +++ .../saved_objects/authorization_utils.ts | 48 ++ .../security/server/saved_objects/index.ts | 46 +- .../saved_objects_security_extension.test.ts | 522 ++++++++++++++++++ .../saved_objects_security_extension.ts | 237 ++++++++ .../saved_objects_service.test.ts | 9 +- .../saved_objects/saved_objects_service.ts | 13 +- ...ved_objects_spaces_extension.test.mocks.ts | 16 + .../saved_objects_spaces_extension.test.ts | 143 +++++ .../saved_objects_spaces_extension.ts | 55 ++ 20 files changed, 1476 insertions(+), 114 deletions(-) create mode 100644 x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.mocks.ts create mode 100644 x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.ts create mode 100644 x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts create mode 100644 x-pack/plugins/security/server/saved_objects/authorization_utils.test.ts create mode 100644 x-pack/plugins/security/server/saved_objects/authorization_utils.ts create mode 100644 x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts create mode 100644 x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts create mode 100644 x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.mocks.ts create mode 100644 x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts create mode 100644 x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index 3602ca8ce77d7b..413b900b3b48b4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -12,10 +12,10 @@ import { ISavedObjectTypeRegistry, SavedObjectsClientFactory, SavedObjectsClientProviderOptions, - type SavedObjectsEncryptionExtensionFactory, - type SavedObjectsSecurityExtensionFactory, - type SavedObjectsSpacesExtensionFactory, - type SavedObjectsExtensions, + SavedObjectsEncryptionExtensionFactory, + SavedObjectsSecurityExtensionFactory, + SavedObjectsSpacesExtensionFactory, + SavedObjectsExtensions, ENCRYPTION_EXTENSION_ID, SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 6e6c241965056b..541cc552dfbbac 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -231,7 +231,7 @@ describe('#updateObjectsSpaces', () => { it('returns mix of type errors, mget/bulk cluster errors, and successes', async () => { const obj1 = { type: SHAREABLE_HIDDEN_OBJ_TYPE, id: 'id-1' }; // invalid type (Not Found) const obj2 = { type: NON_SHAREABLE_OBJ_TYPE, id: 'id-2' }; // non-shareable type (Bad Request) - // obj3 below is mocking an example where a SOC wrapper attempted to retrieve it in a pre-flight request but it was not found. + // obj3 below is mocking an example where the SOC attempted to retrieve it in a pre-flight request but it was not found. // Since it has 'spaces: []', that indicates it should be skipped for cluster calls and just returned as a Not Found error. // Realistically this would not be intermingled with other requested objects that do not have 'spaces' arrays, but it's fine for this // specific test case. diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index e906f119a65756..9b63a10b5afa63 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -190,34 +190,6 @@ describe('SavedObjectsService', () => { }); }); - describe('#addClientWrapper', () => { - it('registers the wrapper to the clientProvider', async () => { - const coreContext = createCoreContext(); - const soService = new SavedObjectsService(coreContext); - const setup = await soService.setup(createSetupDeps()); - - const wrapperA = jest.fn(); - const wrapperB = jest.fn(); - - setup.addClientWrapper(1, 'A', wrapperA); - setup.addClientWrapper(2, 'B', wrapperB); - - await soService.start(createStartDeps()); - - expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledTimes(2); - expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledWith( - 1, - 'A', - wrapperA - ); - expect(clientProviderInstanceMock.addClientWrapperFactory).toHaveBeenCalledWith( - 2, - 'B', - wrapperB - ); - }); - }); - describe('#registerType', () => { it('registers the type to the internal typeRegistry', async () => { // we mocked registerCoreObjectTypes above, so this test case only reflects direct calls to the registerType method @@ -337,12 +309,6 @@ describe('SavedObjectsService', () => { `"cannot call \`setClientFactoryProvider\` after service startup."` ); - expect(() => { - setup.addClientWrapper(0, 'dummy', jest.fn()); - }).toThrowErrorMatchingInlineSnapshot( - `"cannot call \`addClientWrapper\` after service startup."` - ); - expect(() => { setup.registerType({ name: 'someType', diff --git a/packages/core/saved-objects/core-saved-objects-server/index.ts b/packages/core/saved-objects/core-saved-objects-server/index.ts index dc865a4e83d987..6e7e9791ce5793 100644 --- a/packages/core/saved-objects/core-saved-objects-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server/index.ts @@ -11,6 +11,9 @@ export type { SavedObjectsClientFactoryProvider, SavedObjectsRepositoryFactory, SavedObjectsClientProviderOptions, + SavedObjectsEncryptionExtensionFactory, + SavedObjectsSecurityExtensionFactory, + SavedObjectsSpacesExtensionFactory, } from './src/client_factory'; export type { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './src/contracts'; export type { diff --git a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts index 2508357d4ce8dc..124406fc659bb9 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts @@ -9,20 +9,20 @@ import type { SavedObject } from '@kbn/core-saved-objects-common'; /** - * The CheckAuthorizationParams interface contains settings for checking - * authorization via the ISavedObjectsSecurityExtension. + * The EncryptedObjectDescriptor interface contains settings for describing + * an object to be encrypted or decrpyted. */ export interface EncryptedObjectDescriptor { /** - * A set of types to check. + * The Saved Object type */ type: string; /** - * A set of spaces to check. + * The Saved Object ID */ id: string; /** - * An array of actions to check. + * The namespace where the Saved Object resides */ namespace?: string; } diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts index e2b58e3003d969..b6d7621101d28a 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts @@ -22,8 +22,8 @@ import type { SecurityPluginSetup } from '@kbn/security-plugin/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; import type { EncryptedSavedObjectsService } from '../crypto'; -import { EncryptedSavedObjectsClientWrapper } from './encrypted_saved_objects_client_wrapper'; import { getDescriptorNamespace, normalizeNamespace } from './get_descriptor_namespace'; +import { SavedObjectsEncryptionExtension } from './saved_objects_encryption_extension'; export { normalizeNamespace }; @@ -81,22 +81,15 @@ export function setupSavedObjects({ security, getStartServices, }: SetupSavedObjectsParams): ClientInstanciator { - // Register custom saved object client that will encrypt, decrypt and strip saved object - // attributes where appropriate for any saved object repository request. We choose max possible - // priority for this wrapper to allow all other wrappers to set proper `namespace` for the Saved - // Object (e.g. wrapper registered by the Spaces plugin) before we encrypt attributes since - // `namespace` is included into AAD. - savedObjects.addClientWrapper( - Number.MAX_SAFE_INTEGER, - 'encryptedSavedObjects', - ({ client: baseClient, typeRegistry: baseTypeRegistry, request }) => - new EncryptedSavedObjectsClientWrapper({ - baseClient, - baseTypeRegistry, - service, - getCurrentUser: () => security?.authc.getCurrentUser(request) ?? undefined, - }) - ); + // Register custom saved object extension that will encrypt, decrypt and strip saved object + // attributes where appropriate for any saved object repository request. + savedObjects.addEncryptionExtension(({ typeRegistry: baseTypeRegistry, request }) => { + return new SavedObjectsEncryptionExtension({ + baseTypeRegistry, + service, + getCurrentUser: () => security?.authc.getCurrentUser(request) ?? undefined, + }); + }); return (clientOpts) => { const internalRepositoryAndTypeRegistryPromise = getStartServices().then( diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.mocks.ts new file mode 100644 index 00000000000000..d0d5262c1b83a8 --- /dev/null +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.mocks.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { getDescriptorNamespace } from './get_descriptor_namespace'; + +export const mockGetDescriptorNamespace = jest.fn() as jest.MockedFunction< + typeof getDescriptorNamespace +>; + +jest.mock('./get_descriptor_namespace', () => { + return { + getDescriptorNamespace: mockGetDescriptorNamespace, + }; +}); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.ts new file mode 100644 index 00000000000000..aa958c8c9d1d9e --- /dev/null +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.test.ts @@ -0,0 +1,204 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockGetDescriptorNamespace } from './saved_objects_encryption_extension.test.mocks'; + +import { savedObjectsTypeRegistryMock } from '@kbn/core/server/mocks'; + +import { EncryptionError } from '../crypto'; +import { encryptedSavedObjectsServiceMock } from '../crypto/encrypted_saved_objects_service.mocks'; +import { EncryptionErrorOperation } from '../crypto/encryption_error'; +import { SavedObjectsEncryptionExtension } from './saved_objects_encryption_extension'; + +const KNOWN_TYPE = 'known-type'; +const ATTRIBUTE_TO_STRIP = 'attrSecret'; +const ATTRIBUTE_TO_DECRYPT = 'attrNotSoSecret'; +const DESCRIPTOR_NAMESPACE = 'descriptor-namespace'; +const CURRENT_USER = 'current-user'; + +beforeAll(() => { + // Mock the getDescriptorNamespace result so we don't exercise that functionality in these unit tests + mockGetDescriptorNamespace.mockReturnValue(DESCRIPTOR_NAMESPACE); +}); + +function setup() { + const mockBaseTypeRegistry = savedObjectsTypeRegistryMock.create(); + const mockService = encryptedSavedObjectsServiceMock.createWithTypes([ + { + type: KNOWN_TYPE, + attributesToEncrypt: new Set([ + ATTRIBUTE_TO_STRIP, + { key: ATTRIBUTE_TO_DECRYPT, dangerouslyExposeValue: true }, + ]), + }, + ]); + return { + extension: new SavedObjectsEncryptionExtension({ + baseTypeRegistry: mockBaseTypeRegistry, + service: mockService, + getCurrentUser: jest.fn().mockReturnValue(CURRENT_USER), + }), + service: mockService, + }; +} + +describe('#isEncryptableType', () => { + test('returns true for known types', () => { + const { extension } = setup(); + expect(extension.isEncryptableType(KNOWN_TYPE)).toBe(true); + }); + + test('returns false for unknown types', () => { + const { extension } = setup(); + expect(extension.isEncryptableType('unknown-type')).toBe(false); + }); +}); + +describe('#decryptOrStripResponseAttributes', () => { + const unregisteredSO = { + id: 'some-id', + type: 'unknown-type', + attributes: { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }, + score: 1, + references: [], + }; + + const registeredSO = { + id: 'some-id-2', + type: KNOWN_TYPE, + attributes: { + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + score: 1, + references: [], + }; + + test('does not alter response if type is not registered', async () => { + const { extension, service } = setup(); + + await expect(extension.decryptOrStripResponseAttributes(unregisteredSO)).resolves.toEqual({ + ...unregisteredSO, + attributes: { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }, + }); + + expect(service.decryptAttributes).not.toHaveBeenCalled(); + }); + + test('strips encrypted attributes except for ones with `dangerouslyExposeValue` set to `true` if type is registered', async () => { + const { extension, service } = setup(); + + await expect(extension.decryptOrStripResponseAttributes(registeredSO)).resolves.toEqual({ + ...registeredSO, + attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, + }); + + expect(service.stripOrDecryptAttributes).toHaveBeenCalledTimes(1); + }); + + test('includes both attributes and error if decryption fails.', async () => { + const { extension, service } = setup(); + + const decryptionError = new EncryptionError( + 'something failed', + 'attrNotSoSecret', + EncryptionErrorOperation.Decryption + ); + service.stripOrDecryptAttributes.mockResolvedValue({ + attributes: { attrOne: 'one', attrThree: 'three' }, + error: decryptionError, + }); + + await expect(extension.decryptOrStripResponseAttributes(unregisteredSO)).resolves.toEqual({ + ...unregisteredSO, + attributes: { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }, + }); + + expect(service.stripOrDecryptAttributes).not.toHaveBeenCalled(); + + await expect(extension.decryptOrStripResponseAttributes(registeredSO)).resolves.toEqual({ + ...registeredSO, + attributes: { attrOne: 'one', attrThree: 'three' }, + error: decryptionError, + }); + + expect(service.stripOrDecryptAttributes).toHaveBeenCalledTimes(1); + }); +}); + +describe('#encryptAttributes', () => { + test('does not encrypt attributes if type is not registered', async () => { + const { extension, service } = setup(); + + await expect( + extension.encryptAttributes( + { + type: 'unknown-type', + id: 'mock-saved-object-id', + namespace: undefined, + }, + { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + } + ) + ).resolves.toEqual({ + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + }); + + expect(service.encryptAttributes).not.toBeCalled(); + }); + + test('encrypts attributes if the type is registered', async () => { + const { extension, service } = setup(); + + await expect( + extension.encryptAttributes( + { + type: KNOWN_TYPE, + id: 'mock-saved-object-id', + namespace: undefined, + }, + { + attrOne: 'one', + attrSecret: 'secret', + attrNotSoSecret: 'not-so-secret', + attrThree: 'three', + } + ) + ).resolves.toEqual({ + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }); + + expect(service.encryptAttributes).toBeCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts new file mode 100644 index 00000000000000..32015c8c230369 --- /dev/null +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/saved_objects_encryption_extension.ts @@ -0,0 +1,89 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { + EncryptedObjectDescriptor, + ISavedObjectsEncryptionExtension, + ISavedObjectTypeRegistry, +} from '@kbn/core-saved-objects-server'; +import type { AuthenticatedUser } from '@kbn/security-plugin/common'; + +import type { EncryptedSavedObjectsService } from '../crypto'; +import { getDescriptorNamespace } from './get_descriptor_namespace'; + +/** + * @internal Only exported for unit testing. + */ +export interface Params { + baseTypeRegistry: ISavedObjectTypeRegistry; + service: Readonly; + getCurrentUser: () => AuthenticatedUser | undefined; +} + +export class SavedObjectsEncryptionExtension implements ISavedObjectsEncryptionExtension { + readonly _baseTypeRegistry: ISavedObjectTypeRegistry; + readonly _service: Readonly; + readonly _getCurrentUser: () => AuthenticatedUser | undefined; + + constructor({ baseTypeRegistry, service, getCurrentUser }: Params) { + this._baseTypeRegistry = baseTypeRegistry; + this._service = service; + this._getCurrentUser = getCurrentUser; + } + + isEncryptableType(type: string) { + return this._service.isRegistered(type); + } + + async decryptOrStripResponseAttributes>( + response: R, + originalAttributes?: T + ): Promise { + if (response.attributes && this._service.isRegistered(response.type)) { + const namespace = response.namespaces ? response.namespaces[0] : undefined; + const normalizedDescriptor = { + id: response.id, + type: response.type, + namespace: getDescriptorNamespace(this._baseTypeRegistry, response.type, namespace), + }; + // Error is returned when decryption fails, and in this case encrypted attributes will be + // stripped from the returned attributes collection. That will let consumer decide whether to + // fail or handle recovery gracefully. + const { attributes, error } = await this._service.stripOrDecryptAttributes( + normalizedDescriptor, + response.attributes as Record, + originalAttributes as Record, + { user: this._getCurrentUser() } + ); + + return { ...response, attributes, ...(error && { error }) }; + } + + return response; + } + + async encryptAttributes>( + descriptor: EncryptedObjectDescriptor, + attributes: T + ): Promise { + if (!this._service.isRegistered(descriptor.type)) { + return attributes; + } + + const { type, id, namespace } = descriptor; + + const normalizedDescriptor = { + type, + id, + namespace: getDescriptorNamespace(this._baseTypeRegistry, type, namespace), + }; + return this._service.encryptAttributes(normalizedDescriptor, attributes, { + user: this._getCurrentUser(), + }); + } +} diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index deb4b356c9f953..1d077115970a62 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -5,6 +5,7 @@ * 2.0. */ +import type { AddAuditEventParams as SavedObjectEventParams } from '@kbn/core-saved-objects-server'; import type { EcsEventOutcome, EcsEventType, KibanaRequest, LogMeta } from '@kbn/core/server'; import type { AuthenticationProvider } from '../../common/model'; @@ -288,14 +289,15 @@ const savedObjectAuditTypes: Record = { saved_object_update_objects_spaces: 'change', }; -export interface SavedObjectEventParams { - action: SavedObjectAction; - outcome?: EcsEventOutcome; - savedObject?: NonNullable['saved_object']; - addToSpaces?: readonly string[]; - deleteFromSpaces?: readonly string[]; - error?: Error; -} +// This definition comes from '@kbn/core-saved-objects-server' now per Joe's changes and Pierre's migration to packages +// export interface SavedObjectEventParams { +// action: SavedObjectAction; +// outcome?: EcsEventOutcome; +// savedObject?: NonNullable['saved_object']; +// addToSpaces?: readonly string[]; +// deleteFromSpaces?: readonly string[]; +// error?: Error; +// } export function savedObjectEvent({ action, diff --git a/x-pack/plugins/security/server/saved_objects/authorization_utils.test.ts b/x-pack/plugins/security/server/saved_objects/authorization_utils.test.ts new file mode 100644 index 00000000000000..cb87c9e220bb21 --- /dev/null +++ b/x-pack/plugins/security/server/saved_objects/authorization_utils.test.ts @@ -0,0 +1,88 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AuthorizationTypeMap } from '@kbn/core-saved-objects-server'; + +import { getEnsureAuthorizedActionResult, isAuthorizedInAllSpaces } from './authorization_utils'; + +describe('getEnsureAuthorizedActionResult', () => { + const typeMap: AuthorizationTypeMap<'action'> = new Map([ + ['type', { action: { authorizedSpaces: ['space-id'] } }], + ]); + + test('returns the appropriate result if it is in the typeMap', () => { + const result = getEnsureAuthorizedActionResult('type', 'action', typeMap); + expect(result).toEqual({ authorizedSpaces: ['space-id'] }); + }); + + test('returns an unauthorized result if it is not in the typeMap', () => { + const result = getEnsureAuthorizedActionResult('other-type', 'action', typeMap); + expect(result).toEqual({ authorizedSpaces: [] }); + }); +}); + +describe('isAuthorizedInAllSpaces', () => { + const typeMap: AuthorizationTypeMap<'action'> = new Map([ + ['type-1', { action: { authorizedSpaces: [], isGloballyAuthorized: true } }], + ['type-2', { action: { authorizedSpaces: ['space-1', 'space-2'] } }], + ['type-3', { action: { authorizedSpaces: [] } }], + // type-4 is not present in the results + ]); + + test('returns true if the user is authorized for the type in the given spaces', () => { + const type1Result = isAuthorizedInAllSpaces( + 'type-1', + 'action', + ['space-1', 'space-2', 'space-3'], + typeMap + ); + expect(type1Result).toBe(true); + + // check for all authorized spaces + const type2AllSpacesResult = isAuthorizedInAllSpaces( + 'type-2', + 'action', + ['space-1', 'space-2'], + typeMap + ); + expect(type2AllSpacesResult).toBe(true); + + // check for subset of authorized spaces + const type2Space2Result = isAuthorizedInAllSpaces('type-2', 'action', ['space-2'], typeMap); + expect(type2Space2Result).toBe(true); + }); + + test('returns false if the user is not authorized for the type in the given spaces', () => { + const type2Result = isAuthorizedInAllSpaces( + 'type-2', + 'action', + [ + 'space-1', + 'space-2', + 'space-3', // the user is not authorized for this type and action in space-3 + ], + typeMap + ); + expect(type2Result).toBe(false); + + const type3Result = isAuthorizedInAllSpaces( + 'type-3', + 'action', + ['space-1'], // the user is not authorized for this type and action in any space + typeMap + ); + expect(type3Result).toBe(false); + + const type4Result = isAuthorizedInAllSpaces( + 'type-4', + 'action', + ['space-1'], // the user is not authorized for this type and action in any space + typeMap + ); + expect(type4Result).toBe(false); + }); +}); diff --git a/x-pack/plugins/security/server/saved_objects/authorization_utils.ts b/x-pack/plugins/security/server/saved_objects/authorization_utils.ts new file mode 100644 index 00000000000000..21b8055d2d4282 --- /dev/null +++ b/x-pack/plugins/security/server/saved_objects/authorization_utils.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { AuthorizationTypeEntry, AuthorizationTypeMap } from '@kbn/core-saved-objects-server'; + +/** + * Helper function that, given an `CheckAuthorizationResult`, checks to see what spaces the user is authorized to perform a given action for + * the given object type. + * + * Only exported for unit testing purposes. + * + * @param {string} objectType the object type to check. + * @param {T} action the action to check. + * @param {AuthorizationTypeMap} typeMap the typeMap from an CheckAuthorizationResult. + */ +export function getEnsureAuthorizedActionResult( + objectType: string, + action: A, + typeMap: AuthorizationTypeMap +): AuthorizationTypeEntry { + const record = typeMap.get(objectType) ?? ({} as Record); + return record[action] ?? { authorizedSpaces: [] }; +} + +/** + * Helper function that, given an `CheckAuthorizationResult`, ensures that the user is authorized to perform a given action for the given + * object type in the given spaces. + * + * @param objectType The object type to check. + * @param action The action to check. + * @param spaces The spaces to check. + * @param typeMap The typeMap from a CheckAuthorizationResult. + */ +export function isAuthorizedInAllSpaces( + objectType: string, + action: T, + spaces: string[], + typeMap: AuthorizationTypeMap +) { + const actionResult = getEnsureAuthorizedActionResult(objectType, action, typeMap); + const { authorizedSpaces, isGloballyAuthorized } = actionResult; + const authorizedSpacesSet = new Set(authorizedSpaces); + return isGloballyAuthorized || spaces.every((space) => authorizedSpacesSet.has(space)); +} diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index 71a945e9517716..a4599c7737b915 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -10,8 +10,7 @@ import { SavedObjectsClient } from '@kbn/core/server'; import type { AuditServiceSetup } from '../audit'; import type { AuthorizationServiceSetupInternal } from '../authorization'; -import type { SpacesService } from '../plugin'; -import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper'; +import { SavedObjectsSecurityExtension } from './saved_objects_security_extension'; interface SetupSavedObjectsParams { audit: AuditServiceSetup; @@ -20,50 +19,33 @@ interface SetupSavedObjectsParams { 'mode' | 'actions' | 'checkSavedObjectsPrivilegesWithRequest' >; savedObjects: CoreSetup['savedObjects']; - getSpacesService(): SpacesService | undefined; } -export type { - EnsureAuthorizedDependencies, - EnsureAuthorizedOptions, - EnsureAuthorizedResult, - EnsureAuthorizedActionResult, -} from './ensure_authorized'; - -export { - ensureAuthorized, - getEnsureAuthorizedActionResult, - isAuthorizedForObjectInAllSpaces, -} from './ensure_authorized'; - -export function setupSavedObjects({ - audit, - authz, - savedObjects, - getSpacesService, -}: SetupSavedObjectsParams) { +export function setupSavedObjects({ audit, authz, savedObjects }: SetupSavedObjectsParams) { savedObjects.setClientFactoryProvider( + // This is not used by Kibana itself, but it can be leveraged for Kibana to use a third-party authentication header if there is a custom + // authentication layer sitting between Kibana and Elasticsearch, and if Elasticsearch security is disabled. It's unclear if it's even + // possible for that to function anymore, perhaps we should deprecate this custom client factory provider and remove it in 9.0? (repositoryFactory) => - ({ request, includedHiddenTypes }) => { + ({ request, includedHiddenTypes, extensions }) => { return new SavedObjectsClient( authz.mode.useRbacForRequest(request) - ? repositoryFactory.createInternalRepository(includedHiddenTypes) - : repositoryFactory.createScopedRepository(request, includedHiddenTypes) + ? repositoryFactory.createInternalRepository(includedHiddenTypes, extensions) + : repositoryFactory.createScopedRepository(request, includedHiddenTypes, extensions) ); } ); - savedObjects.addClientWrapper(Number.MAX_SAFE_INTEGER - 1, 'security', ({ client, request }) => { + savedObjects.addSecurityExtension(({ request }) => { return authz.mode.useRbacForRequest(request) - ? new SecureSavedObjectsClientWrapper({ + ? new SavedObjectsSecurityExtension({ actions: authz.actions, auditLogger: audit.asScoped(request), - baseClient: client, - checkSavedObjectsPrivilegesAsCurrentUser: - authz.checkSavedObjectsPrivilegesWithRequest(request), + checkPrivileges: authz.checkSavedObjectsPrivilegesWithRequest(request), errors: SavedObjectsClient.errors, - getSpacesService, }) - : client; + : undefined; }); } + +export { SavedObjectsSecurityExtension }; diff --git a/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts new file mode 100644 index 00000000000000..89d5a0945accef --- /dev/null +++ b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts @@ -0,0 +1,522 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { AuditAction } from '@kbn/core-saved-objects-server'; +import type { EcsEventOutcome, SavedObjectsClient } from '@kbn/core/server'; + +import { auditLoggerMock } from '../audit/mocks'; +import type { CheckSavedObjectsPrivileges } from '../authorization'; +import { Actions } from '../authorization'; +import type { CheckPrivilegesResponse } from '../authorization/types'; +import { SavedObjectsSecurityExtension } from './saved_objects_security_extension'; + +function setup() { + const actions = new Actions('some-version'); + jest + .spyOn(actions.savedObject, 'get') + .mockImplementation((type: string, action: string) => `mock-saved_object:${type}/${action}`); + const auditLogger = auditLoggerMock.create(); + const errors = { + decorateForbiddenError: jest.fn().mockImplementation((err) => err), + decorateGeneralError: jest.fn().mockImplementation((err) => err), + } as unknown as jest.Mocked; + const checkPrivileges: jest.MockedFunction = jest.fn(); + const securityExtension = new SavedObjectsSecurityExtension({ + actions, + auditLogger, + errors, + checkPrivileges, + }); + return { actions, auditLogger, errors, checkPrivileges, securityExtension }; +} + +describe('#checkAuthorization', () => { + // These arguments are used for all unit tests below + const types = new Set(['a', 'b', 'c']); + const spaces = new Set(['x', 'y']); + const actions = ['foo', 'bar']; + + const fullyAuthorizedCheckPrivilegesResponse = { + hasAllRequested: true, + privileges: { + kibana: [ + { privilege: 'mock-saved_object:a/foo', authorized: true }, + { privilege: 'mock-saved_object:a/bar', authorized: true }, + { privilege: 'login:', authorized: true }, + { resource: 'x', privilege: 'mock-saved_object:b/foo', authorized: true }, + { resource: 'x', privilege: 'mock-saved_object:b/bar', authorized: true }, + { resource: 'x', privilege: 'mock-saved_object:c/foo', authorized: true }, + { resource: 'x', privilege: 'mock-saved_object:c/bar', authorized: true }, + { resource: 'y', privilege: 'mock-saved_object:b/foo', authorized: true }, + { resource: 'y', privilege: 'mock-saved_object:b/bar', authorized: true }, + { resource: 'y', privilege: 'mock-saved_object:c/foo', authorized: true }, + { resource: 'y', privilege: 'mock-saved_object:c/bar', authorized: true }, + ], + }, + } as CheckPrivilegesResponse; + + test('calls checkPrivileges with expected privilege actions and namespaces', async () => { + const { securityExtension, checkPrivileges } = setup(); + checkPrivileges.mockResolvedValue(fullyAuthorizedCheckPrivilegesResponse); // Return any well-formed response to avoid an unhandled error + + await securityExtension.checkAuthorization({ types, spaces, actions }); + expect(checkPrivileges).toHaveBeenCalledWith( + [ + 'mock-saved_object:a/foo', + 'mock-saved_object:a/bar', + 'mock-saved_object:b/foo', + 'mock-saved_object:b/bar', + 'mock-saved_object:c/foo', + 'mock-saved_object:c/bar', + 'login:', + ], + [...spaces] + ); + }); + + test('throws an error when `types` is empty', async () => { + const { securityExtension, checkPrivileges } = setup(); + + await expect( + securityExtension.checkAuthorization({ types: new Set(), spaces, actions }) + ).rejects.toThrowError('No types specified for authorization check'); + expect(checkPrivileges).not.toHaveBeenCalled(); + }); + + test('throws an error when `spaces` is empty', async () => { + const { securityExtension, checkPrivileges } = setup(); + + await expect( + securityExtension.checkAuthorization({ types, spaces: new Set(), actions }) + ).rejects.toThrowError('No spaces specified for authorization check'); + expect(checkPrivileges).not.toHaveBeenCalled(); + }); + + test('throws an error when `actions` is empty', async () => { + const { securityExtension, checkPrivileges } = setup(); + + await expect( + securityExtension.checkAuthorization({ types, spaces, actions: [] }) + ).rejects.toThrowError('No actions specified for authorization check'); + expect(checkPrivileges).not.toHaveBeenCalled(); + }); + + test('throws an error when privilege check fails', async () => { + const { securityExtension, checkPrivileges } = setup(); + checkPrivileges.mockRejectedValue(new Error('Oh no!')); + + await expect( + securityExtension.checkAuthorization({ types, spaces, actions }) + ).rejects.toThrowError('Oh no!'); + }); + + test('fully authorized', async () => { + const { securityExtension, checkPrivileges } = setup(); + checkPrivileges.mockResolvedValue(fullyAuthorizedCheckPrivilegesResponse); + + const result = await securityExtension.checkAuthorization({ types, spaces, actions }); + expect(result).toEqual({ + status: 'fully_authorized', + typeMap: new Map() + .set('a', { + foo: { isGloballyAuthorized: true, authorizedSpaces: [] }, + bar: { isGloballyAuthorized: true, authorizedSpaces: [] }, + // Technically, 'login:' is not a saved object action, it is a Kibana privilege -- however, we include it in the `typeMap` results + // for ease of use with the `redactNamespaces` function. The user is never actually authorized to "login" for a given object type, + // they are authorized to log in on a per-space basis, and this is applied to each object type in the typeMap result accordingly. + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('b', { + foo: { authorizedSpaces: ['x', 'y'] }, + bar: { authorizedSpaces: ['x', 'y'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('c', { + foo: { authorizedSpaces: ['x', 'y'] }, + bar: { authorizedSpaces: ['x', 'y'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }), + }); + }); + + test('partially authorized', async () => { + const { securityExtension, checkPrivileges } = setup(); + checkPrivileges.mockResolvedValue({ + hasAllRequested: false, + privileges: { + kibana: [ + // For type 'a', the user is authorized to use 'foo' action but not 'bar' action (all spaces) + // For type 'b', the user is authorized to use 'foo' action but not 'bar' action (both spaces) + // For type 'c', the user is authorized to use both actions in space 'x' but not space 'y' + { privilege: 'mock-saved_object:a/foo', authorized: true }, + { privilege: 'mock-saved_object:a/bar', authorized: false }, + { privilege: 'mock-saved_object:a/bar', authorized: true }, // fail-secure check + { resource: 'x', privilege: 'mock-saved_object:b/foo', authorized: true }, + { resource: 'x', privilege: 'mock-saved_object:b/bar', authorized: false }, + { resource: 'x', privilege: 'mock-saved_object:c/foo', authorized: true }, + { privilege: 'mock-saved_object:c/foo', authorized: false }, // inverse fail-secure check + { resource: 'x', privilege: 'mock-saved_object:c/bar', authorized: true }, + { resource: 'x', privilege: 'login:', authorized: true }, + { resource: 'y', privilege: 'mock-saved_object:b/foo', authorized: true }, + { resource: 'y', privilege: 'mock-saved_object:b/bar', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:c/foo', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:c/bar', authorized: false }, + { privilege: 'mock-saved_object:c/bar', authorized: true }, // fail-secure check + { resource: 'y', privilege: 'mock-saved_object:c/bar', authorized: true }, // fail-secure check + { resource: 'y', privilege: 'login:', authorized: true }, + // The fail-secure checks are a contrived scenario, as we *shouldn't* get both an unauthorized and authorized result for a given resource... + // However, in case we do, we should fail-secure (authorized + unauthorized = unauthorized) + ], + }, + } as CheckPrivilegesResponse); + + const result = await securityExtension.checkAuthorization({ types, spaces, actions }); + expect(result).toEqual({ + status: 'partially_authorized', + typeMap: new Map() + .set('a', { + foo: { isGloballyAuthorized: true, authorizedSpaces: [] }, + ['login:']: { authorizedSpaces: ['x', 'y'] }, + }) + .set('b', { + foo: { authorizedSpaces: ['x', 'y'] }, + ['login:']: { authorizedSpaces: ['x', 'y'] }, + }) + .set('c', { + foo: { authorizedSpaces: ['x'] }, + bar: { authorizedSpaces: ['x'] }, + ['login:']: { authorizedSpaces: ['x', 'y'] }, + }), + }); + }); + + test('unauthorized', async () => { + const { securityExtension, checkPrivileges } = setup(); + checkPrivileges.mockResolvedValue({ + hasAllRequested: false, + privileges: { + kibana: [ + { privilege: 'mock-saved_object:a/foo', authorized: false }, + { privilege: 'mock-saved_object:a/bar', authorized: false }, + { privilege: 'mock-saved_object:a/bar', authorized: true }, // fail-secure check + { resource: 'x', privilege: 'mock-saved_object:b/foo', authorized: false }, + { resource: 'x', privilege: 'mock-saved_object:b/bar', authorized: false }, + { resource: 'x', privilege: 'mock-saved_object:c/foo', authorized: false }, + { resource: 'x', privilege: 'mock-saved_object:c/bar', authorized: false }, + { resource: 'x', privilege: 'login:', authorized: false }, + { resource: 'x', privilege: 'login:', authorized: true }, // fail-secure check + { resource: 'y', privilege: 'mock-saved_object:a/foo', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:a/bar', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:b/foo', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:b/bar', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:c/foo', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:c/bar', authorized: false }, + { privilege: 'mock-saved_object:c/bar', authorized: true }, // fail-secure check + { resource: 'y', privilege: 'mock-saved_object:c/bar', authorized: true }, // fail-secure check + { resource: 'y', privilege: 'login:', authorized: true }, // should *not* result in a 'partially_authorized' status + // The fail-secure checks are a contrived scenario, as we *shouldn't* get both an unauthorized and authorized result for a given resource... + // However, in case we do, we should fail-secure (authorized + unauthorized = unauthorized) + ], + }, + } as CheckPrivilegesResponse); + + const result = await securityExtension.checkAuthorization({ types, spaces, actions }); + expect(result).toEqual({ + // The user is authorized to log into space Y, but they are not authorized to take any actions on any of the requested object types. + // Therefore, the status is 'unauthorized'. + status: 'unauthorized', + typeMap: new Map() + .set('a', { ['login:']: { authorizedSpaces: ['y'] } }) + .set('b', { ['login:']: { authorizedSpaces: ['y'] } }) + .set('c', { ['login:']: { authorizedSpaces: ['y'] } }), + }); + }); + + test('conflicting privilege failsafe', async () => { + const conflictingPrivilegesResponse = { + hasAllRequested: true, + privileges: { + kibana: [ + // redundant conflicting privileges for space X, type B, action Foo + { resource: 'x', privilege: 'mock-saved_object:b/foo', authorized: true }, + { resource: 'x', privilege: 'mock-saved_object:b/foo', authorized: false }, + { resource: 'y', privilege: 'mock-saved_object:b/foo', authorized: true }, + ], + }, + } as CheckPrivilegesResponse; + + const { securityExtension, checkPrivileges } = setup(); + checkPrivileges.mockResolvedValue(conflictingPrivilegesResponse); + + const result = await securityExtension.checkAuthorization({ types, spaces, actions }); + expect(result).toEqual({ + status: 'fully_authorized', + typeMap: new Map().set('b', { + foo: { authorizedSpaces: ['y'] }, // should NOT be authorized for conflicted privilege + }), + }); + }); +}); + +describe('#enforceAuthorization', () => { + test('fully authorized', () => { + const { securityExtension } = setup(); + + const authorizationResult = { + status: 'fully_authorized', + typeMap: new Map() + .set('a', { + foo: { isGloballyAuthorized: true, authorizedSpaces: [] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('b', { + foo: { authorizedSpaces: ['x', 'y'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('c', { + foo: { authorizedSpaces: ['y', 'z'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }), + }; + + const spacesToEnforce = new Set(['x', 'y', 'z']); + + expect(() => + securityExtension.enforceAuthorization({ + typesAndSpaces: new Map([ + ['a', spacesToEnforce], + ['b', new Set(['x', 'y'])], + ['c', new Set(['y', 'z'])], + ]), + action: 'foo', + typeMap: authorizationResult.typeMap, + }) + ).not.toThrowError(); + }); + + test('partially authorized', () => { + const { securityExtension } = setup(); + + const authorizationResult = { + status: 'partially_authorized', + typeMap: new Map() + .set('a', { + foo: { isGloballyAuthorized: true, authorizedSpaces: [] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('b', { + foo: { authorizedSpaces: ['x'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('c', { + foo: { authorizedSpaces: ['z'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }), + }; + + const spacesToEnforce = new Set(['x', 'y', 'z']); + + expect(() => + securityExtension.enforceAuthorization({ + typesAndSpaces: new Map([ + ['a', spacesToEnforce], + ['b', new Set(['x', 'y'])], + ['c', new Set(['y', 'z'])], + ]), + action: 'foo', + typeMap: authorizationResult.typeMap, + }) + ).toThrowError('Unable to foo b,c'); + }); + + test('unauthorized', () => { + const { securityExtension } = setup(); + + const authorizationResult = { + status: 'unauthorized', + typeMap: new Map() + .set('a', { + foo: { authorizedSpaces: ['x'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('b', { + foo: { authorizedSpaces: ['y'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }) + .set('c', { + foo: { authorizedSpaces: ['z'] }, + ['login:']: { isGloballyAuthorized: true, authorizedSpaces: [] }, + }), + }; + + expect(() => + securityExtension.enforceAuthorization({ + typesAndSpaces: new Map([ + ['a', new Set(['y', 'z'])], + ['b', new Set(['x', 'z'])], + ['c', new Set(['x', 'y'])], + ]), + action: 'foo', + typeMap: authorizationResult.typeMap, + }) + ).toThrowError('Unable to foo a,b,c'); + }); +}); + +describe('#addAuditEvent', () => { + test(`adds an unknown audit event`, async () => { + const { auditLogger, securityExtension } = setup(); + const action = AuditAction.UPDATE_OBJECTS_SPACES; + const outcome: EcsEventOutcome = 'unknown'; + const savedObject = { type: 'dashboard', id: '3' }; + const spaces = ['space-id']; + + const auditParams = { + action, + outcome, + savedObject, + deleteFromSpaces: spaces, + }; + + securityExtension.addAuditEvent(auditParams); + + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action, + outcome, + }), + kibana: savedObject + ? expect.objectContaining({ + saved_object: savedObject, + delete_from_spaces: spaces, + }) + : expect.anything(), + message: `User is updating spaces of ${savedObject.type} [id=${savedObject.id}]`, + }) + ); + }); + + test(`adds a success audit event`, async () => { + const { auditLogger, securityExtension } = setup(); + const action = AuditAction.UPDATE_OBJECTS_SPACES; + const outcome: EcsEventOutcome = 'success'; + const savedObject = { type: 'dashboard', id: '3' }; + const spaces = ['space-id']; + + const auditParams = { + action, + outcome, + savedObject, + addToSpaces: spaces, + }; + + securityExtension.addAuditEvent(auditParams); + + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action, + outcome, + }), + kibana: savedObject + ? expect.objectContaining({ + saved_object: savedObject, + add_to_spaces: spaces, + }) + : expect.anything(), + message: `User has updated spaces of ${savedObject.type} [id=${savedObject.id}]`, + }) + ); + }); + + test(`adds a failure audit event`, async () => { + const { auditLogger, securityExtension } = setup(); + const action = AuditAction.DELETE; + const outcome: EcsEventOutcome = 'failure'; + const savedObject = { type: 'dashboard', id: '3' }; + const error: Error = { + name: 'test_error', + message: 'this is just a test', + }; + + const auditParams = { + action, + outcome, + savedObject, + error, + }; + + securityExtension.addAuditEvent(auditParams); + + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + error: { code: error.name, message: error.message }, + event: expect.objectContaining({ + action, + outcome, + }), + kibana: savedObject + ? expect.objectContaining({ + saved_object: savedObject, + }) + : expect.anything(), + message: `Failed attempt to delete ${savedObject.type} [id=${savedObject.id}]`, + }) + ); + }); +}); + +describe('#redactNamespaces', () => { + test(`filters namespaces that the user doesn't have access to`, () => { + const { securityExtension } = setup(); + + const typeMap = new Map().set('so-type', { + // redact is only concerned with 'login' attribute, not specific action + ['login:']: { authorizedSpaces: ['authorized-space'] }, + }); + + const so = { + id: 'some-id', + type: 'so-type', + namespaces: ['authorized-space', 'unauthorized-space'], + attributes: { + test: 'attr', + }, + score: 1, + references: [], + }; + + const result = securityExtension.redactNamespaces({ typeMap, savedObject: so }); + expect(result).toEqual(expect.objectContaining({ namespaces: ['authorized-space', '?'] })); + }); + + test(`does not redact on isGloballyAuthorized`, () => { + const { securityExtension } = setup(); + + const typeMap = new Map().set('so-type', { + // redact is only concerned with 'login' attribute, not specific action + ['login:']: { isGloballyAuthorized: true }, + }); + + const so = { + id: 'some-id', + type: 'so-type', + namespaces: ['space-a', 'space-b', 'space-c'], + attributes: { + test: 'attr', + }, + score: 1, + references: [], + }; + + const result = securityExtension.redactNamespaces({ typeMap, savedObject: so }); + expect(result).toEqual( + expect.objectContaining({ namespaces: ['space-a', 'space-b', 'space-c'] }) + ); + }); +}); diff --git a/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts new file mode 100644 index 00000000000000..739b5938abdc27 --- /dev/null +++ b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts @@ -0,0 +1,237 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { SavedObjectsClient } from '@kbn/core-saved-objects-api-server-internal'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { + AddAuditEventParams, + AuthorizationTypeEntry, + AuthorizationTypeMap, + CheckAuthorizationParams, + CheckAuthorizationResult, + EnforceAuthorizationParams, + ISavedObjectsSecurityExtension, + RedactNamespacesParams, +} from '@kbn/core-saved-objects-server'; + +import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../common/constants'; +import type { AuditLogger } from '../audit'; +import { savedObjectEvent } from '../audit'; +import type { Actions, CheckSavedObjectsPrivileges } from '../authorization'; +import type { CheckPrivilegesResponse } from '../authorization/types'; +import { isAuthorizedInAllSpaces } from './authorization_utils'; + +interface Params { + actions: Actions; + auditLogger: AuditLogger; + errors: SavedObjectsClient['errors']; + checkPrivileges: CheckSavedObjectsPrivileges; +} + +export class SavedObjectsSecurityExtension implements ISavedObjectsSecurityExtension { + private readonly actions: Actions; + private readonly auditLogger: AuditLogger; + private readonly errors: SavedObjectsClient['errors']; + private readonly checkPrivilegesFunc: CheckSavedObjectsPrivileges; + + constructor({ actions, auditLogger, errors, checkPrivileges }: Params) { + this.actions = actions; + this.auditLogger = auditLogger; + this.errors = errors; + this.checkPrivilegesFunc = checkPrivileges; + } + + async checkAuthorization( + params: CheckAuthorizationParams + ): Promise> { + const { types, spaces, actions, options = { allowGlobalResource: false } } = params; + const { allowGlobalResource } = options; + if (types.size === 0) { + throw new Error('No types specified for authorization check'); + } + if (spaces.size === 0) { + throw new Error('No spaces specified for authorization check'); + } + if (actions.length === 0) { + throw new Error('No actions specified for authorization check'); + } + const typesArray = [...types]; + const privilegeActionsMap = new Map( + typesArray.flatMap((type) => + actions.map((action) => [this.actions.savedObject.get(type, action), { type, action }]) + ) + ); + const privilegeActions = [...privilegeActionsMap.keys(), this.actions.login]; // Always check login action, we will need it later for redacting namespaces + const { hasAllRequested, privileges } = await this.checkPrivileges( + privilegeActions, + getAuthorizableSpaces(spaces, allowGlobalResource) + ); + + const missingPrivileges = getMissingPrivileges(privileges); + const typeMap = privileges.kibana.reduce>( + (acc, { resource, privilege }) => { + const missingPrivilegesAtResource = + (resource && missingPrivileges.get(resource)?.has(privilege)) || + (!resource && missingPrivileges.get(undefined)?.has(privilege)); + + if (missingPrivilegesAtResource) { + return acc; + } + + let objTypes: string[]; + let action: A; + if (privilege === this.actions.login) { + // Technically, 'login:' is not a saved object action, it is a Kibana privilege -- however, we include it in the `typeMap` results + // for ease of use with the `redactNamespaces` function. The user is never actually authorized to "login" for a given object type, + // they are authorized to log in on a per-space basis, and this is applied to each object type in the typeMap result accordingly. + objTypes = typesArray; + action = this.actions.login as A; + } else { + const entry = privilegeActionsMap.get(privilege)!; // always defined + objTypes = [entry.type]; + action = entry.action; + } + + for (const type of objTypes) { + const actionAuthorizations = acc.get(type) ?? ({} as Record); + const authorization: AuthorizationTypeEntry = actionAuthorizations[action] ?? { + authorizedSpaces: [], + }; + + if (resource === undefined) { + acc.set(type, { + ...actionAuthorizations, + [action]: { ...authorization, isGloballyAuthorized: true }, + }); + } else { + acc.set(type, { + ...actionAuthorizations, + [action]: { + ...authorization, + authorizedSpaces: authorization.authorizedSpaces.concat(resource), + }, + }); + } + } + return acc; + }, + new Map() + ); + + if (hasAllRequested) { + return { typeMap, status: 'fully_authorized' }; + } else if (typeMap.size > 0) { + for (const entry of typeMap.values()) { + const typeActions = Object.keys(entry); + if (actions.some((a) => typeActions.includes(a))) { + // Only return 'partially_authorized' if the user is actually authorized for one of the actions they requested + // (e.g., not just the 'login:' action) + return { typeMap, status: 'partially_authorized' }; + } + } + } + return { typeMap, status: 'unauthorized' }; + } + + enforceAuthorization(params: EnforceAuthorizationParams): void { + const { typesAndSpaces, action, typeMap, auditCallback } = params; + const unauthorizedTypes = new Set(); + for (const [type, spaces] of typesAndSpaces) { + const spacesArray = [...spaces]; + if (!isAuthorizedInAllSpaces(type, action, spacesArray, typeMap)) { + unauthorizedTypes.add(type); + } + } + + if (unauthorizedTypes.size > 0) { + const targetTypes = [...unauthorizedTypes].sort().join(','); + const msg = `Unable to ${action} ${targetTypes}`; + const error = this.errors.decorateForbiddenError(new Error(msg)); + auditCallback?.(error); + throw error; + } + auditCallback?.(); + } + + addAuditEvent(params: AddAuditEventParams): void { + if (this.auditLogger.enabled) { + const auditEvent = savedObjectEvent(params); + this.auditLogger.log(auditEvent); + } + } + + redactNamespaces(params: RedactNamespacesParams): SavedObject { + const { savedObject, typeMap } = params; + const loginAction = this.actions.login as A; // This typeMap came from the `checkAuthorization` function, which always checks privileges for the "login" action (in addition to what the consumer requested) + const actionRecord = typeMap.get(savedObject.type); + const entry: AuthorizationTypeEntry = actionRecord?.[loginAction] ?? { authorizedSpaces: [] }; // fail-secure if attribute is not defined + const { authorizedSpaces, isGloballyAuthorized } = entry; + + if (isGloballyAuthorized || !savedObject.namespaces?.length) { + return savedObject; + } + const authorizedSpacesSet = new Set(authorizedSpaces); + const redactedSpaces = savedObject.namespaces + ?.map((x) => (x === ALL_SPACES_ID || authorizedSpacesSet.has(x) ? x : UNKNOWN_SPACE)) + .sort(namespaceComparator); + return { ...savedObject, namespaces: redactedSpaces }; + } + + private async checkPrivileges( + actions: string | string[], + namespaceOrNamespaces?: string | Array + ) { + try { + return await this.checkPrivilegesFunc(actions, namespaceOrNamespaces); + } catch (error) { + throw this.errors.decorateGeneralError(error, error.body && error.body.reason); + } + } +} + +/** + * The '*' string is an identifier for All Spaces, but that is also the identifier for the Global Resource. We should not check + * authorization against it unless explicitly specified, because you can only check privileges for the Global Resource *or* individual + * resources (not both). + */ +function getAuthorizableSpaces(spaces: Set, allowGlobalResource?: boolean) { + const spacesArray = [...spaces]; + if (allowGlobalResource) return spacesArray; + return spacesArray.filter((x) => x !== ALL_SPACES_ID); +} + +function getMissingPrivileges(privileges: CheckPrivilegesResponse['privileges']) { + return privileges.kibana.reduce>>( + (acc, { resource, privilege, authorized }) => { + if (!authorized) { + if (resource) { + acc.set(resource, (acc.get(resource) || new Set()).add(privilege)); + } + // Fail-secure: if a user is not authorized for a specific resource, they are not authorized for the global resource too (global resource is undefined) + // The inverse is not true; if a user is not authorized for the global resource, they may still be authorized for a specific resource + acc.set(undefined, (acc.get(undefined) || new Set()).add(privilege)); + } + return acc; + }, + new Map() + ); +} + +/** + * Utility function to sort potentially redacted namespaces. + * Sorts in a case-insensitive manner, and ensures that redacted namespaces ('?') always show up at the end of the array. + */ +function namespaceComparator(a: string, b: string) { + if (a === UNKNOWN_SPACE && b !== UNKNOWN_SPACE) { + return 1; + } else if (a !== UNKNOWN_SPACE && b === UNKNOWN_SPACE) { + return -1; + } + const A = a.toUpperCase(); + const B = b.toUpperCase(); + return A > B ? 1 : A < B ? -1 : 0; +} diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts index 27e632052dcb65..475ee038e72c10 100644 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts @@ -31,19 +31,14 @@ describe('SpacesSavedObjectsService', () => { ); }); - it('registers the client wrapper', () => { + it('registers the spaces extension', () => { const core = coreMock.createSetup(); const spacesService = spacesServiceMock.createStartContract(); const service = new SpacesSavedObjectsService(); service.setup({ core, getSpacesService: () => spacesService }); - expect(core.savedObjects.addClientWrapper).toHaveBeenCalledTimes(1); - expect(core.savedObjects.addClientWrapper).toHaveBeenCalledWith( - Number.MIN_SAFE_INTEGER, - 'spaces', - expect.any(Function) - ); + expect(core.savedObjects.addSpacesExtension).toHaveBeenCalledTimes(1); }); }); }); diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts index 9a90a26a647d3f..236dff48888b4e 100644 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts @@ -11,7 +11,7 @@ import type { SpacesServiceStart } from '../spaces_service'; import { SPACES_USAGE_STATS_TYPE } from '../usage_stats'; import { SpacesSavedObjectMappings, UsageStatsMappings } from './mappings'; import { spaceMigrations, usageStatsMigrations } from './migrations'; -import { spacesSavedObjectsClientWrapperFactory } from './saved_objects_client_wrapper_factory'; +import { SavedObjectsSpacesExtension } from './saved_objects_spaces_extension'; interface SetupDeps { core: Pick; @@ -40,10 +40,11 @@ export class SpacesSavedObjectsService { }, }); - core.savedObjects.addClientWrapper( - Number.MIN_SAFE_INTEGER, - 'spaces', - spacesSavedObjectsClientWrapperFactory(getSpacesService) - ); + core.savedObjects.addSpacesExtension(({ request }) => { + const spacesService = getSpacesService(); + const spacesClient = spacesService.createSpacesClient(request); + const activeSpaceId = spacesService.getSpaceId(request); + return new SavedObjectsSpacesExtension({ spacesClient, activeSpaceId }); + }); } } diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.mocks.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.mocks.ts new file mode 100644 index 00000000000000..048b76b406bcf8 --- /dev/null +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.mocks.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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { spaceIdToNamespace } from '../lib/utils/namespace'; + +export const mockSpaceIdToNamespace = jest.fn() as jest.MockedFunction; + +jest.mock('../lib/utils/namespace', () => { + return { + spaceIdToNamespace: mockSpaceIdToNamespace, + }; +}); diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts new file mode 100644 index 00000000000000..4dc6ae8cd24971 --- /dev/null +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.test.ts @@ -0,0 +1,143 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { mockSpaceIdToNamespace } from './saved_objects_spaces_extension.test.mocks'; + +import Boom from '@hapi/boom'; + +import { spacesClientMock } from '../mocks'; +import { SavedObjectsSpacesExtension } from './saved_objects_spaces_extension'; + +const ACTIVE_SPACE_ID = 'active-spaceId'; +function setup() { + const spacesClient = spacesClientMock.create(); + const spacesExtension = new SavedObjectsSpacesExtension({ + activeSpaceId: ACTIVE_SPACE_ID, + spacesClient, + }); + return { spacesClient, spacesExtension }; +} + +beforeAll(() => { + mockSpaceIdToNamespace.mockImplementation((spaceId) => `namespace-for-${spaceId}`); +}); + +describe('#getCurrentNamespace', () => { + test('throws an error when the namespace parameter is truthy', () => { + const { spacesExtension } = setup(); + expect(() => spacesExtension.getCurrentNamespace('some-namespace')).toThrowError( + 'Namespace cannot be specified by the caller when the spaces extension is enabled. Spaces currently determines the namespace.' + ); + }); + + test('returns namespace for the active space ID when the namespace parameter is falsy', () => { + const { spacesExtension } = setup(); + const result = spacesExtension.getCurrentNamespace(undefined); + expect(result).toEqual('namespace-for-active-spaceId'); + }); +}); + +describe('#getSearchableNamespaces', () => { + test(`returns empty result if user is unauthorized in this space`, async () => { + const { spacesClient, spacesExtension } = setup(); + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + { id: 'ns-3', name: '', disabledFeatures: [] }, + { id: 'ns-4', name: '', disabledFeatures: [] }, + ]) + ); + await expect(spacesExtension.getSearchableNamespaces(['some-namespace'])).resolves.toEqual([]); + }); + + test(`throws an error if user is unauthorized in any space`, async () => { + const { spacesClient, spacesExtension } = setup(); + spacesClient.getAll.mockRejectedValue(Boom.forbidden()); + await expect(spacesExtension.getSearchableNamespaces(['some-namespace'])).rejects.toThrow( + 'Forbidden' + ); + }); + + test(`returns the active namespace if the namespaces argument is undefined`, async () => { + const { spacesClient, spacesExtension } = setup(); + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + { id: 'ns-3', name: '', disabledFeatures: [] }, + { id: 'ns-4', name: '', disabledFeatures: [] }, + ]) + ); + await expect(spacesExtension.getSearchableNamespaces(undefined)).resolves.toEqual([ + ACTIVE_SPACE_ID, + ]); + }); + + test(`returns an empty array if the namespaces argument is an empty array`, async () => { + const { spacesClient, spacesExtension } = setup(); + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + { id: 'ns-3', name: '', disabledFeatures: [] }, + { id: 'ns-4', name: '', disabledFeatures: [] }, + ]) + ); + await expect(spacesExtension.getSearchableNamespaces([])).resolves.toEqual([]); + }); + + test(`filters results based on requested namespaces`, async () => { + const { spacesClient, spacesExtension } = setup(); + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + { id: 'ns-3', name: '', disabledFeatures: [] }, + { id: 'ns-4', name: '', disabledFeatures: [] }, + ]) + ); + + await expect(spacesExtension.getSearchableNamespaces(['ns-1', 'ns-3'])).resolves.toEqual([ + 'ns-1', + 'ns-3', + ]); + }); + + test(`filters options.namespaces based on authorization`, async () => { + const { spacesClient, spacesExtension } = setup(); + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + ]) + ); + + await expect(spacesExtension.getSearchableNamespaces(['ns-1', 'ns-3'])).resolves.toEqual([ + 'ns-1', + ]); + }); + + test(`handles namespaces argument ['*']`, async () => { + const { spacesClient, spacesExtension } = setup(); + spacesClient.getAll.mockImplementation(() => + Promise.resolve([ + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + { id: 'ns-3', name: '', disabledFeatures: [] }, + { id: 'ns-4', name: '', disabledFeatures: [] }, + ]) + ); + + await expect(spacesExtension.getSearchableNamespaces(['*'])).resolves.toEqual([ + 'ns-1', + 'ns-2', + 'ns-3', + 'ns-4', + ]); + }); +}); diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts new file mode 100644 index 00000000000000..4520ff5ae33534 --- /dev/null +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_spaces_extension.ts @@ -0,0 +1,55 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { ISavedObjectsSpacesExtension } from '@kbn/core-saved-objects-server'; + +import { ALL_SPACES_ID } from '../../common/constants'; +import { spaceIdToNamespace } from '../lib/utils/namespace'; +import type { ISpacesClient } from '../spaces_client'; + +interface Params { + activeSpaceId: string; + spacesClient: ISpacesClient; +} + +export class SavedObjectsSpacesExtension implements ISavedObjectsSpacesExtension { + private readonly activeSpaceId: string; + private readonly spacesClient: ISpacesClient; + + constructor({ activeSpaceId, spacesClient }: Params) { + this.activeSpaceId = activeSpaceId; + this.spacesClient = spacesClient; + } + + getCurrentNamespace(namespace: string | undefined): string | undefined { + if (namespace) { + throw new Error( + 'Namespace cannot be specified by the caller when the spaces extension is enabled. Spaces currently determines the namespace.' + ); + } + return spaceIdToNamespace(this.activeSpaceId); + } + + async getSearchableNamespaces(namespaces: string[] | undefined): Promise { + if (!namespaces) { + // If no namespaces option was specified, fall back to the active space. + return [this.activeSpaceId]; + } else if (!namespaces.length) { + // If the namespaces option is empty, return early and let the consumer handle it appropriately. + return namespaces; + } + + const availableSpaces = await this.spacesClient.getAll({ purpose: 'findSavedObjects' }); + if (namespaces.includes(ALL_SPACES_ID)) { + return availableSpaces.map((space) => space.id); + } else { + return namespaces.filter((namespace) => + availableSpaces.some((space) => space.id === namespace) + ); + } + } +} From 2f462220f2971ab85c7a89f636f79062e5c32d88 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Tue, 11 Oct 2022 17:09:25 -0400 Subject: [PATCH 03/47] Adds ROC extension implementations, complete removal of SOC wrapper references. Functional unit tests passing. --- ...collect_multi_namespace_references.test.ts | 229 +- .../lib/collect_multi_namespace_references.ts | 220 +- .../src/lib/internal_bulk_resolve.test.ts | 261 +- .../src/lib/internal_bulk_resolve.ts | 244 +- .../src/lib/point_in_time_finder.test.ts | 266 +- .../src/lib/point_in_time_finder.ts | 42 +- .../src/lib/repository.common.test.ts | 856 ++++++ .../repository.encryption_extension.test.ts | 687 +++++ .../lib/repository.security_extension.test.ts | 1826 ++++++++++++ .../lib/repository.spaces_extension.test.ts | 852 ++++++ .../src/lib/repository.test.mock.ts | 1 + .../src/lib/repository.test.ts | 2532 +++++++---------- .../src/lib/repository.ts | 1402 ++++++--- .../lib/repository_create_repository.test.ts | 2 + .../src/lib/update_objects_spaces.test.ts | 612 ++-- .../src/lib/update_objects_spaces.ts | 139 +- .../core-saved-objects-api-server/index.ts | 5 +- .../src/apis/create_point_in_time_finder.ts | 4 +- .../src/saved_objects_repository.ts | 22 +- .../index.ts | 2 +- .../src/index.ts | 1 + x-pack/plugins/actions/server/plugin.ts | 4 +- .../invalidate_pending_api_keys/task.ts | 2 +- .../server/rules_client_factory.test.ts | 4 +- .../alerting/server/rules_client_factory.ts | 2 +- x-pack/plugins/cases/server/client/factory.ts | 2 +- .../encryption_key_rotation_service.test.ts | 4 +- .../crypto/encryption_key_rotation_service.ts | 2 +- ...ypted_saved_objects_client_wrapper.test.ts | 2200 -------------- .../encrypted_saved_objects_client_wrapper.ts | 399 --- ...pgrade_agent_policy_schema_version.test.ts | 2 +- .../upgrade_package_install_version.test.ts | 2 +- x-pack/plugins/fleet/server/plugin.ts | 2 +- .../fleet/server/services/app_context.ts | 2 +- .../security/server/audit/audit_events.ts | 33 +- x-pack/plugins/security/server/plugin.ts | 1 - ...saved_objects_client_wrapper.test.mocks.ts | 17 - ...ecure_saved_objects_client_wrapper.test.ts | 1984 ------------- .../secure_saved_objects_client_wrapper.ts | 1201 -------- .../endpoint/lib/policy/license_watch.ts | 2 +- .../create_internal_readonly_so_client.ts | 2 +- .../lib/saved_objects_client_opts.ts | 2 +- .../routes/api/external/copy_to_space.test.ts | 4 +- .../saved_objects_client_wrapper_factory.ts | 26 - .../spaces_saved_objects_client.test.ts | 865 ------ .../spaces_saved_objects_client.ts | 397 --- .../fixtures/plugins/alerts/server/routes.ts | 2 +- 47 files changed, 7911 insertions(+), 9457 deletions(-) create mode 100644 packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts create mode 100644 packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts create mode 100644 packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts create mode 100644 packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts delete mode 100644 x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts delete mode 100644 x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts delete mode 100644 x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.mocks.ts delete mode 100644 x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts delete mode 100644 x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts delete mode 100644 x-pack/plugins/spaces/server/saved_objects/saved_objects_client_wrapper_factory.ts delete mode 100644 x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts delete mode 100644 x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 86e1e82d12a6fb..5c1b7849b019d4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -26,6 +26,19 @@ import { } from './collect_multi_namespace_references'; import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; +import { extensionsMock } from './extensions.test.mock'; +import { + authMap, + enforceError, + mapsAreEqual, + setsAreEqual, + setupCheckAuthorized, + setupCheckUnauthorized, + setupEnforceFailure, + setupEnforceSuccess, + setupRedactPassthrough, +} from './repository.common.test'; const SPACES = ['default', 'another-space']; const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 }; @@ -50,7 +63,8 @@ describe('collectMultiNamespaceReferences', () => { /** Sets up the type registry, saved objects client, etc. and return the full parameters object to be passed to `collectMultiNamespaceReferences` */ function setup( objects: SavedObjectsCollectMultiNamespaceReferencesObject[], - options: SavedObjectsCollectMultiNamespaceReferencesOptions = {} + options: SavedObjectsCollectMultiNamespaceReferencesOptions = {}, + securityExtension?: ISavedObjectsSecurityExtension | undefined ): CollectMultiNamespaceReferencesParams { const registry = typeRegistryMock.create(); registry.isMultiNamespace.mockImplementation( @@ -65,8 +79,8 @@ describe('collectMultiNamespaceReferences', () => { (type) => [MULTI_NAMESPACE_OBJ_TYPE_1, MULTI_NAMESPACE_HIDDEN_OBJ_TYPE].includes(type) // MULTI_NAMESPACE_OBJ_TYPE_2 and NON_MULTI_NAMESPACE_TYPE are omitted ); client = elasticsearchClientMock.createElasticsearchClient(); - const serializer = new SavedObjectsSerializer(registry); + return { registry, allowedTypes: [ @@ -78,6 +92,7 @@ describe('collectMultiNamespaceReferences', () => { serializer, getIndexForType: (type: string) => `index-for-${type}`, createPointInTimeFinder: jest.fn() as CreatePointInTimeFinderFn, + securityExtension, objects, options, }; @@ -287,6 +302,7 @@ describe('collectMultiNamespaceReferences', () => { // obj3 is excluded from the results ]); }); + it(`handles 404 responses that don't come from Elasticsearch`, async () => { const createEsUnavailableNotFoundError = () => { return SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(); @@ -447,4 +463,213 @@ describe('collectMultiNamespaceReferences', () => { ); }); }); + + describe('with security enabled', () => { + const mockSecurityExt = extensionsMock.createSecurityExtension(); + const obj1 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-1' }; + const obj2 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-2' }; + const obj3 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-3' }; + const objects = [obj1, obj2]; + const obj1LegacySpaces = ['space-1', 'space-2', 'space-3', 'space-4']; + let params: CollectMultiNamespaceReferencesParams; + + beforeEach(() => { + params = setup([obj1, obj2], {}, mockSecurityExt); + mockMgetResults({ found: true, references: [obj3] }, { found: true, references: [] }); // results for obj1 and obj2 + mockMgetResults({ found: true, references: [] }); // results for obj3 + mockFindLegacyUrlAliases.mockResolvedValue( + new Map([ + [`${obj1.type}:${obj1.id}`, new Set(obj1LegacySpaces)], + // the result map does not contain keys for obj2 or obj3 because we did not find any aliases for those objects + ]) + ); + }); + + afterEach(() => { + mockSecurityExt.checkAuthorization.mockReset(); + mockSecurityExt.enforceAuthorization.mockReset(); + mockSecurityExt.redactNamespaces.mockReset(); + mockSecurityExt.addAuditEvent.mockReset(); + }); + + describe(`errors`, () => { + test(`propogates decorated error when not authorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + // Unlike other functions, it doesn't validate the level of authorization first, so we need to + // carry on and mock the enforce function as well to create an unauthorized condition + setupEnforceFailure(mockSecurityExt); + + await expect(collectMultiNamespaceReferences(params)).rejects.toThrow(enforceError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`adds audit event per object when not successful`, async () => { + setupCheckUnauthorized(mockSecurityExt); + // Unlike other functions, it doesn't validate the level of authorization first, so we need to + // carry on and mock the enforce function as well to create an unauthorized condition + setupEnforceFailure(mockSecurityExt); + + await expect(collectMultiNamespaceReferences(params)).rejects.toThrow(enforceError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.COLLECT_MULTINAMESPACE_REFERENCES, + savedObject: { type: obj.type, id: obj.id }, + error: enforceError, + }); + }); + }); + }); + + describe('checks privileges', () => { + beforeEach(() => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + }); + test(`in the default state`, async () => { + await expect(collectMultiNamespaceReferences(params)).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedSpaces = new Set(['default', ...SPACES, ...obj1LegacySpaces]); + const { spaces: actualSpaces } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + const expectedTypesAndSpaces = new Map([[objects[0].type, new Set(['default'])]]); + const { typesAndSpaces: actualTypesAndSpaces } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + }); + + test(`in a non-default state`, async () => { + const namespace = 'space-X'; + await expect( + collectMultiNamespaceReferences({ ...params, options: { namespace } }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedSpaces = new Set([namespace, ...SPACES, ...obj1LegacySpaces]); + const { spaces: actualSpaces } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + const expectedTypesAndSpaces = new Map([[objects[0].type, new Set([namespace])]]); + const { typesAndSpaces: actualTypesAndSpaces } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + }); + + test(`with purpose 'collectMultiNamespaceReferences'`, async () => { + const options: SavedObjectsCollectMultiNamespaceReferencesOptions = { + purpose: 'collectMultiNamespaceReferences', + }; + + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(collectMultiNamespaceReferences({ ...params, options })).rejects.toThrow( + enforceError + ); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.checkAuthorization).toBeCalledWith( + expect.objectContaining({ + actions: ['bulk_get'], + }) + ); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`with purpose 'updateObjectsSpaces'`, async () => { + const options: SavedObjectsCollectMultiNamespaceReferencesOptions = { + purpose: 'updateObjectsSpaces', + }; + + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(collectMultiNamespaceReferences({ ...params, options })).rejects.toThrow( + enforceError + ); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.checkAuthorization).toBeCalledWith( + expect.objectContaining({ + actions: ['share_to_space'], + }) + ); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + }); + + describe('success', () => { + beforeEach(async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + await collectMultiNamespaceReferences(params); + }); + test(`calls redactNamespaces with type, spaces, and authorization map`, async () => { + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedSpaces = new Set(['default', ...SPACES, ...obj1LegacySpaces]); + const { spaces: actualSpaces } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + + const resultObjects = [obj1, obj2, obj3]; + + // enforce is called once for all objects/spaces, then once per object + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes( + 1 + resultObjects.length + ); + const expectedTypesAndSpaces = new Map([[objects[0].type, new Set(['default'])]]); + const { typesAndSpaces: actualTypesAndSpaces } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + + // Redact is called once per object, but an additional time for object 1 because it has legacy URL aliases in another set of spaces + expect(mockSecurityExt.redactNamespaces).toBeCalledTimes(resultObjects.length + 1); + const expectedRedactParams = [ + { type: obj1.type, spaces: SPACES }, + { type: obj1.type, spaces: obj1LegacySpaces }, + { type: obj2.type, spaces: SPACES }, + { type: obj3.type, spaces: SPACES }, + ]; + + expectedRedactParams.forEach((expected, i) => { + const { savedObject, typeMap } = mockSecurityExt.redactNamespaces.mock.calls[i][0]; + expect(savedObject).toEqual( + expect.objectContaining({ + type: expected.type, + namespaces: expected.spaces, + }) + ); + expect(typeMap).toBe(authMap); + }); + }); + + test(`adds audit event per object when successful`, async () => { + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + + const resultObjects = [obj1, obj2, obj3]; + + // enforce is called once for all objects/spaces, then once per object + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes( + 1 + resultObjects.length + ); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(resultObjects.length); + resultObjects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.COLLECT_MULTINAMESPACE_REFERENCES, + savedObject: { type: obj.type, id: obj.id }, + error: undefined, + }); + }); + }); + }); + }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts index 16343ee38c4fd4..0db38392efd55f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts @@ -14,8 +14,12 @@ import type { SavedObjectsCollectMultiNamespaceReferencesResponse, SavedObjectReferenceWithContext, } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; +import { + AuditAction, + ISavedObjectsSecurityExtension, + ISavedObjectTypeRegistry, +} from '@kbn/core-saved-objects-server'; +import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '@kbn/core-saved-objects-utils-server'; import { type SavedObjectsSerializer, getObjectKey, @@ -54,6 +58,7 @@ export interface CollectMultiNamespaceReferencesParams { serializer: SavedObjectsSerializer; getIndexForType: (type: string) => string; createPointInTimeFinder: CreatePointInTimeFinderFn; + securityExtension: ISavedObjectsSecurityExtension | undefined; objects: SavedObjectsCollectMultiNamespaceReferencesObject[]; options?: SavedObjectsCollectMultiNamespaceReferencesOptions; } @@ -94,17 +99,17 @@ export async function collectMultiNamespaceReferences( }; }); - const objectsToFindAliasesFor = objectsWithContext - .filter(({ spaces }) => spaces.length !== 0) - .map(({ type, id }) => ({ type, id })); + const foundObjects = objectsWithContext.filter(({ spaces }) => spaces.length !== 0); // Any objects that have a non-empty `spaces` field are "found" + const objectsToFindAliasesFor = foundObjects.map(({ type, id }) => ({ type, id })); const aliasesMap = await findLegacyUrlAliases( createPointInTimeFinder, objectsToFindAliasesFor, ALIAS_OR_SHARED_ORIGIN_SEARCH_PER_PAGE ); - const objectOriginsToSearchFor = objectsWithContext - .filter(({ spaces }) => spaces.length !== 0) - .map(({ type, id, originId }) => ({ type, origin: originId || id })); + const objectOriginsToSearchFor = foundObjects.map(({ type, id, originId }) => ({ + type, + origin: originId || id, + })); const originsMap = await findSharedOriginObjects( createPointInTimeFinder, objectOriginsToSearchFor, @@ -118,8 +123,12 @@ export async function collectMultiNamespaceReferences( return { ...obj, spacesWithMatchingAliases, spacesWithMatchingOrigins }; }); + // Now that we have *all* information for the object graph, if the Security extension is enabled, we can: check/enforce authorization, + // write audit events, filter the object graph, and redact spaces from the objects. + const filteredAndRedactedResults = await optionallyUseSecurity(results, params); + return { - objects: results, + objects: filteredAndRedactedResults, }; } @@ -211,3 +220,196 @@ async function getObjectsAndReferences({ return { objectMap, inboundReferencesMap }; } + +/** + * Checks/enforces authorization, writes audit events, filters the object graph, and redacts spaces from the share_to_space/bulk_get + * response. In other SavedObjectsRepository functions we do this before decrypting attributes. However, because of the + * share_to_space/bulk_get response logic involved in deciding between the exact match or alias match, it's cleaner to do authorization, + * auditing, filtering, and redaction all afterwards. + */ +async function optionallyUseSecurity( + objectsWithContext: SavedObjectReferenceWithContext[], + params: CollectMultiNamespaceReferencesParams +) { + const { securityExtension, objects, options = {} } = params; + const { purpose, namespace } = options; + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + if (!securityExtension) { + return objectsWithContext; + } + + // Check authorization based on all *found* object types / spaces + const typesToAuthorize = new Set(); + const spacesToAuthorize = new Set([namespaceString]); + const addSpacesToAuthorize = (spaces: string[] = []) => { + for (const space of spaces) spacesToAuthorize.add(space); + }; + for (const obj of objectsWithContext) { + typesToAuthorize.add(obj.type); + addSpacesToAuthorize(obj.spaces); + addSpacesToAuthorize(obj.spacesWithMatchingAliases); + addSpacesToAuthorize(obj.spacesWithMatchingOrigins); + } + const action = + purpose === 'updateObjectsSpaces' ? ('share_to_space' as const) : ('bulk_get' as const); + const { typeMap } = await securityExtension.checkAuthorization({ + types: typesToAuthorize, + spaces: spacesToAuthorize, + actions: [action], + }); + + // Enforce authorization based on all *requested* object types and the current space + const typesAndSpaces = objects.reduce( + (acc, { type }) => (acc.has(type) ? acc : acc.set(type, new Set([namespaceString]))), // Always enforce authZ for the active space + new Map>() + ); + securityExtension!.enforceAuthorization({ + typesAndSpaces, + action, + typeMap, + auditCallback: (error) => { + if (!error) return; // We will audit success results below, after redaction + for (const { type, id } of objects) { + securityExtension!.addAuditEvent({ + action: AuditAction.COLLECT_MULTINAMESPACE_REFERENCES, + savedObject: { type, id }, + error, + }); + } + }, + }); + + // Now, filter/redact the results. Most SOR functions just redact the `namespaces` field from each returned object. However, this function + // will actually filter the returned object graph itself. + // This is done in two steps: (1) objects which the user can't access *in this space* are filtered from the graph, and the + // graph is rearranged to avoid leaking information. (2) any spaces that the user can't access are redacted from each individual object. + // After we finish filtering, we can write audit events for each object that is going to be returned to the user. + const requestedObjectsSet = objects.reduce( + (acc, { type, id }) => acc.add(`${type}:${id}`), + new Set() + ); + const retrievedObjectsSet = objectsWithContext.reduce( + (acc, { type, id }) => acc.add(`${type}:${id}`), + new Set() + ); + const traversedObjects = new Set(); + const filteredObjectsMap = new Map(); + const getIsAuthorizedForInboundReference = (inbound: { type: string; id: string }) => { + const found = filteredObjectsMap.get(`${inbound.type}:${inbound.id}`); + return found && !found.isMissing; // If true, this object can be linked back to one of the requested objects + }; + let objectsToProcess = [...objectsWithContext]; + while (objectsToProcess.length > 0) { + const obj = objectsToProcess.shift()!; + const { type, id, spaces, inboundReferences } = obj; + const objKey = `${type}:${id}`; + traversedObjects.add(objKey); + // Is the user authorized to access this object in this space? + let isAuthorizedForObject = true; + try { + securityExtension.enforceAuthorization({ + typesAndSpaces: new Map([[type, new Set([namespaceString])]]), + action, + typeMap, + }); + } catch (err) { + isAuthorizedForObject = false; + } + // Redact the inbound references so we don't leak any info about other objects that the user is not authorized to access + const redactedInboundReferences = inboundReferences.filter((inbound) => { + if (inbound.type === type && inbound.id === id) { + // circular reference, don't redact it + return true; + } + return getIsAuthorizedForInboundReference(inbound); + }); + // If the user is not authorized to access at least one inbound reference of this object, then we should omit this object. + const isAuthorizedForGraph = + requestedObjectsSet.has(objKey) || // If true, this is one of the requested objects, and we checked authorization above + redactedInboundReferences.some(getIsAuthorizedForInboundReference); + + if (isAuthorizedForObject && isAuthorizedForGraph) { + if (spaces.length) { + // Only generate success audit records for "non-empty results" with 1+ spaces + // ("empty result" means the object was a non-multi-namespace type, or hidden type, or not found) + securityExtension.addAuditEvent({ + action: AuditAction.COLLECT_MULTINAMESPACE_REFERENCES, + savedObject: { type, id }, + }); + } + filteredObjectsMap.set(objKey, obj); + } else if (!isAuthorizedForObject && isAuthorizedForGraph) { + filteredObjectsMap.set(objKey, { ...obj, spaces: [], isMissing: true }); + } else if (isAuthorizedForObject && !isAuthorizedForGraph) { + const hasUntraversedInboundReferences = inboundReferences.some( + (ref) => + !traversedObjects.has(`${ref.type}:${ref.id}`) && + retrievedObjectsSet.has(`${ref.type}:${ref.id}`) + ); + + if (hasUntraversedInboundReferences) { + // this object has inbound reference(s) that we haven't traversed yet; bump it to the back of the list + objectsToProcess = [...objectsToProcess, obj]; + } else { + // There should never be a missing inbound reference. + // If there is, then something has gone terribly wrong. + const missingInboundReference = inboundReferences.find( + (ref) => + !traversedObjects.has(`${ref.type}:${ref.id}`) && + !retrievedObjectsSet.has(`${ref.type}:${ref.id}`) + ); + + if (missingInboundReference) { + throw new Error( + `Unexpected inbound reference to "${missingInboundReference.type}:${missingInboundReference.id}"` + ); + } + } + } + } + + const filteredAndRedactedObjects = [ + ...filteredObjectsMap.values(), + ].map((obj) => { + const { + type, + id, + spaces, + spacesWithMatchingAliases, + spacesWithMatchingOrigins, + inboundReferences, + } = obj; + // Redact the inbound references so we don't leak any info about other objects that the user is not authorized to access + const redactedInboundReferences = inboundReferences.filter((inbound) => { + if (inbound.type === type && inbound.id === id) { + // circular reference, don't redact it + return true; + } + return getIsAuthorizedForInboundReference(inbound); + }); + + /** Simple wrapper for the `redactNamespaces` function that expects a saved object in its params. */ + const getRedactedSpaces = (spacesArray: string[] | undefined) => { + if (!spacesArray) return; + const savedObject = { type, namespaces: spacesArray } as SavedObject; // Other SavedObject attributes aren't required + const result = securityExtension.redactNamespaces({ savedObject, typeMap }); + return result.namespaces; + }; + const redactedSpaces = getRedactedSpaces(spaces)!; + const redactedSpacesWithMatchingAliases = getRedactedSpaces(spacesWithMatchingAliases); + const redactedSpacesWithMatchingOrigins = getRedactedSpaces(spacesWithMatchingOrigins); + return { + ...obj, + spaces: redactedSpaces, + ...(redactedSpacesWithMatchingAliases && { + spacesWithMatchingAliases: redactedSpacesWithMatchingAliases, + }), + ...(redactedSpacesWithMatchingOrigins && { + spacesWithMatchingOrigins: redactedSpacesWithMatchingOrigins, + }), + inboundReferences: redactedInboundReferences, + }; + }); + + return filteredAndRedactedObjects; +} diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index 119b3ac85b6986..b22cd68aac70cd 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -26,10 +26,29 @@ import { import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { internalBulkResolve, InternalBulkResolveParams } from './internal_bulk_resolve'; import { normalizeNamespace } from './internal_utils'; +import { + AuditAction, + ISavedObjectsEncryptionExtension, + ISavedObjectsSecurityExtension, + ISavedObjectTypeRegistry, +} from '@kbn/core-saved-objects-server'; +import { extensionsMock } from './extensions.test.mock'; +import { + authMap, + enforceError, + mapsAreEqual, + setsAreEqual, + setupCheckAuthorized, + setupCheckUnauthorized, + setupEnforceFailure, + setupEnforceSuccess, + setupRedactPassthrough, +} from './repository.common.test'; const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 }; const OBJ_TYPE = 'obj-type'; const UNSUPPORTED_TYPE = 'unsupported-type'; +const ENCRYPTED_TYPE = 'encrypted-type'; beforeEach(() => { mockGetSavedObjectFromSource.mockReset(); @@ -46,23 +65,30 @@ describe('internalBulkResolve', () => { let client: ReturnType; let serializer: SavedObjectsSerializer; let incrementCounterInternal: jest.Mock; + let registry: jest.Mocked; /** Sets up the type registry, saved objects client, etc. and return the full parameters object to be passed to `internalBulkResolve` */ function setup( objects: SavedObjectsBulkResolveObject[], - options: SavedObjectsBaseOptions = {} + options: SavedObjectsBaseOptions = {}, + extensions?: { + encryptionExt?: ISavedObjectsEncryptionExtension; + securityExt?: ISavedObjectsSecurityExtension; + } ): InternalBulkResolveParams { - const registry = typeRegistryMock.create(); + registry = typeRegistryMock.create(); client = elasticsearchClientMock.createElasticsearchClient(); serializer = new SavedObjectsSerializer(registry); incrementCounterInternal = jest.fn().mockRejectedValue(new Error('increment error')); // mock error to implicitly test that it is caught and swallowed return { - registry: typeRegistryMock.create(), // doesn't need additional mocks for this test suite - allowedTypes: [OBJ_TYPE], + registry, + allowedTypes: [OBJ_TYPE, ENCRYPTED_TYPE], client, serializer, getIndexForType: (type: string) => `index-for-${type}`, incrementCounterInternal, + encryptionExtension: extensions?.encryptionExt, + securityExtension: extensions?.securityExt, objects, options, }; @@ -343,4 +369,231 @@ describe('internalBulkResolve', () => { ]); }); } + + describe('with encryption extension', () => { + const namespace = 'foo'; + + const attributes = { + attrNotSoSecret: '*not-so-secret*', + attrOne: 'one', + attrSecret: '*secret*', + attrThree: 'three', + title: 'Testing', + }; + + beforeEach(() => { + mockGetSavedObjectFromSource.mockImplementation((_registry, type, id) => { + return { + id, + type, + namespaces: [namespace], + attributes, + references: [], + } as SavedObject; + }); + }); + + it('only attempts to decrypt and strip attributes for types that are encryptable', async () => { + const objects = [ + { type: OBJ_TYPE, id: '11' }, // non encryptable type + { type: ENCRYPTED_TYPE, id: '12' }, // encryptable type + ]; + const mockEncryptionExt = extensionsMock.createEncryptionExtension(); + const params = setup(objects, { namespace }, { encryptionExt: mockEncryptionExt }); + mockBulkResults( + // No alias matches + { found: false }, + { found: false } + ); + mockMgetResults( + // exact matches + { found: true }, + { found: true } + ); + + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(false); + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + + await internalBulkResolve(params); + + expect(mockEncryptionExt.isEncryptableType).toBeCalledTimes(2); + expect(mockEncryptionExt.isEncryptableType).toBeCalledWith(OBJ_TYPE); + expect(mockEncryptionExt.isEncryptableType).toBeCalledWith(ENCRYPTED_TYPE); + + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toBeCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toBeCalledWith( + expect.objectContaining({ type: ENCRYPTED_TYPE, id: '12', attributes }) + ); + }); + }); + + describe('with security extension', () => { + const namespace = 'foo'; + const objects = [ + { type: OBJ_TYPE, id: '13' }, + { type: OBJ_TYPE, id: '14' }, + ]; + let mockSecurityExt: jest.Mocked; + let params: InternalBulkResolveParams; + + beforeEach(() => { + mockGetSavedObjectFromSource.mockReset(); + mockGetSavedObjectFromSource.mockImplementation((_registry, type, id) => { + return { + id, + type, + namespaces: [namespace], + attributes: {}, + references: [], + } as SavedObject; + }); + + mockSecurityExt = extensionsMock.createSecurityExtension(); + params = setup(objects, { namespace }, { securityExt: mockSecurityExt }); + + mockBulkResults( + // No alias matches + { found: false }, + { found: false } + ); + mockMgetResults( + // exact matches + { found: true }, + { found: true } + ); + }); + + test(`propagates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(internalBulkResolve(params)).rejects.toThrow(enforceError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await internalBulkResolve(params); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + const bulkIds = objects.map((obj) => obj.id); + const expectedNamespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + expectBulkArgs(expectedNamespaceString, bulkIds); + const mgetIds = bulkIds; + expectMgetArgs(namespace, mgetIds); + expect(result.resolved_objects).toEqual([ + expect.objectContaining({ + outcome: 'exactMatch', + saved_object: expect.objectContaining({ id: objects[0].id }), + }), + expect.objectContaining({ + outcome: 'exactMatch', + saved_object: expect.objectContaining({ id: objects[1].id }), + }), + ]); + }); + + test(`calls checkAuthorization with type, actions, namespace, and object namespaces`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + await internalBulkResolve(params); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['bulk_get']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([objects[0].type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + await internalBulkResolve(params); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'bulk_get', + }) + ); + + const expectedTypesAndSpaces = new Map([[objects[0].type, new Set([namespace])]]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + await internalBulkResolve(params); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj, i) => { + const { savedObject, typeMap } = mockSecurityExt.redactNamespaces.mock.calls[i][0]; + expect(savedObject).toEqual( + expect.objectContaining({ + type: obj.type, + id: obj.id, + namespaces: [namespace], + }) + ); + expect(typeMap).toBe(authMap); + }); + }); + + test(`adds audit event per object when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + await internalBulkResolve(params); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.RESOLVE, + savedObject: { type: obj.type, id: obj.id }, + error: undefined, + }); + }); + }); + + test(`adds audit event per object when not successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(internalBulkResolve(params)).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.RESOLVE, + savedObject: { type: obj.type, id: obj.id }, + error: enforceError, + }); + }); + }); + }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts index 28e22d6e2634cb..6e13671661b95d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts @@ -17,12 +17,16 @@ import type { SavedObjectsIncrementCounterField, SavedObjectsIncrementCounterOptions, } from '@kbn/core-saved-objects-api-server'; -import type { +import { + AuditAction, + ISavedObjectsEncryptionExtension, + ISavedObjectsSecurityExtension, ISavedObjectTypeRegistry, SavedObjectsRawDocSource, } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers, + SavedObjectsUtils, type DecoratedError, } from '@kbn/core-saved-objects-utils-server'; import { @@ -35,6 +39,7 @@ import { CORE_USAGE_STATS_TYPE, REPOSITORY_RESOLVE_OUTCOME_STATS, } from '@kbn/core-usage-data-base-server-internal'; +import pMap from 'p-map'; import { getCurrentTime, getSavedObjectFromSource, @@ -47,6 +52,8 @@ import { } from './internal_utils'; import type { RepositoryEsClient } from './repository_es_client'; +const MAX_CONCURRENT_RESOLVE = 10; + /** * Parameters for the internal bulkResolve function. * @@ -64,6 +71,8 @@ export interface InternalBulkResolveParams { counterFields: Array, options?: SavedObjectsIncrementCounterOptions ) => Promise>; + encryptionExtension: ISavedObjectsEncryptionExtension | undefined; + securityExtension: ISavedObjectsSecurityExtension | undefined; objects: SavedObjectsBulkResolveObject[]; options?: SavedObjectsBaseOptions; } @@ -88,6 +97,13 @@ export interface InternalBulkResolveError { error: DecoratedError; } +/** Type guard used in the repository. */ +export function isBulkResolveError( + result: SavedObjectsResolveResponse | InternalBulkResolveError +): result is InternalBulkResolveError { + return !!(result as InternalBulkResolveError).error; +} + type AliasInfo = Pick; export async function internalBulkResolve( @@ -100,6 +116,8 @@ export async function internalBulkResolve( serializer, getIndexForType, incrementCounterInternal, + encryptionExtension, + securityExtension, objects, options = {}, } = params; @@ -166,70 +184,85 @@ export async function internalBulkResolve( let getResponseIndex = 0; let aliasInfoIndex = 0; - const resolveCounter = new ResolveCounter(); - const resolvedObjects = allObjects.map | InternalBulkResolveError>( - (either) => { - if (isLeft(either)) { - return either.value; - } - const exactMatchDoc = bulkGetResponse?.body.docs[getResponseIndex++]; - let aliasMatchDoc: MgetResponseItem | undefined; - const aliasInfo = aliasInfoArray[aliasInfoIndex++]; - if (aliasInfo !== undefined) { - aliasMatchDoc = bulkGetResponse?.body.docs[getResponseIndex++]; - } - const foundExactMatch = - // @ts-expect-error MultiGetHit._source is optional - exactMatchDoc.found && rawDocExistsInNamespace(registry, exactMatchDoc, namespace); - const foundAliasMatch = - // @ts-expect-error MultiGetHit._source is optional - aliasMatchDoc?.found && rawDocExistsInNamespace(registry, aliasMatchDoc, namespace); - - const { type, id } = either.value; - let result: SavedObjectsResolveResponse | null = null; - if (foundExactMatch && foundAliasMatch) { - result = { - // @ts-expect-error MultiGetHit._source is optional - saved_object: getSavedObjectFromSource(registry, type, id, exactMatchDoc), - outcome: 'conflict', - alias_target_id: aliasInfo!.targetId, - alias_purpose: aliasInfo!.purpose, - }; - resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.CONFLICT); - } else if (foundExactMatch) { - result = { - // @ts-expect-error MultiGetHit._source is optional - saved_object: getSavedObjectFromSource(registry, type, id, exactMatchDoc), - outcome: 'exactMatch', - }; - resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.EXACT_MATCH); - } else if (foundAliasMatch) { - result = { - saved_object: getSavedObjectFromSource( - registry, - type, - aliasInfo!.targetId, - // @ts-expect-error MultiGetHit._source is optional - aliasMatchDoc! - ), - outcome: 'aliasMatch', - alias_target_id: aliasInfo!.targetId, - alias_purpose: aliasInfo!.purpose, - }; - resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.ALIAS_MATCH); - } - if (result !== null) { - return result; - } - resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.NOT_FOUND); - return { - type, - id, - error: SavedObjectsErrorHelpers.createGenericNotFoundError(type, id), + // Helper function for the map block below + async function getSavedObject( + objectType: string, + objectId: string, + doc: MgetResponseItem + ) { + // Encryption + // @ts-expect-error MultiGetHit._source is optional + const object = getSavedObjectFromSource(registry, objectType, objectId, doc); + if (!encryptionExtension?.isEncryptableType(object.type)) { + return object; + } + return encryptionExtension.decryptOrStripResponseAttributes(object); + } + + // map function for pMap below + const mapper = async ( + either: Either + ) => { + if (isLeft(either)) { + return either.value; + } + const exactMatchDoc = bulkGetResponse?.body.docs[getResponseIndex++]; + let aliasMatchDoc: MgetResponseItem | undefined; + const aliasInfo = aliasInfoArray[aliasInfoIndex++]; + if (aliasInfo !== undefined) { + aliasMatchDoc = bulkGetResponse?.body.docs[getResponseIndex++]; + } + const foundExactMatch = + // @ts-expect-error MultiGetHit._source is optional + exactMatchDoc.found && rawDocExistsInNamespace(registry, exactMatchDoc, namespace); + const foundAliasMatch = + // @ts-expect-error MultiGetHit._source is optional + aliasMatchDoc?.found && rawDocExistsInNamespace(registry, aliasMatchDoc, namespace); + + const { type, id } = either.value; + let result: SavedObjectsResolveResponse | null = null; + + if (foundExactMatch && foundAliasMatch) { + result = { + saved_object: await getSavedObject(type, id, exactMatchDoc!), + outcome: 'conflict', + alias_target_id: aliasInfo!.targetId, + alias_purpose: aliasInfo!.purpose, }; + resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.CONFLICT); + } else if (foundExactMatch) { + result = { + saved_object: await getSavedObject(type, id, exactMatchDoc!), + outcome: 'exactMatch', + }; + resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.EXACT_MATCH); + } else if (foundAliasMatch) { + result = { + saved_object: await getSavedObject(type, aliasInfo!.targetId, aliasMatchDoc!), + outcome: 'aliasMatch', + alias_target_id: aliasInfo!.targetId, + alias_purpose: aliasInfo!.purpose, + }; + resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.ALIAS_MATCH); } - ); + + if (result !== null) { + return result; + } + resolveCounter.recordOutcome(REPOSITORY_RESOLVE_OUTCOME_STATS.NOT_FOUND); + return { + type, + id, + error: SavedObjectsErrorHelpers.createGenericNotFoundError(type, id), + }; + }; + + const resolveCounter = new ResolveCounter(); + + const resolvedObjects = await pMap(allObjects, mapper, { + concurrency: MAX_CONCURRENT_RESOLVE, + }); incrementCounterInternal( CORE_USAGE_STATS_TYPE, @@ -238,7 +271,91 @@ export async function internalBulkResolve( { refresh: false } ).catch(() => {}); // if the call fails for some reason, intentionally swallow the error - return { resolved_objects: resolvedObjects }; + const redacted = await authorizeAuditAndRedact(resolvedObjects, securityExtension, namespace); + return { resolved_objects: redacted }; +} + +/** + * Checks authorization, writes audit events, and redacts namespaces from the bulkResolve response. In other SavedObjectsRepository + * functions we do this before decrypting attributes. However, because of the bulkResolve logic involved in deciding between the exact match + * or alias match, it's cleaner to do authorization, auditing, and redaction all afterwards. + */ +async function authorizeAuditAndRedact( + resolvedObjects: Array | InternalBulkResolveError>, + securityExtension: ISavedObjectsSecurityExtension | undefined, + namespace: string | undefined +) { + if (!securityExtension) { + return resolvedObjects; + } + + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + const typesAndSpaces = new Map>(); + const spacesToAuthorize = new Set(); + const auditableObjects: Array<{ type: string; id: string }> = []; + + for (const result of resolvedObjects) { + let auditableObject: { type: string; id: string } | undefined; + if (isBulkResolveError(result)) { + const { type, id, error } = result; + if (!SavedObjectsErrorHelpers.isBadRequestError(error)) { + // Only "not found" errors should show up as audit events (not "unsupported type" errors) + auditableObject = { type, id }; + } + } else { + const { type, id, namespaces = [] } = result.saved_object; + auditableObject = { type, id }; + for (const space of namespaces) { + spacesToAuthorize.add(space); + } + } + if (auditableObject) { + auditableObjects.push(auditableObject); + const spacesToEnforce = + typesAndSpaces.get(auditableObject.type) ?? new Set([namespaceString]); // Always enforce authZ for the active space + spacesToEnforce.add(namespaceString); + typesAndSpaces.set(auditableObject.type, spacesToEnforce); + spacesToAuthorize.add(namespaceString); + } + } + + if (typesAndSpaces.size === 0) { + // We only had "unsupported type" errors, there are no types to check privileges for, just return early + return resolvedObjects; + } + + const authorizationResult = await securityExtension.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: spacesToAuthorize, + actions: ['bulk_get'], + }); + securityExtension.enforceAuthorization({ + typesAndSpaces, + action: 'bulk_get', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + for (const { type, id } of auditableObjects) { + securityExtension.addAuditEvent({ + action: AuditAction.RESOLVE, + savedObject: { type, id }, + error, + }); + } + }, + }); + + return resolvedObjects.map((result) => { + if (isBulkResolveError(result)) { + return result; + } + return { + ...result, + saved_object: securityExtension.redactNamespaces({ + typeMap: authorizationResult.typeMap, + savedObject: result.saved_object, + }), + }; + }); } /** Separates valid and invalid object types */ @@ -317,7 +434,6 @@ async function fetchAndUpdateAliases( return item.update?.get; }); } - class ResolveCounter { private record = new Map(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts index 2e31de3b267ef2..a8f9722c09e7a5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts @@ -69,9 +69,11 @@ describe('createPointInTimeFinder()', () => { namespaces: ['ns1', 'ns2'], }; + const internalOptions = {}; const finder = new PointInTimeFinder(findOptions, { logger, client: repository, + internalOptions, }); expect(repository.openPointInTimeForType).not.toHaveBeenCalled(); @@ -79,150 +81,158 @@ describe('createPointInTimeFinder()', () => { await finder.find().next(); expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(repository.openPointInTimeForType).toHaveBeenCalledWith(findOptions.type, { - namespaces: findOptions.namespaces, - }); - }); - - test('throws if a PIT is already open', async () => { - repository.openPointInTimeForType.mockResolvedValueOnce({ - id: 'abc123', - }); - repository.find - .mockResolvedValueOnce({ - total: 2, - saved_objects: mockHits, - pit_id: 'abc123', - per_page: 1, - page: 0, - }) - .mockResolvedValueOnce({ - total: 2, - saved_objects: mockHits, - pit_id: 'abc123', - per_page: 1, - page: 1, - }); - - const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { - type: ['visualization'], - search: 'foo*', - perPage: 1, - }; - - const finder = new PointInTimeFinder(findOptions, { - logger, - client: repository, - }); - await finder.find().next(); - - expect(repository.find).toHaveBeenCalledTimes(1); - - expect(async () => { - await finder.find().next(); - }).rejects.toThrowErrorMatchingInlineSnapshot( - `"Point In Time has already been opened for this finder instance. Please call \`close()\` before calling \`find()\` again."` + expect(repository.openPointInTimeForType).toHaveBeenCalledWith( + findOptions.type, + { namespaces: findOptions.namespaces }, + internalOptions ); - expect(repository.find).toHaveBeenCalledTimes(1); }); + }); - test('works with a single page of results', async () => { - repository.openPointInTimeForType.mockResolvedValueOnce({ - id: 'abc123', - }); - repository.find.mockResolvedValueOnce({ + test('throws if a PIT is already open', async () => { + repository.openPointInTimeForType.mockResolvedValueOnce({ + id: 'abc123', + }); + repository.find + .mockResolvedValueOnce({ total: 2, saved_objects: mockHits, pit_id: 'abc123', - per_page: 2, + per_page: 1, page: 0, + }) + .mockResolvedValueOnce({ + total: 2, + saved_objects: mockHits, + pit_id: 'abc123', + per_page: 1, + page: 1, }); - const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { - type: ['visualization'], - search: 'foo*', - }; + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + type: ['visualization'], + search: 'foo*', + perPage: 1, + }; - const finder = new PointInTimeFinder(findOptions, { - logger, - client: repository, - }); - const hits: SavedObjectsFindResult[] = []; - for await (const result of finder.find()) { - hits.push(...result.saved_objects); - } + const finder = new PointInTimeFinder(findOptions, { + logger, + client: repository, + }); + await finder.find().next(); - expect(hits.length).toBe(2); - expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(repository.closePointInTime).toHaveBeenCalledTimes(1); - expect(repository.find).toHaveBeenCalledTimes(1); - expect(repository.find).toHaveBeenCalledWith( - expect.objectContaining({ - pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), - sortField: 'updated_at', - sortOrder: 'desc', - type: ['visualization'], - }) - ); + expect(repository.find).toHaveBeenCalledTimes(1); + + expect(async () => { + await finder.find().next(); + }).rejects.toThrowErrorMatchingInlineSnapshot( + `"Point In Time has already been opened for this finder instance. Please call \`close()\` before calling \`find()\` again."` + ); + expect(repository.find).toHaveBeenCalledTimes(1); + }); + + test('works with a single page of results', async () => { + repository.openPointInTimeForType.mockResolvedValueOnce({ + id: 'abc123', + }); + repository.find.mockResolvedValueOnce({ + total: 2, + saved_objects: mockHits, + pit_id: 'abc123', + per_page: 2, + page: 0, }); - test('works with multiple pages of results', async () => { - repository.openPointInTimeForType.mockResolvedValueOnce({ - id: 'abc123', - }); - repository.find - .mockResolvedValueOnce({ - total: 2, - saved_objects: [mockHits[0]], - pit_id: 'abc123', - per_page: 1, - page: 0, - }) - .mockResolvedValueOnce({ - total: 2, - saved_objects: [mockHits[1]], - pit_id: 'abc123', - per_page: 1, - page: 0, - }); - repository.find.mockResolvedValueOnce({ + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + type: ['visualization'], + search: 'foo*', + }; + + const internalOptions = {}; + const finder = new PointInTimeFinder(findOptions, { + logger, + client: repository, + internalOptions, + }); + const hits: SavedObjectsFindResult[] = []; + for await (const result of finder.find()) { + hits.push(...result.saved_objects); + } + + expect(hits.length).toBe(2); + expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); + expect(repository.closePointInTime).toHaveBeenCalledTimes(1); + expect(repository.find).toHaveBeenCalledTimes(1); + expect(repository.find).toHaveBeenCalledWith( + expect.objectContaining({ + pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), + sortField: 'updated_at', + sortOrder: 'desc', + type: ['visualization'], + }), + internalOptions + ); + }); + + test('works with multiple pages of results', async () => { + repository.openPointInTimeForType.mockResolvedValueOnce({ + id: 'abc123', + }); + repository.find + .mockResolvedValueOnce({ total: 2, - saved_objects: [], + saved_objects: [mockHits[0]], + pit_id: 'abc123', per_page: 1, + page: 0, + }) + .mockResolvedValueOnce({ + total: 2, + saved_objects: [mockHits[1]], pit_id: 'abc123', + per_page: 1, page: 0, }); + repository.find.mockResolvedValueOnce({ + total: 2, + saved_objects: [], + per_page: 1, + pit_id: 'abc123', + page: 0, + }); - const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { - type: ['visualization'], - search: 'foo*', - perPage: 1, - }; - - const finder = new PointInTimeFinder(findOptions, { - logger, - client: repository, - }); - const hits: SavedObjectsFindResult[] = []; - for await (const result of finder.find()) { - hits.push(...result.saved_objects); - } - - expect(hits.length).toBe(2); - expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(repository.closePointInTime).toHaveBeenCalledTimes(1); - // called 3 times since we need a 3rd request to check if we - // are done paginating through results. - expect(repository.find).toHaveBeenCalledTimes(3); - expect(repository.find).toHaveBeenCalledWith( - expect.objectContaining({ - pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), - sortField: 'updated_at', - sortOrder: 'desc', - type: ['visualization'], - }) - ); + const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { + type: ['visualization'], + search: 'foo*', + perPage: 1, + }; + + const internalOptions = {}; + const finder = new PointInTimeFinder(findOptions, { + logger, + client: repository, + internalOptions, }); + const hits: SavedObjectsFindResult[] = []; + for await (const result of finder.find()) { + hits.push(...result.saved_objects); + } + + expect(hits.length).toBe(2); + expect(repository.openPointInTimeForType).toHaveBeenCalledTimes(1); + expect(repository.closePointInTime).toHaveBeenCalledTimes(1); + // called 3 times since we need a 3rd request to check if we + // are done paginating through results. + expect(repository.find).toHaveBeenCalledTimes(3); + expect(repository.find).toHaveBeenCalledWith( + expect.objectContaining({ + pit: expect.objectContaining({ id: 'abc123', keepAlive: '2m' }), + sortField: 'updated_at', + sortOrder: 'desc', + type: ['visualization'], + }), + internalOptions + ); }); describe('#close', () => { @@ -244,9 +254,11 @@ describe('createPointInTimeFinder()', () => { perPage: 2, }; + const internalOptions = {}; const finder = new PointInTimeFinder(findOptions, { logger, client: repository, + internalOptions, }); const hits: SavedObjectsFindResult[] = []; for await (const result of finder.find()) { @@ -254,7 +266,7 @@ describe('createPointInTimeFinder()', () => { await finder.close(); } - expect(repository.closePointInTime).toHaveBeenCalledWith('test'); + expect(repository.closePointInTime).toHaveBeenCalledWith('test', undefined, internalOptions); }); test('causes generator to stop', async () => { @@ -315,9 +327,11 @@ describe('createPointInTimeFinder()', () => { perPage: 2, }; + const internalOptions = {}; const finder = new PointInTimeFinder(findOptions, { logger, client: repository, + internalOptions, }); const hits: SavedObjectsFindResult[] = []; try { @@ -328,7 +342,7 @@ describe('createPointInTimeFinder()', () => { // intentionally empty } - expect(repository.closePointInTime).toHaveBeenCalledWith('test'); + expect(repository.closePointInTime).toHaveBeenCalledWith('test', undefined, internalOptions); }); test('finder can be reused after closing', async () => { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts index 2245201a634a0d..7d5a8fea1370ff 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts @@ -14,6 +14,7 @@ import type { SavedObjectsCreatePointInTimeFinderOptions, ISavedObjectsPointInTimeFinder, SavedObjectsPointInTimeFinderClient, + SavedObjectsFindInternalOptions, } from '@kbn/core-saved-objects-api-server'; /** @@ -22,13 +23,15 @@ import type { export interface PointInTimeFinderDependencies extends SavedObjectsCreatePointInTimeFinderDependencies { logger: Logger; + internalOptions?: SavedObjectsFindInternalOptions; } /** * @internal */ export type CreatePointInTimeFinderFn = ( - findOptions: SavedObjectsCreatePointInTimeFinderOptions + findOptions: SavedObjectsCreatePointInTimeFinderOptions, + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies ) => ISavedObjectsPointInTimeFinder; /** @@ -40,15 +43,17 @@ export class PointInTimeFinder readonly #log: Logger; readonly #client: SavedObjectsPointInTimeFinderClient; readonly #findOptions: SavedObjectsFindOptions; + readonly #internalOptions: SavedObjectsFindInternalOptions | undefined; #open: boolean = false; #pitId?: string; constructor( findOptions: SavedObjectsCreatePointInTimeFinderOptions, - { logger, client }: PointInTimeFinderDependencies + { logger, client, internalOptions }: PointInTimeFinderDependencies ) { this.#log = logger.get('point-in-time-finder'); this.#client = client; + this.#internalOptions = internalOptions; this.#findOptions = { // Default to 1000 items per page as a tradeoff between // speed and memory consumption. @@ -99,7 +104,7 @@ export class PointInTimeFinder try { if (this.#pitId) { this.#log.debug(`Closing PIT for types [${this.#findOptions.type}]`); - await this.#client.closePointInTime(this.#pitId); + await this.#client.closePointInTime(this.#pitId, undefined, this.#internalOptions); this.#pitId = undefined; } this.#open = false; @@ -111,9 +116,11 @@ export class PointInTimeFinder private async open() { try { - const { id } = await this.#client.openPointInTimeForType(this.#findOptions.type, { - namespaces: this.#findOptions.namespaces, - }); + const { id } = await this.#client.openPointInTimeForType( + this.#findOptions.type, + { namespaces: this.#findOptions.namespaces }, + this.#internalOptions + ); this.#pitId = id; this.#open = true; } catch (e) { @@ -137,16 +144,19 @@ export class PointInTimeFinder searchAfter?: estypes.Id[]; }) { try { - return await this.#client.find({ - // Sort fields are required to use searchAfter, so we set some defaults here - sortField: 'updated_at', - sortOrder: 'desc', - // Bump keep_alive by 2m on every new request to allow for the ES client - // to make multiple retries in the event of a network failure. - pit: id ? { id, keepAlive: '2m' } : undefined, - searchAfter, - ...findOptions, - }); + return await this.#client.find( + { + // Sort fields are required to use searchAfter, so we set some defaults here + sortField: 'updated_at', + sortOrder: 'desc', + // Bump keep_alive by 2m on every new request to allow for the ES client + // to make multiple retries in the event of a network failure. + pit: id ? { id, keepAlive: '2m' } : undefined, + searchAfter, + ...findOptions, + }, + this.#internalOptions + ); } catch (e) { if (id) { // Clean up PIT on any errors. diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts new file mode 100644 index 00000000000000..d2f71da3763806 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts @@ -0,0 +1,856 @@ +/* + * 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 * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { schema } from '@kbn/config-schema'; +import { loggerMock } from '@kbn/logging-mocks'; +import { isEqual } from 'lodash'; +import { Payload } from 'elastic-apm-node'; +import { + AuthorizationTypeEntry, + EnforceAuthorizationParams, + ISavedObjectsSecurityExtension, + SavedObjectsMappingProperties, + SavedObjectsRawDocSource, + SavedObjectsType, + SavedObjectsTypeMappingDefinition, +} from '@kbn/core-saved-objects-server'; +import { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-common'; +import { + SavedObjectsBaseOptions, + SavedObjectsBulkCreateObject, + SavedObjectsBulkGetObject, + SavedObjectsBulkUpdateObject, + SavedObjectsBulkUpdateOptions, + SavedObjectsCreateOptions, + SavedObjectsDeleteOptions, + SavedObjectsFindOptions, + SavedObjectsUpdateOptions, +} from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; + +import { + encodeHitVersion, + SavedObjectsSerializer, + SavedObjectTypeRegistry, +} from '@kbn/core-saved-objects-base-server-internal'; +import { + elasticsearchClientMock, + ElasticsearchClientMock, +} from '@kbn/core-elasticsearch-client-server-mocks'; +import { DocumentMigrator } from '@kbn/core-saved-objects-migration-server-internal'; +import { SavedObjectsRepository } from './repository'; +import { mockGetSearchDsl } from './repository.test.mock'; + +export const DEFAULT_SPACE = 'default'; + +export interface ExpectedErrorResult { + type: string; + id: string; + error: Record; +} + +export type ErrorPayload = Error & Payload; + +export const createBadRequestErrorPayload = (reason?: string) => + SavedObjectsErrorHelpers.createBadRequestError(reason).output.payload as unknown as ErrorPayload; +export const createConflictErrorPayload = (type: string, id: string, reason?: string) => + SavedObjectsErrorHelpers.createConflictError(type, id, reason).output + .payload as unknown as ErrorPayload; +export const createGenericNotFoundErrorPayload = ( + type: string | null = null, + id: string | null = null +) => + SavedObjectsErrorHelpers.createGenericNotFoundError(type, id).output + .payload as unknown as ErrorPayload; +export const createUnsupportedTypeErrorPayload = (type: string) => + SavedObjectsErrorHelpers.createUnsupportedTypeError(type).output + .payload as unknown as ErrorPayload; + +export const expectError = ({ type, id }: { type: string; id: string }) => ({ + type, + id, + error: expect.any(Object), +}); + +export const expectErrorResult = ( + { type, id }: TypeIdTuple, + error: Record, + overrides: Record = {} +): ExpectedErrorResult => ({ + type, + id, + error: { ...error, ...overrides }, +}); +export const expectErrorNotFound = (obj: TypeIdTuple, overrides?: Record) => + expectErrorResult(obj, createGenericNotFoundErrorPayload(obj.type, obj.id), overrides); +export const expectErrorConflict = (obj: TypeIdTuple, overrides?: Record) => + expectErrorResult(obj, createConflictErrorPayload(obj.type, obj.id), overrides); +export const expectErrorInvalidType = (obj: TypeIdTuple, overrides?: Record) => + expectErrorResult(obj, createUnsupportedTypeErrorPayload(obj.type), overrides); + +export const KIBANA_VERSION = '2.0.0'; +export const CUSTOM_INDEX_TYPE = 'customIndex'; +/** This type has namespaceType: 'agnostic'. */ +export const NAMESPACE_AGNOSTIC_TYPE = 'globalType'; +/** + * This type has namespaceType: 'multiple'. + * + * That means that the object is serialized with a globally unique ID across namespaces. It also means that the object is shareable across + * namespaces. + **/ +export const MULTI_NAMESPACE_TYPE = 'multiNamespaceType'; +/** + * This type has namespaceType: 'multiple-isolated'. + * + * That means that the object is serialized with a globally unique ID across namespaces. It also means that the object is NOT shareable + * across namespaces. This distinction only matters when using the `collectMultiNamespaceReferences` or `updateObjectsSpaces` APIs, or + * when using the `initialNamespaces` argument with the `create` and `bulkCreate` APIs. Those allow you to define or change what + * namespaces an object exists in. + * + * In a nutshell, this type is more restrictive than `MULTI_NAMESPACE_TYPE`, so we use `MULTI_NAMESPACE_ISOLATED_TYPE` for any test cases + * where `MULTI_NAMESPACE_TYPE` would also satisfy the test case. + **/ +export const MULTI_NAMESPACE_ISOLATED_TYPE = 'multiNamespaceIsolatedType'; +/** This type has namespaceType: 'multiple', and it uses a custom index. */ +export const MULTI_NAMESPACE_CUSTOM_INDEX_TYPE = 'multiNamespaceTypeCustomIndex'; +export const HIDDEN_TYPE = 'hiddenType'; +export const ENCRYPTED_TYPE = 'encryptedType'; +export const MULTI_NAMESPACE_ENCRYPTED_TYPE = 'multiNamespaceEncryptedType'; +export const mockVersionProps = { _seq_no: 1, _primary_term: 1 }; +export const mockVersion = encodeHitVersion(mockVersionProps); +export const mockTimestamp = '2017-08-14T15:49:14.886Z'; +export const mockTimestampFields = { updated_at: mockTimestamp }; +export const REMOVE_REFS_COUNT = 42; + +export interface TypeIdTuple { + id: string; + type: string; +} + +export const mappings: SavedObjectsTypeMappingDefinition = { + properties: { + config: { + properties: { + otherField: { + type: 'keyword', + }, + }, + }, + 'index-pattern': { + properties: { + someField: { + type: 'keyword', + }, + }, + }, + dashboard: { + properties: { + otherField: { + type: 'keyword', + }, + }, + }, + [CUSTOM_INDEX_TYPE]: { + properties: { + otherField: { + type: 'keyword', + }, + }, + }, + [NAMESPACE_AGNOSTIC_TYPE]: { + properties: { + yetAnotherField: { + type: 'keyword', + }, + }, + }, + [MULTI_NAMESPACE_TYPE]: { + properties: { + evenYetAnotherField: { + type: 'keyword', + }, + }, + }, + [MULTI_NAMESPACE_ISOLATED_TYPE]: { + properties: { + evenYetAnotherField: { + type: 'keyword', + }, + }, + }, + [MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]: { + properties: { + evenYetAnotherField: { + type: 'keyword', + }, + }, + }, + [HIDDEN_TYPE]: { + properties: { + someField: { + type: 'keyword', + }, + }, + }, + [ENCRYPTED_TYPE]: { + properties: { + encryptedField: { + type: 'keyword', + }, + }, + }, + [MULTI_NAMESPACE_ENCRYPTED_TYPE]: { + properties: { + encryptedField: { + type: 'keyword', + }, + }, + }, + }, +}; + +export const authRecord: Record = { + get: { authorizedSpaces: ['bar'] }, +}; +export const authMap = Object.freeze(new Map([['foo', authRecord]])); + +export const checkAuthError = SavedObjectsErrorHelpers.createBadRequestError( + 'Failed to check authorization' +); + +export const enforceError = SavedObjectsErrorHelpers.decorateForbiddenError( + new Error('Unauthorized'), + 'User lacks privileges' +); + +export const setupCheckAuthorized = ( + mockSecurityExt: jest.Mocked +) => { + mockSecurityExt.checkAuthorization.mockResolvedValue({ + status: 'fully_authorized', + typeMap: authMap, + }); +}; + +export const setupCheckPartiallyAuthorized = ( + mockSecurityExt: jest.Mocked +) => { + mockSecurityExt.checkAuthorization.mockResolvedValue({ + status: 'partially_authorized', + typeMap: authMap, + }); +}; + +export const setupCheckUnauthorized = ( + mockSecurityExt: jest.Mocked +) => { + mockSecurityExt.checkAuthorization.mockResolvedValue({ + status: 'unauthorized', + typeMap: new Map([]), + }); +}; + +export const setupEnforceSuccess = ( + mockSecurityExt: jest.Mocked +) => { + mockSecurityExt.enforceAuthorization.mockImplementation( + (params: EnforceAuthorizationParams) => { + const { auditCallback } = params; + auditCallback?.(undefined); + } + ); +}; + +export const setupEnforceFailure = ( + mockSecurityExt: jest.Mocked +) => { + mockSecurityExt.enforceAuthorization.mockImplementation( + (params: EnforceAuthorizationParams) => { + const { auditCallback } = params; + auditCallback?.(enforceError); + throw enforceError; + } + ); +}; + +export const setupRedactPassthrough = ( + mockSecurityExt: jest.Mocked +) => { + mockSecurityExt.redactNamespaces.mockImplementation(({ savedObject: object }) => { + return object; + }); +}; + +export const createType = ( + type: string, + parts: Partial = {} +): SavedObjectsType => ({ + name: type, + hidden: false, + namespaceType: 'single', + mappings: { + properties: mappings.properties[type].properties! as SavedObjectsMappingProperties, + }, + migrations: { '1.1.1': (doc) => doc }, + ...parts, +}); + +export const createRegistry = () => { + const registry = new SavedObjectTypeRegistry(); + registry.registerType(createType('config')); + registry.registerType(createType('index-pattern')); + registry.registerType( + createType('dashboard', { + schemas: { + '8.0.0-testing': schema.object({ + title: schema.maybe(schema.string()), + otherField: schema.maybe(schema.string()), + }), + }, + }) + ); + registry.registerType(createType(CUSTOM_INDEX_TYPE, { indexPattern: 'custom' })); + registry.registerType(createType(NAMESPACE_AGNOSTIC_TYPE, { namespaceType: 'agnostic' })); + registry.registerType(createType(MULTI_NAMESPACE_TYPE, { namespaceType: 'multiple' })); + registry.registerType( + createType(MULTI_NAMESPACE_ISOLATED_TYPE, { namespaceType: 'multiple-isolated' }) + ); + registry.registerType( + createType(MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, { + namespaceType: 'multiple', + indexPattern: 'custom', + }) + ); + registry.registerType( + createType(HIDDEN_TYPE, { + hidden: true, + namespaceType: 'agnostic', + }) + ); + registry.registerType( + createType(ENCRYPTED_TYPE, { + namespaceType: 'single', + }) + ); + registry.registerType( + createType(MULTI_NAMESPACE_ENCRYPTED_TYPE, { + namespaceType: 'multiple', + }) + ); + return registry; +}; + +export const createSpySerializer = (registry: SavedObjectTypeRegistry) => { + const spyInstance = { + isRawSavedObject: jest.fn(), + rawToSavedObject: jest.fn(), + savedObjectToRaw: jest.fn(), + generateRawId: jest.fn(), + generateRawLegacyUrlAliasId: jest.fn(), + trimIdPrefix: jest.fn(), + }; + const realInstance = new SavedObjectsSerializer(registry); + Object.keys(spyInstance).forEach((key) => { + // @ts-expect-error no proper way to do this with typing support + spyInstance[key].mockImplementation((...args) => realInstance[key](...args)); + }); + + return spyInstance as unknown as jest.Mocked; +}; + +export const createDocumentMigrator = (registry: SavedObjectTypeRegistry) => { + return new DocumentMigrator({ + typeRegistry: registry, + kibanaVersion: KIBANA_VERSION, + log: loggerMock.create(), + }); +}; + +export const getMockGetResponse = ( + registry: SavedObjectTypeRegistry, + { + type, + id, + references, + namespace: objectNamespace, + originId, + }: { + type: string; + id: string; + namespace?: string; + originId?: string; + references?: SavedObjectReference[]; + }, + namespace?: string | string[] +) => { + let namespaces; + if (objectNamespace) { + namespaces = [objectNamespace]; + } else if (namespace) { + namespaces = Array.isArray(namespace) ? namespace : [namespace]; + } else { + namespaces = ['default']; + } + const namespaceId = namespaces[0] === 'default' ? undefined : namespaces[0]; + + return { + // NOTE: Elasticsearch returns more fields (_index, _type) but the SavedObjectsRepository method ignores these + found: true, + _id: `${registry.isSingleNamespace(type) && namespaceId ? `${namespaceId}:` : ''}${type}:${id}`, + ...mockVersionProps, + _source: { + ...(registry.isSingleNamespace(type) && { namespace: namespaceId }), + ...(registry.isMultiNamespace(type) && { namespaces }), + ...(originId && { originId }), + type, + [type]: + type !== ENCRYPTED_TYPE && type !== MULTI_NAMESPACE_ENCRYPTED_TYPE + ? { title: 'Testing' } + : { + title: 'Testing', + attrOne: 'one', + attrSecret: '*secret*', + attrNotSoSecret: '*not-so-secret*', + attrThree: 'three', + }, + references, + specialProperty: 'specialValue', + ...mockTimestampFields, + } as SavedObjectsRawDocSource, + } as estypes.GetResponse; +}; + +export const getMockMgetResponse = ( + registry: SavedObjectTypeRegistry, + objects: Array, + namespace?: string +) => + ({ + docs: objects.map((obj) => + obj.found === false + ? obj + : getMockGetResponse(registry, obj, obj.initialNamespaces ?? namespace) + ), + } as estypes.MgetResponse); + +expect.extend({ + toBeDocumentWithoutError(received, type, id) { + if (received.type === type && received.id === id && !received.error) { + return { message: () => `expected type and id not to match without error`, pass: true }; + } else { + return { message: () => `expected type and id to match without error`, pass: false }; + } + }, +}); + +export const mockUpdateResponse = ( + client: ElasticsearchClientMock, + type: string, + id: string, + options?: SavedObjectsUpdateOptions, + namespaces?: string[], + originId?: string +) => { + client.update.mockResponseOnce( + { + _id: `${type}:${id}`, + ...mockVersionProps, + result: 'updated', + // don't need the rest of the source for test purposes, just the namespace and namespaces attributes + get: { + _source: { + namespaces: namespaces ?? [options?.namespace ?? 'default'], + namespace: options?.namespace, + + // If the existing saved object contains an originId attribute, the operation will return it in the result. + // The originId parameter is just used for test purposes to modify the mock cluster call response. + ...(!!originId && { originId }), + }, + }, + } as estypes.UpdateResponse, + { statusCode: 200 } + ); +}; + +export const updateSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + registry: SavedObjectTypeRegistry, + type: string, + id: string, + attributes: T, + options?: SavedObjectsUpdateOptions, + internalOptions: { + originId?: string; + mockGetResponseValue?: estypes.GetResponse; + } = {}, + objNamespaces?: string[] +) => { + const { mockGetResponseValue, originId } = internalOptions; + if (registry.isMultiNamespace(type)) { + const mockGetResponse = + mockGetResponseValue ?? + getMockGetResponse(registry, { type, id }, objNamespaces ?? options?.namespace); + client.get.mockResponseOnce(mockGetResponse, { statusCode: 200 }); + } + mockUpdateResponse(client, type, id, options, objNamespaces, originId); + const result = await repository.update(type, id, attributes, options); + expect(client.get).toHaveBeenCalledTimes(registry.isMultiNamespace(type) ? 1 : 0); + return result; +}; + +export const bulkGet = async ( + repository: SavedObjectsRepository, + objects: SavedObjectsBulkGetObject[], + options?: SavedObjectsBaseOptions +) => + repository.bulkGet( + objects.map(({ type, id, namespaces }) => ({ type, id, namespaces })), // bulkGet only uses type, id, and optionally namespaces + options + ); + +export const bulkGetSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + registry: SavedObjectTypeRegistry, + objects: SavedObject[], + options?: SavedObjectsBaseOptions +) => { + const mockResponse = getMockMgetResponse(registry, objects, options?.namespace); + client.mget.mockResponseOnce(mockResponse); + const result = await bulkGet(repository, objects, options); + expect(client.mget).toHaveBeenCalledTimes(1); + return { result, mockResponse }; +}; + +export const expectBulkGetResult = ( + { type, id }: TypeIdTuple, + doc: estypes.GetGetResult +) => ({ + type, + id, + namespaces: doc._source!.namespaces ?? [doc._source!.namespace] ?? ['default'], + ...(doc._source!.originId && { originId: doc._source!.originId }), + ...(doc._source!.updated_at && { updated_at: doc._source!.updated_at }), + version: encodeHitVersion(doc), + attributes: doc._source![type], + references: doc._source!.references || [], + migrationVersion: doc._source!.migrationVersion, +}); + +export const getMockBulkCreateResponse = ( + objects: SavedObjectsBulkCreateObject[], + namespace?: string +) => { + return { + errors: false, + took: 1, + items: objects.map(({ type, id, originId, attributes, references, migrationVersion }) => ({ + create: { + // status: 1, + // _index: '.kibana', + _id: `${namespace ? `${namespace}:` : ''}${type}:${id}`, + _source: { + [type]: attributes, + type, + namespace, + ...(originId && { originId }), + references, + ...mockTimestampFields, + migrationVersion: migrationVersion || { [type]: '1.1.1' }, + }, + ...mockVersionProps, + }, + })), + } as unknown as estypes.BulkResponse; +}; + +export const bulkCreateSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + objects: SavedObjectsBulkCreateObject[], + options?: SavedObjectsCreateOptions +) => { + const mockResponse = getMockBulkCreateResponse(objects, options?.namespace); + client.bulk.mockResponse(mockResponse); + const result = await repository.bulkCreate(objects, options); + return result; +}; + +export const expectCreateResult = (obj: { + type: string; + namespace?: string; + namespaces?: string[]; +}) => ({ + ...obj, + migrationVersion: { [obj.type]: '1.1.1' }, + coreMigrationVersion: KIBANA_VERSION, + version: mockVersion, + namespaces: obj.namespaces ?? [obj.namespace ?? 'default'], + ...mockTimestampFields, +}); + +export const getMockBulkUpdateResponse = ( + registry: SavedObjectTypeRegistry, + objects: TypeIdTuple[], + options?: SavedObjectsBulkUpdateOptions, + originId?: string +) => + ({ + items: objects.map(({ type, id }) => ({ + update: { + _id: `${ + registry.isSingleNamespace(type) && options?.namespace ? `${options?.namespace}:` : '' + }${type}:${id}`, + ...mockVersionProps, + get: { + _source: { + // If the existing saved object contains an originId attribute, the operation will return it in the result. + // The originId parameter is just used for test purposes to modify the mock cluster call response. + ...(!!originId && { originId }), + }, + }, + result: 'updated', + }, + })), + } as estypes.BulkResponse); + +export const bulkUpdateSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + registry: SavedObjectTypeRegistry, + objects: SavedObjectsBulkUpdateObject[], + options?: SavedObjectsBulkUpdateOptions, + originId?: string, + multiNamespaceSpace?: string // the space for multi namespace objects returned by mock mget (this is only needed for space ext testing) +) => { + const multiNamespaceObjects = objects.filter(({ type }) => registry.isMultiNamespace(type)); + if (multiNamespaceObjects?.length) { + const response = getMockMgetResponse( + registry, + multiNamespaceObjects, + multiNamespaceSpace ?? options?.namespace + ); + client.mget.mockResponseOnce(response); + } + const response = getMockBulkUpdateResponse(registry, objects, options, originId); + client.bulk.mockResponseOnce(response); + const result = await repository.bulkUpdate(objects, options); + expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0); + return result; +}; + +export const expectUpdateResult = ({ + type, + id, + attributes, + references, +}: SavedObjectsBulkUpdateObject) => ({ + type, + id, + attributes, + references, + version: mockVersion, + namespaces: ['default'], + ...mockTimestampFields, +}); + +export type IGenerateSearchResultsFunction = ( + namespace?: string +) => estypes.SearchResponse; + +export const generateIndexPatternSearchResults = (namespace?: string) => { + return { + took: 1, + timed_out: false, + _shards: {} as any, + hits: { + total: 4, + hits: [ + { + _index: '.kibana', + _id: `${namespace ? `${namespace}:` : ''}index-pattern:logstash-*`, + _score: 1, + ...mockVersionProps, + _source: { + namespace, + originId: 'some-origin-id', // only one of the results has an originId, this is intentional to test both a positive and negative case + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { + title: 'logstash-*', + timeFieldName: '@timestamp', + notExpandable: true, + }, + }, + }, + { + _index: '.kibana', + _id: `${namespace ? `${namespace}:` : ''}config:6.0.0-alpha1`, + _score: 2, + ...mockVersionProps, + _source: { + namespace, + type: 'config', + ...mockTimestampFields, + config: { + buildNum: 8467, + defaultIndex: 'logstash-*', + }, + }, + }, + { + _index: '.kibana', + _id: `${namespace ? `${namespace}:` : ''}index-pattern:stocks-*`, + _score: 3, + ...mockVersionProps, + _source: { + namespace, + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { + title: 'stocks-*', + timeFieldName: '@timestamp', + notExpandable: true, + }, + }, + }, + { + _index: '.kibana', + _id: `${NAMESPACE_AGNOSTIC_TYPE}:something`, + _score: 4, + ...mockVersionProps, + _source: { + type: NAMESPACE_AGNOSTIC_TYPE, + ...mockTimestampFields, + [NAMESPACE_AGNOSTIC_TYPE]: { + name: 'bar', + }, + }, + }, + ], + }, + } as estypes.SearchResponse; +}; + +export const findSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + options: SavedObjectsFindOptions, + namespace?: string, + generateSearchResultsFunc: IGenerateSearchResultsFunction = generateIndexPatternSearchResults +) => { + const generatedResults = generateSearchResultsFunc(namespace); + client.search.mockResponseOnce(generatedResults); + const result = await repository.find(options); + expect(mockGetSearchDsl).toHaveBeenCalledTimes(1); + expect(client.search).toHaveBeenCalledTimes(1); + return { result, generatedResults }; +}; + +export const deleteSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + registry: SavedObjectTypeRegistry, + type: string, + id: string, + options?: SavedObjectsDeleteOptions, + internalOptions: { mockGetResponseValue?: estypes.GetResponse } = {} +) => { + const { mockGetResponseValue } = internalOptions; + if (registry.isMultiNamespace(type)) { + const mockGetResponse = + mockGetResponseValue ?? getMockGetResponse(registry, { type, id }, options?.namespace); + client.get.mockResponseOnce(mockGetResponse); + } + client.delete.mockResponseOnce({ + result: 'deleted', + } as estypes.DeleteResponse); + const result = await repository.delete(type, id, options); + expect(client.get).toHaveBeenCalledTimes(registry.isMultiNamespace(type) ? 1 : 0); + return result; +}; + +export const removeReferencesToSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + type: string, + id: string, + options = {}, + updatedCount = REMOVE_REFS_COUNT +) => { + client.updateByQuery.mockResponseOnce({ + updated: updatedCount, + }); + return await repository.removeReferencesTo(type, id, options); +}; + +export const checkConflicts = async ( + repository: SavedObjectsRepository, + objects: TypeIdTuple[], + options?: SavedObjectsBaseOptions +) => + repository.checkConflicts( + objects.map(({ type, id }) => ({ type, id })), // checkConflicts only uses type and id + options + ); + +export const checkConflictsSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + registry: SavedObjectTypeRegistry, + objects: TypeIdTuple[], + options?: SavedObjectsBaseOptions +) => { + const response = getMockMgetResponse(registry, objects, options?.namespace); + client.mget.mockResolvedValue( + elasticsearchClientMock.createSuccessTransportRequestPromise(response) + ); + const result = await checkConflicts(repository, objects, options); + expect(client.mget).toHaveBeenCalledTimes(1); + return result; +}; + +export const getSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + registry: SavedObjectTypeRegistry, + type: string, + id: string, + options?: SavedObjectsBaseOptions, + originId?: string, + objNamespaces?: string[] +) => { + const response = getMockGetResponse( + registry, + { + type, + id, + // "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the + // operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response. + originId, + }, + objNamespaces ?? options?.namespace + ); + client.get.mockResponseOnce(response); + const result = await repository.get(type, id, options); + expect(client.get).toHaveBeenCalledTimes(1); + return result; +}; + +export function setsAreEqual(setA: Set, setB: Set) { + return isEqual(Array(setA).sort(), Array(setB).sort()); +} + +export function mapsAreEqual(mapA: Map>, mapB: Map>) { + return ( + mapA.size === mapB.size && + Array.from(mapA.keys()).every((key) => setsAreEqual(mapA.get(key)!, mapB.get(key)!)) + ); +} diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts new file mode 100644 index 00000000000000..d37bf6ff8ed605 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts @@ -0,0 +1,687 @@ +/* + * 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 { + pointInTimeFinderMock, + mockGetCurrentTime, + mockPreflightCheckForCreate, + mockGetSearchDsl, +} from './repository.test.mock'; + +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { SavedObjectsRepository } from './repository'; +import { loggerMock } from '@kbn/logging-mocks'; + +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { kibanaMigratorMock } from '../mocks'; +import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; +import { + ISavedObjectsEncryptionExtension, + SavedObjectsRawDocSource, +} from '@kbn/core-saved-objects-server'; +import { + bulkCreateSuccess, + bulkGetSuccess, + bulkUpdateSuccess, + createDocumentMigrator, + createRegistry, + createSpySerializer, + ENCRYPTED_TYPE, + findSuccess, + getMockGetResponse, + mappings, + mockTimestamp, + mockTimestampFields, + mockVersion, + mockVersionProps, + MULTI_NAMESPACE_ENCRYPTED_TYPE, + TypeIdTuple, + updateSuccess, +} from './repository.common.test'; +import { extensionsMock } from './extensions.test.mock'; + +// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository +// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. + +describe('SavedObjectsRepository Encryption Extension', () => { + let client: ReturnType; + let repository: SavedObjectsRepository; + let migrator: ReturnType; + let logger: ReturnType; + let serializer: jest.Mocked; + let mockEncryptionExt: jest.Mocked; + + const registry = createRegistry(); + const documentMigrator = createDocumentMigrator(registry); + + const namespace = 'foo-namespace'; + + const encryptedSO = { + id: 'encrypted-id', + type: ENCRYPTED_TYPE, + namespaces: [namespace], + attributes: { + attrNotSoSecret: '*not-so-secret*', + attrOne: 'one', + attrSecret: '*secret*', + attrThree: 'three', + title: 'Testing', + }, + references: [], + }; + const decryptedStrippedAttributes = { + attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, + }; + const nonEncryptedSO = { + id: 'non-encrypted-id', + type: 'index-pattern', + namespaces: [namespace], + attributes: { title: 'Logstash' }, + references: [], + }; + + const instantiateRepository = () => { + const allTypes = registry.getAllTypes().map((type) => type.name); + const allowedTypes = [...new Set(allTypes.filter((type) => !registry.isHidden(type)))]; + + // @ts-expect-error must use the private constructor to use the mocked serializer + return new SavedObjectsRepository({ + index: '.kibana-test', + mappings, + client, + migrator, + typeRegistry: registry, + serializer, + allowedTypes, + logger, + extensions: { encryptionExtension: mockEncryptionExt }, + }); + }; + + beforeEach(() => { + pointInTimeFinderMock.mockClear(); + client = elasticsearchClientMock.createElasticsearchClient(); + migrator = kibanaMigratorMock.create(); + documentMigrator.prepareMigrations(); + migrator.migrateDocument = jest.fn().mockImplementation(documentMigrator.migrate); + migrator.runMigrations = jest.fn().mockResolvedValue([{ status: 'skipped' }]); + logger = loggerMock.create(); + + // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation + serializer = createSpySerializer(registry); + + // create a mock saved objects encryption extension + mockEncryptionExt = extensionsMock.createEncryptionExtension(); + + mockGetCurrentTime.mockReturnValue(mockTimestamp); + mockGetSearchDsl.mockClear(); + + repository = instantiateRepository(); + }); + + describe('#get', () => { + it('does not attempt to decrypt or strip attributes if type is not encryptable', async () => { + const options = { namespace }; + + const response = getMockGetResponse(registry, { + type: nonEncryptedSO.type, + id: nonEncryptedSO.id, + namespace, + }); + + client.get.mockResponseOnce(response); + mockEncryptionExt.isEncryptableType.mockReturnValue(false); + const result = await repository.get(nonEncryptedSO.type, nonEncryptedSO.id, options); + expect(client.get).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(nonEncryptedSO.type); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).not.toBeCalled(); + expect(result).toEqual( + expect.objectContaining({ + type: nonEncryptedSO.type, + id: nonEncryptedSO.id, + namespaces: [namespace], + }) + ); + }); + + it('decrypts and strips attributes if type is encryptable', async () => { + const options = { namespace }; + + const response = getMockGetResponse(registry, { + type: encryptedSO.type, + id: encryptedSO.id, + namespace: options.namespace, + }); + client.get.mockResponseOnce(response); + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({ + ...encryptedSO, + ...decryptedStrippedAttributes, + }); + + const result = await repository.get(encryptedSO.type, encryptedSO.id, options); + expect(client.get).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledWith( + expect.objectContaining({ + ...encryptedSO, + }), + undefined + ); + expect(result).toEqual( + expect.objectContaining({ + ...decryptedStrippedAttributes, + }) + ); + }); + }); + + describe('#create', () => { + beforeEach(() => { + mockPreflightCheckForCreate.mockReset(); + mockPreflightCheckForCreate.mockImplementation(({ objects }) => { + return Promise.resolve(objects.map(({ type, id }) => ({ type, id }))); // respond with no errors by default + }); + client.create.mockResponseImplementation((params) => { + return { + body: { + _id: params.id, + ...mockVersionProps, + } as estypes.CreateResponse, + }; + }); + }); + + it('does not attempt to encrypt or decrypt if type is not encryptable', async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(false); + const result = await repository.create(nonEncryptedSO.type, nonEncryptedSO.attributes, { + namespace, + }); + expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); + expect(client.create).toHaveBeenCalled(); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(3); // getValidId, optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(nonEncryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).not.toHaveBeenCalled(); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).not.toBeCalled(); + expect(result).toEqual( + expect.objectContaining({ + type: nonEncryptedSO.type, + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + namespaces: [namespace], + }) + ); + }); + + it('encrypts attributes and strips them from response if type is encryptable', async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({ + ...encryptedSO, + ...decryptedStrippedAttributes, + }); + + const result = await repository.create(encryptedSO.type, encryptedSO.attributes, { + namespace, + }); + expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); + expect(client.create).toHaveBeenCalled(); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(3); // getValidId, optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + { + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + namespace, + type: ENCRYPTED_TYPE, + }, + encryptedSO.attributes + ); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledWith( + expect.objectContaining({ + ...encryptedSO, + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + attributes: undefined, + }), + encryptedSO.attributes // original attributes + ); + expect(result).toEqual( + expect.objectContaining({ + ...decryptedStrippedAttributes, + }) + ); + }); + + it(`fails if non-UUID ID is specified for encrypted type`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({ + ...encryptedSO, + ...decryptedStrippedAttributes, + }); + + await expect( + repository.create(encryptedSO.type, encryptedSO.attributes, { + id: 'this-should-throw-an-error', + }) + ).rejects.toThrow( + 'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.: Bad Request' + ); + }); + + it(`allows a specified ID when overwriting an existing object`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({ + ...encryptedSO, + ...decryptedStrippedAttributes, + }); + + await expect( + repository.create(encryptedSO.type, encryptedSO.attributes, { + id: encryptedSO.id, + overwrite: true, + version: mockVersion, + }) + ).resolves.not.toThrowError(); + }); + + describe('namespace', () => { + const doTest = async (optNamespace: string, expectNamespaceInDescriptor: boolean) => { + const options = { overwrite: true, namespace: optNamespace }; + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + + await repository.create( + expectNamespaceInDescriptor ? ENCRYPTED_TYPE : MULTI_NAMESPACE_ENCRYPTED_TYPE, + encryptedSO.attributes, + options + ); + expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); + expect(client.index).toHaveBeenCalled(); // if overwrite is true, index will be called instead of create + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(3); // getValidId, optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith( + expectNamespaceInDescriptor ? ENCRYPTED_TYPE : MULTI_NAMESPACE_ENCRYPTED_TYPE + ); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + { + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + namespace: expectNamespaceInDescriptor ? namespace : undefined, + type: expectNamespaceInDescriptor ? ENCRYPTED_TYPE : MULTI_NAMESPACE_ENCRYPTED_TYPE, + }, + encryptedSO.attributes + ); + }; + + it('uses `namespace` to encrypt attributes if it is specified when type is single-namespace', async () => { + await doTest(namespace, true); + }); + + it('does not use `namespace` to encrypt attributes if it is specified when type is not single-namespace', async () => { + await doTest(namespace, false); + }); + }); + }); + + describe('#update', () => { + const attributes = { title: 'Testing' }; + + beforeEach(() => { + mockPreflightCheckForCreate.mockReset(); + mockPreflightCheckForCreate.mockImplementation(({ objects }) => { + return Promise.resolve(objects.map(({ type, id }) => ({ type, id }))); // respond with no errors by default + }); + }); + + it('does not attempt to encrypt or decrypt if type is not encryptable', async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(false); + const result = await updateSuccess( + client, + repository, + registry, + nonEncryptedSO.type, + nonEncryptedSO.id, + attributes, + { + namespace, + } + ); + expect(client.update).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(2); // (no upsert) optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(nonEncryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).not.toHaveBeenCalled(); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).not.toBeCalled(); + expect(result).toEqual( + expect.objectContaining({ + type: nonEncryptedSO.type, + id: nonEncryptedSO.id, + namespaces: [namespace], + }) + ); + }); + + it('encrypts attributes and strips them from response if type is encryptable', async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({ + ...encryptedSO, + ...decryptedStrippedAttributes, + }); + const result = await updateSuccess( + client, + repository, + registry, + encryptedSO.type, + encryptedSO.id, + encryptedSO.attributes, + { + namespace, + references: encryptedSO.references, + } + ); + expect(client.update).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(2); // (no upsert) optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + { + id: encryptedSO.id, + namespace, + type: ENCRYPTED_TYPE, + }, + encryptedSO.attributes + ); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledWith( + expect.objectContaining({ + ...encryptedSO, + }), + encryptedSO.attributes // original attributes + ); + expect(result).toEqual( + expect.objectContaining({ + ...decryptedStrippedAttributes, + }) + ); + }); + }); + + describe('#bulkGet', () => { + const _expectClientCallArgs = ( + objects: TypeIdTuple[], + { + _index = expect.any(String), + getId = () => expect.any(String), + }: { _index?: string; getId?: (type: string, id: string) => string } + ) => { + expect(client.mget).toHaveBeenCalledWith( + expect.objectContaining({ + body: { + docs: objects.map(({ type, id }) => + expect.objectContaining({ + _index, + _id: getId(type, id), + }) + ), + }, + }), + expect.anything() + ); + }; + + it(`only attempts to decrypt and strip attributes for types that are encryptable`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(false); + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + const getId = (type: string, id: string) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) + await bulkGetSuccess(client, repository, registry, [nonEncryptedSO, encryptedSO], { + namespace, + }); + _expectClientCallArgs([nonEncryptedSO, encryptedSO], { getId }); + expect(mockEncryptionExt.isEncryptableType).toBeCalledTimes(2); + expect(mockEncryptionExt.isEncryptableType).toBeCalledWith(nonEncryptedSO.type); + expect(mockEncryptionExt.isEncryptableType).toBeCalledWith(encryptedSO.type); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toBeCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toBeCalledWith( + expect.objectContaining({ ...encryptedSO }), + undefined + ); + }); + }); + + describe('#bulkCreate', () => { + it(`only attempts to encrypt and decrypt attributes for types that are encryptable`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(false); // getValidId + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(false); // optionallyEncryptAttributes + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(false); // optionallyDecryptAndRedactSingleResult + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + await bulkCreateSuccess(client, repository, [ + nonEncryptedSO, + { ...encryptedSO, id: undefined }, // Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID + ]); + expect(client.bulk).toHaveBeenCalledTimes(1); + + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(6); // x2 getValidId, optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(nonEncryptedSO.type); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + expect.objectContaining({ type: encryptedSO.type }), + encryptedSO.attributes + ); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledWith( + expect.objectContaining({ type: encryptedSO.type }), + encryptedSO.attributes + ); + }); + + it(`fails if non-UUID ID is specified for encrypted type`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + const result = await bulkCreateSuccess(client, repository, [ + encryptedSO, // Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID + ]); + expect(client.bulk).not.toHaveBeenCalled(); + expect(result.saved_objects).not.toBeUndefined(); + expect(result.saved_objects.length).toBe(1); + expect(result.saved_objects[0].error).not.toBeUndefined(); + expect(result.saved_objects[0].error).toMatchObject({ + statusCode: 400, + error: 'Bad Request', + message: + 'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.: Bad Request', + }); + }); + + it(`does not fail if ID is specified for not encrypted type`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(false); + const result = await bulkCreateSuccess(client, repository, [nonEncryptedSO]); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(result.saved_objects).not.toBeUndefined(); + expect(result.saved_objects.length).toBe(1); + expect(result.saved_objects[0].error).toBeUndefined(); + }); + + it(`allows a specified ID when overwriting an existing object`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + mockEncryptionExt.decryptOrStripResponseAttributes.mockResolvedValue({ + ...encryptedSO, + version: mockVersion, + ...decryptedStrippedAttributes, + }); + + const result = await bulkCreateSuccess( + client, + repository, + [{ ...encryptedSO, version: mockVersion }], + { + overwrite: true, + // version: mockVersion, // this doesn't work in bulk...looks like it checks the object itself? + } + ); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(result.saved_objects).not.toBeUndefined(); + expect(result.saved_objects.length).toBe(1); + expect(result.saved_objects[0].error).toBeUndefined(); + }); + }); + + describe('#bulkUpdate', () => { + it(`only attempts to encrypt and decrypt attributes for types that are encryptable`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(false); // optionallyEncryptAttributes + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(false); // optionallyDecryptAndRedactSingleResult + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + + await bulkUpdateSuccess(client, repository, registry, [nonEncryptedSO, encryptedSO]); + expect(client.bulk).toHaveBeenCalledTimes(1); + + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(4); // 2x optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(nonEncryptedSO.type); + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + expect.objectContaining({ type: encryptedSO.type }), + encryptedSO.attributes + ); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toHaveBeenCalledWith( + expect.objectContaining({ type: encryptedSO.type }), + encryptedSO.attributes + ); + }); + + it('does not use options `namespace` or object `namespace` to encrypt attributes if neither are specified', async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + + await bulkUpdateSuccess( + client, + repository, + registry, + [{ ...encryptedSO, namespace: undefined }], + { namespace: undefined } + ); + expect(client.bulk).toHaveBeenCalledTimes(1); + + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(2); // 2x optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + { id: encryptedSO.id, type: encryptedSO.type, namespace: undefined }, + encryptedSO.attributes + ); + }); + + it('with a single-namespace type...uses options `namespace` to encrypt attributes if it is specified and object `namespace` is not', async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + const usedNamespace = 'options-namespace'; + + await bulkUpdateSuccess( + client, + repository, + registry, + [{ ...encryptedSO, namespace: undefined }], + { namespace: usedNamespace } + ); + expect(client.bulk).toHaveBeenCalledTimes(1); + + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(2); // 2x optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + { id: encryptedSO.id, type: encryptedSO.type, namespace: usedNamespace }, + encryptedSO.attributes + ); + }); + + it('with a single-namespace type...uses object `namespace` to encrypt attributes if it is specified', async () => { + mockEncryptionExt.isEncryptableType.mockReturnValue(true); + const usedNamespace = 'object-namespace'; + + await bulkUpdateSuccess( + client, + repository, + registry, + [{ ...encryptedSO, namespace: usedNamespace }], + { namespace: undefined } + ); + expect(client.bulk).toHaveBeenCalledTimes(1); + + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledTimes(2); // 2x optionallyEncryptAttributes, optionallyDecryptAndRedactSingleResult + expect(mockEncryptionExt.isEncryptableType).toHaveBeenCalledWith(encryptedSO.type); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.encryptAttributes).toHaveBeenCalledWith( + { id: encryptedSO.id, type: encryptedSO.type, namespace: usedNamespace }, + encryptedSO.attributes + ); + }); + }); + + describe('#find', () => { + const generateSearchResults = (space?: string) => { + return { + took: 1, + timed_out: false, + _shards: {} as any, + hits: { + total: 2, + hits: [ + { + _index: '.kibana', + _id: `${space ? `${space}:` : ''}${encryptedSO.type}:${encryptedSO.id}`, + _score: 1, + ...mockVersionProps, + _source: { + ...encryptedSO, + originId: 'some-origin-id', // only one of the results has an originId, this is intentional to test both a positive and negative case + }, + }, + { + _index: '.kibana', + _id: `${space ? `${space}:` : ''}index-pattern:logstash-*`, + _score: 2, + ...mockVersionProps, + _source: { + namespace: space, + originId: 'some-origin-id', // only one of the results has an originId, this is intentional to test both a positive and negative case + type: 'index-pattern', + ...mockTimestampFields, + 'index-pattern': { + title: 'logstash-*', + timeFieldName: '@timestamp', + notExpandable: true, + }, + }, + }, + ], + }, + } as estypes.SearchResponse; + }; + + it(`only attempts to decrypt and strip attributes for types that are encryptable`, async () => { + mockEncryptionExt.isEncryptableType.mockReturnValueOnce(true); + await findSuccess( + client, + repository, + { type: [encryptedSO.type, 'index-pattern'] }, + undefined, + generateSearchResults + ); + expect(client.search).toHaveBeenCalledTimes(1); + expect(mockEncryptionExt.isEncryptableType).toBeCalledTimes(2); + expect(mockEncryptionExt.isEncryptableType).toBeCalledWith(encryptedSO.type); + expect(mockEncryptionExt.isEncryptableType).toBeCalledWith('index-pattern'); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toBeCalledTimes(1); + expect(mockEncryptionExt.decryptOrStripResponseAttributes).toBeCalledWith( + expect.objectContaining({ type: encryptedSO.type, id: encryptedSO.id }), + undefined + ); + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts new file mode 100644 index 00000000000000..4c8cfc2ece4056 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -0,0 +1,1826 @@ +/* + * 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 { + pointInTimeFinderMock, + mockGetCurrentTime, + mockGetSearchDsl, + mockDeleteLegacyUrlAliases, + mockPreflightCheckForCreate, +} from './repository.test.mock'; + +import { SavedObjectsRepository } from './repository'; +import { loggerMock } from '@kbn/logging-mocks'; +import { estypes } from '@elastic/elasticsearch'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { SavedObjectsBulkUpdateObject } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { + ISavedObjectsSecurityExtension, + AuditAction, + SavedObjectsRawDocSource, +} from '@kbn/core-saved-objects-server'; +import { kibanaMigratorMock } from '../mocks'; +import { extensionsMock } from './extensions.test.mock'; +import { + createRegistry, + createDocumentMigrator, + mappings, + createSpySerializer, + mockTimestamp, + checkAuthError, + getSuccess, + setupCheckUnauthorized, + setupEnforceFailure, + enforceError, + setupCheckAuthorized, + setupRedactPassthrough, + MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, + setsAreEqual, + mapsAreEqual, + authMap, + setupEnforceSuccess, + updateSuccess, + deleteSuccess, + removeReferencesToSuccess, + REMOVE_REFS_COUNT, + checkConflictsSuccess, + findSuccess, + mockTimestampFields, + mockVersion, + NAMESPACE_AGNOSTIC_TYPE, + setupCheckPartiallyAuthorized, + bulkGetSuccess, + expectBulkGetResult, + bulkCreateSuccess, + expectCreateResult, + MULTI_NAMESPACE_TYPE, + bulkUpdateSuccess, + expectUpdateResult, +} from './repository.common.test'; + +// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository +// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. + +describe('SavedObjectsRepository Security Extension', () => { + let client: ReturnType; + let repository: SavedObjectsRepository; + let migrator: ReturnType; + let logger: ReturnType; + let serializer: jest.Mocked; + let mockSecurityExt: jest.Mocked; + + const registry = createRegistry(); + const documentMigrator = createDocumentMigrator(registry); + + const instantiateRepository = () => { + const allTypes = registry.getAllTypes().map((type) => type.name); + const allowedTypes = [...new Set(allTypes.filter((type) => !registry.isHidden(type)))]; + + // @ts-expect-error must use the private constructor to use the mocked serializer + return new SavedObjectsRepository({ + index: '.kibana-test', + mappings, + client, + migrator, + typeRegistry: registry, + serializer, + allowedTypes, + logger, + extensions: { securityExtension: mockSecurityExt }, + }); + }; + + const type = 'index-pattern'; + const id = 'logstash-*'; + const namespace = 'foo-namespace'; + const attributes = { attr1: 'one', attr2: 'two', attr3: 'three' }; + const multiNamespaceObjNamespaces = ['ns-1', 'ns-2', namespace]; + + beforeEach(() => { + pointInTimeFinderMock.mockClear(); + client = elasticsearchClientMock.createElasticsearchClient(); + migrator = kibanaMigratorMock.create(); + documentMigrator.prepareMigrations(); + migrator.migrateDocument = jest.fn().mockImplementation(documentMigrator.migrate); + migrator.runMigrations = jest.fn().mockResolvedValue([{ status: 'skipped' }]); + logger = loggerMock.create(); + + // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation + serializer = createSpySerializer(registry); + + // create a mock saved objects encryption extension + mockSecurityExt = extensionsMock.createSecurityExtension(); + + mockGetCurrentTime.mockReturnValue(mockTimestamp); + mockGetSearchDsl.mockClear(); + + repository = instantiateRepository(); + }); + + afterEach(() => { + mockSecurityExt.checkAuthorization.mockClear(); + mockSecurityExt.redactNamespaces.mockClear(); + }); + + describe('#get', () => { + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect( + getSuccess(client, repository, registry, type, id, { namespace }) + ).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + getSuccess(client, repository, registry, type, id, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await getSuccess(client, repository, registry, type, id, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.get).toHaveBeenCalledTimes(1); + expect(result).toEqual(expect.objectContaining({ type, id, namespaces: [namespace] })); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + await getSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, + id, + { + namespace, + }, + undefined, + multiNamespaceObjNamespaces // all of the object's namespaces from preflight check are added to the auth check call + ); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['get']; + const expectedSpaces = new Set(multiNamespaceObjNamespaces); + const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await getSuccess(client, repository, registry, type, id, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'get', + }) + ); + + const expectedTypesAndSpaces = new Map([[type, new Set([namespace])]]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + await getSuccess(client, repository, registry, type, id, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledWith( + expect.objectContaining({ + typeMap: authMap, + savedObject: expect.objectContaining({ type, id, namespaces: [namespace] }), + }) + ); + }); + + test(`adds audit event when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + await getSuccess(client, repository, registry, type, id, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.GET, + savedObject: { type, id }, + }); + }); + + test(`adds audit event when not successful`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + getSuccess(client, repository, registry, type, id, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.GET, + savedObject: { type, id }, + error: enforceError, + }); + }); + }); + + describe('#update', () => { + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect( + updateSuccess(client, repository, registry, type, id, attributes, { namespace }) + ).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + updateSuccess(client, repository, registry, type, id, attributes, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await updateSuccess(client, repository, registry, type, id, attributes, { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.update).toHaveBeenCalledTimes(1); + expect(result).toEqual( + expect.objectContaining({ id, type, attributes, namespaces: [namespace] }) + ); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + await updateSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, + id, + attributes, + { + namespace, + }, + {}, + multiNamespaceObjNamespaces // all of the object's namespaces from preflight check are added to the auth check call + ); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + + const expectedActions = ['update']; + const expectedSpaces = new Set(multiNamespaceObjNamespaces); + const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + mockSecurityExt.checkAuthorization.mockResolvedValue({ + status: 'fully_authorized', + typeMap: authMap, + }); + + await updateSuccess( + client, + repository, + registry, + type, + id, + attributes, + { namespace }, + undefined, + multiNamespaceObjNamespaces + ); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'update', + }) + ); + + const expectedTypesAndSpaces = new Map([[type, new Set([namespace])]]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + await updateSuccess(client, repository, registry, type, id, attributes, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledWith( + expect.objectContaining({ + typeMap: authMap, + savedObject: expect.objectContaining({ type, id, namespaces: [namespace] }), + }) + ); + }); + + test(`adds audit event when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + await updateSuccess(client, repository, registry, type, id, attributes, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE, + savedObject: { type, id }, + error: undefined, + outcome: 'unknown', + }); + }); + test(`adds audit event when not successful`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + updateSuccess(client, repository, registry, type, id, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE, + savedObject: { type, id }, + error: enforceError, + }); + }); + }); + + describe('#create', () => { + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect(repository.create(type, attributes, { namespace })).rejects.toThrow( + checkAuthError + ); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(repository.create(type, attributes, { namespace })).rejects.toThrow( + enforceError + ); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await repository.create(type, attributes, { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.create).toHaveBeenCalledTimes(1); + expect(result).toEqual( + expect.objectContaining({ + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + type, + attributes, + namespaces: [namespace], + }) + ); + }); + + test(`calls checkAuthorization with type, actions, and namespace`, async () => { + await repository.create(MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, attributes, { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['create']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls checkAuthorization with type, actions, namespace, and initial namespaces`, async () => { + await repository.create(MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, attributes, { + namespace, + initialNamespaces: multiNamespaceObjNamespaces, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['create']; + const expectedSpaces = new Set(multiNamespaceObjNamespaces); + const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await repository.create(MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, attributes, { + namespace, + initialNamespaces: multiNamespaceObjNamespaces, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'create', + }) + ); + + const expectedTypesAndSpaces = new Map([ + [MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, new Set(multiNamespaceObjNamespaces)], + ]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + await repository.create(type, attributes, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledWith( + expect.objectContaining({ + typeMap: authMap, + savedObject: expect.objectContaining({ + type, + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + namespaces: [namespace], + }), + }) + ); + }); + + test(`adds audit event when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + await repository.create(type, attributes, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.CREATE, + savedObject: { + type, + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + }, + error: undefined, + outcome: 'unknown', + }); + }); + + test(`adds audit event when not successful`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(repository.create(type, attributes, { namespace })).rejects.toThrow( + enforceError + ); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.CREATE, + savedObject: { + type, + id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/), + }, + error: enforceError, + }); + }); + }); + + describe('#delete', () => { + beforeAll(() => { + mockDeleteLegacyUrlAliases.mockResolvedValue(); + }); + + afterAll(() => { + mockDeleteLegacyUrlAliases.mockClear(); + }); + + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect( + deleteSuccess(client, repository, registry, type, id, { namespace }) + ).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + deleteSuccess(client, repository, registry, type, id, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns empty object result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await deleteSuccess(client, repository, registry, type, id, { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.delete).toHaveBeenCalledTimes(1); + expect(result).toEqual({}); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + await deleteSuccess(client, repository, registry, MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, id, { + namespace, + force: true, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['delete']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await deleteSuccess(client, repository, registry, MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, id, { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'delete', + }) + ); + const expectedTypesAndSpaces = new Map([ + [MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, new Set([namespace])], + ]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`adds audit event when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + await deleteSuccess(client, repository, registry, type, id, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.DELETE, + savedObject: { type, id }, + error: undefined, + outcome: 'unknown', + }); + }); + + test(`adds audit event when not successful`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + deleteSuccess(client, repository, registry, type, id, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.DELETE, + savedObject: { type, id }, + error: enforceError, + }); + }); + }); + + describe('#removeReferencesTo', () => { + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect( + removeReferencesToSuccess(client, repository, type, id, { namespace }) + ).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + removeReferencesToSuccess(client, repository, type, id, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await removeReferencesToSuccess(client, repository, type, id, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.updateByQuery).toHaveBeenCalledTimes(1); + expect(result).toEqual(expect.objectContaining({ updated: REMOVE_REFS_COUNT })); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + await removeReferencesToSuccess(client, repository, type, id, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['delete']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with type/namespace map`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await removeReferencesToSuccess(client, repository, type, id, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'delete', + }) + ); + + const expectedTypesAndSpaces = new Map([[type, new Set([namespace])]]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`adds audit event when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + await removeReferencesToSuccess(client, repository, type, id, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.REMOVE_REFERENCES, + savedObject: { type, id }, + error: undefined, + outcome: 'unknown', + }); + }); + + test(`adds audit event when not successful`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + removeReferencesToSuccess(client, repository, type, id, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.REMOVE_REFERENCES, + savedObject: { type, id }, + error: enforceError, + }); + }); + }); + + describe('#checkConflicts', () => { + const obj1 = { type, id: 'one' }; + const obj2 = { type, id: 'two' }; + + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect( + checkConflictsSuccess(client, repository, registry, [obj1, obj2], { namespace }) + ).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + checkConflictsSuccess(client, repository, registry, [obj1, obj2], { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await checkConflictsSuccess(client, repository, registry, [obj1, obj2], { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.mget).toHaveBeenCalledTimes(1); + // Default mock mget makes each object found + expect(result).toEqual( + expect.objectContaining({ + errors: [ + { + error: { + error: 'Conflict', + message: `Saved object [${obj1.type}/${obj1.id}] conflict`, + statusCode: 409, + }, + id: obj1.id, + type: obj1.type, + }, + { + error: { + error: 'Conflict', + message: `Saved object [${obj2.type}/${obj2.id}] conflict`, + statusCode: 409, + }, + id: obj2.id, + type: obj2.type, + }, + ], + }) + ); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + await checkConflictsSuccess(client, repository, registry, [obj1, obj2], { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['bulk_create']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with type/namespace map`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await checkConflictsSuccess(client, repository, registry, [obj1, obj2], { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'bulk_create', + }) + ); + const expectedTypesAndSpaces = new Map([[type, new Set([namespace])]]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + }); + + describe('#openPointInTimeForType', () => { + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect(repository.openPointInTimeForType(type)).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + + client.openPointInTime.mockResponseOnce({ id }); + const result = await repository.openPointInTimeForType(type); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + expect(client.openPointInTime).toHaveBeenCalledTimes(1); + expect(result).toEqual(expect.objectContaining({ id })); + }); + + test(`adds audit event when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + + client.openPointInTime.mockResponseOnce({ id }); + await repository.openPointInTimeForType(type); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.OPEN_POINT_IN_TIME, + outcome: 'unknown', + }); + }); + + test(`throws an error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + await expect(repository.openPointInTimeForType(type)).rejects.toThrowError(); + }); + + test(`adds audit event when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + + await expect(repository.openPointInTimeForType(type)).rejects.toThrowError(); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.OPEN_POINT_IN_TIME, + error: new Error('User is unauthorized for any requested types/spaces.'), + }); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + setupCheckAuthorized(mockSecurityExt); + client.openPointInTime.mockResponseOnce({ id }); + await repository.openPointInTimeForType(type, { namespaces: [namespace] }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['open_point_in_time']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + }); + + describe('#closePointInTime', () => { + test(`returns result of es client`, async () => { + const expectedResult = { succeeded: true, num_freed: 3 }; + client.closePointInTime.mockResponseOnce(expectedResult); + const result = await repository.closePointInTime(id); + expect(client.closePointInTime).toHaveBeenCalledTimes(1); + expect(result).toEqual(expectedResult); + }); + + test(`adds audit event`, async () => { + await repository.closePointInTime(id); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.CLOSE_POINT_IN_TIME, + outcome: 'unknown', + }); + }); + }); + + describe('#find', () => { + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect(findSuccess(client, repository, { type })).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`returns empty result when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + + const result = await repository.find({ type }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(result).toEqual( + expect.objectContaining({ + saved_objects: [], + total: 0, + }) + ); + }); + + test(`returns result of es find when fully authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const { result, generatedResults } = await findSuccess( + client, + repository, + { type }, + namespace + ); + const count = generatedResults.hits.hits.length; + + expect(result.total).toBe(count); + expect(result.saved_objects).toHaveLength(count); + + generatedResults.hits.hits.forEach((doc, i) => { + expect(result.saved_objects[i]).toEqual({ + id: doc._id.replace(/(foo-namespace\:)?(index-pattern|config|globalType)\:/, ''), + type: doc._source!.type, + originId: doc._source!.originId, + ...mockTimestampFields, + version: mockVersion, + score: doc._score, + attributes: doc._source![doc._source!.type], + references: [], + namespaces: doc._source!.type === NAMESPACE_AGNOSTIC_TYPE ? undefined : [namespace], + }); + }); + }); + + test(`returns result of es find when partially authorized`, async () => { + setupCheckPartiallyAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const { result, generatedResults } = await findSuccess( + client, + repository, + { type }, + namespace + ); + const count = generatedResults.hits.hits.length; + + expect(result.total).toBe(count); + expect(result.saved_objects).toHaveLength(count); + + generatedResults.hits.hits.forEach((doc, i) => { + expect(result.saved_objects[i]).toEqual({ + id: doc._id.replace(/(foo-namespace\:)?(index-pattern|config|globalType)\:/, ''), + type: doc._source!.type, + originId: doc._source!.originId, + ...mockTimestampFields, + version: mockVersion, + score: doc._score, + attributes: doc._source![doc._source!.type], + references: [], + namespaces: doc._source!.type === NAMESPACE_AGNOSTIC_TYPE ? undefined : [namespace], + }); + }); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + setupCheckPartiallyAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + await findSuccess(client, repository, { type, namespaces: [namespace] }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(2); + const expectedActions = ['find']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const { generatedResults } = await findSuccess(client, repository, { + type, + namespaces: [namespace], + }); + + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes( + generatedResults.hits.hits.length + ); + expect(mockSecurityExt.redactNamespaces).toBeCalledWith( + expect.objectContaining({ + typeMap: authMap, + }) + ); + }); + + test(`adds audit per object event when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const { generatedResults } = await findSuccess(client, repository, { + type, + namespaces: [namespace], + }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes( + generatedResults.hits.hits.length + ); + + generatedResults.hits.hits.forEach((doc, i) => { + expect(mockSecurityExt.addAuditEvent.mock.calls[i]).toEqual([ + { + action: AuditAction.FIND, + savedObject: { + type: doc._source!.type, + id: doc._id.replace(/(foo-namespace\:)?(index-pattern|config|globalType)\:/, ''), + }, + }, + ]); + }); + }); + + test(`adds audit event when not successful`, async () => { + setupCheckUnauthorized(mockSecurityExt); + + await repository.find({ type }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.FIND, + error: new Error('User is unauthorized for any requested types/spaces.'), + }); + }); + }); + + describe('#bulkGet', () => { + const obj1: SavedObject = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Testing' }, + references: [ + { + name: 'ref_0', + type: 'test', + id: '1', + }, + ], + namespaces: [namespace], + originId: 'some-origin-id', // only one of the results has an originId, this is intentional to test both a positive and negative case + }; + const obj2: SavedObject = { + type: 'index-pattern', + id: 'logstash-*', + attributes: { title: 'Testing' }, + references: [ + { + name: 'ref_0', + type: 'test', + id: '2', + }, + ], + namespaces: [namespace], + }; + + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect( + bulkGetSuccess(client, repository, registry, [obj1, obj2], { namespace }) + ).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + bulkGetSuccess(client, repository, registry, [obj1, obj2], { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'bulk_get', + }) + ); + + const expectedTypesAndSpaces = new Map([ + [obj1.type, new Set([namespace])], + [obj2.type, new Set([namespace])], + ]); + + const { typesAndSpaces: actualTypesAndSpaces } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const { result, mockResponse } = await bulkGetSuccess( + client, + repository, + registry, + [obj1, obj2], + { namespace } + ); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.mget).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + saved_objects: [ + expectBulkGetResult( + obj1, + mockResponse.docs[0] as estypes.GetGetResult + ), + expectBulkGetResult( + obj2, + mockResponse.docs[1] as estypes.GetGetResult + ), + ], + }); + }); + + test(`calls checkAuthorization with type, actions, namespace, and object namespaces`, async () => { + const objA = { + ...obj1, + type: MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, // replace the type to a mult-namespace type for this test to be thorough + namespaces: multiNamespaceObjNamespaces, // include multiple spaces + }; + const objB = { ...obj2, namespaces: ['ns-3'] }; // use a different namespace than the options namespace; + const optionsNamespace = 'ns-4'; + + await bulkGetSuccess(client, repository, registry, [objA, objB], { + namespace: optionsNamespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['bulk_get']; + const expectedSpaces = new Set([optionsNamespace, ...objA.namespaces, ...objB.namespaces]); + const expectedTypes = new Set([objA.type, objB.type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + const objA = { + ...obj1, + type: MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, // replace the type to a mult-namespace type for this test to be thorough + namespaces: multiNamespaceObjNamespaces, // include multiple spaces + }; + const objB = { ...obj2, namespaces: ['ns-3'] }; // use a different namespace than the options namespace; + const optionsNamespace = 'ns-4'; + + setupCheckAuthorized(mockSecurityExt); + + await bulkGetSuccess(client, repository, registry, [objA, objB], { + namespace: optionsNamespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'bulk_get', + }) + ); + + const expectedTypesAndSpaces = new Map([ + [objA.type, new Set([optionsNamespace, ...objA.namespaces])], + [objB.type, new Set([optionsNamespace, ...objB.namespaces])], + ]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); // ToDo? reference comparison ok, object is frozen + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const objects = [obj1, obj2]; + + await bulkGetSuccess(client, repository, registry, objects, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes(2); + + objects.forEach((obj, i) => { + const { savedObject, typeMap } = mockSecurityExt.redactNamespaces.mock.calls[i][0]; + expect(savedObject).toEqual( + expect.objectContaining({ + type: obj.type, + id: obj.id, + namespaces: [namespace], + }) + ); + expect(typeMap).toBe(authMap); + }); + }); + + test(`adds audit event per object when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + const objects = [obj1, obj2]; + await bulkGetSuccess(client, repository, registry, objects, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.GET, + savedObject: { type: obj.type, id: obj.id }, + }); + }); + }); + + test(`adds audit event per object when not successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + const objects = [obj1, obj2]; + await expect( + bulkGetSuccess(client, repository, registry, objects, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.GET, + savedObject: { type: obj.type, id: obj.id }, + error: enforceError, + }); + }); + }); + }); + + describe('#bulkCreate', () => { + beforeEach(() => { + mockPreflightCheckForCreate.mockReset(); + mockPreflightCheckForCreate.mockImplementation(({ objects }) => { + // eslint-disable-next-line @typescript-eslint/no-shadow + return Promise.resolve(objects.map(({ type, id }) => ({ type, id }))); // respond with no errors by default + }); + }); + + const obj1 = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }; + const obj2 = { + type: 'index-pattern', + id: 'logstash-*', + attributes: { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }; + + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect(bulkCreateSuccess(client, repository, [obj1, obj2])).rejects.toThrow( + checkAuthError + ); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + bulkCreateSuccess(client, repository, [obj1, obj2], { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const objects = [obj1, obj2]; + const result = await bulkCreateSuccess(client, repository, objects); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + saved_objects: objects.map((obj) => expectCreateResult(obj)), + }); + }); + + test(`calls checkAuthorization with type, actions, and namespace`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await bulkCreateSuccess(client, repository, [obj1, obj2], { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['bulk_create']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([obj1.type, obj2.type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls checkAuthorization with type, actions, namespace, and initial namespaces`, async () => { + const objA = { + ...obj1, + type: MULTI_NAMESPACE_TYPE, + initialNamespaces: ['ns-1', 'ns-2'], + }; + const objB = { + ...obj2, + type: MULTI_NAMESPACE_TYPE, + initialNamespaces: ['ns-3', 'ns-4'], + }; + const optionsNamespace = 'ns-5'; + + setupCheckAuthorized(mockSecurityExt); + + await bulkCreateSuccess(client, repository, [objA, objB], { + namespace: optionsNamespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + + const expectedActions = ['bulk_create']; + const expectedSpaces = new Set([ + optionsNamespace, + ...objA.initialNamespaces, + ...objB.initialNamespaces, + ]); + const expectedTypes = new Set([objA.type, objB.type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + const objA = { + ...obj1, + type: MULTI_NAMESPACE_TYPE, + initialNamespaces: ['ns-1', 'ns-2'], + }; + const objB = { + ...obj2, + type: MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, + initialNamespaces: ['ns-3', 'ns-4'], + }; + const optionsNamespace = 'ns-5'; + + setupCheckAuthorized(mockSecurityExt); + + await bulkCreateSuccess(client, repository, [objA, objB], { + namespace: optionsNamespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'bulk_create', + }) + ); + const expectedTypesAndSpaces = new Map([ + [objA.type, new Set([optionsNamespace, ...objA.initialNamespaces])], + [objB.type, new Set([optionsNamespace, ...objB.initialNamespaces])], + ]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const objects = [obj1, obj2]; + await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes(2); + + objects.forEach((obj, i) => { + const { savedObject, typeMap } = mockSecurityExt.redactNamespaces.mock.calls[i][0]; + expect(savedObject).toEqual( + expect.objectContaining({ + type: obj.type, + id: obj.id, + namespaces: [namespace], + }) + ); + expect(typeMap).toBe(authMap); + }); + }); + + test(`adds audit event per object when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + const objects = [obj1, obj2]; + await bulkCreateSuccess(client, repository, objects, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.CREATE, + savedObject: { type: obj.type, id: obj.id }, + outcome: 'unknown', + }); + }); + }); + + test(`adds audit event per object when not successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + const objects = [obj1, obj2]; + await expect(bulkCreateSuccess(client, repository, objects, { namespace })).rejects.toThrow( + enforceError + ); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.CREATE, + savedObject: { type: obj.type, id: obj.id }, + error: enforceError, + }); + }); + }); + }); + + describe('#bulkUpdate', () => { + const obj1: SavedObjectsBulkUpdateObject = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + }; + const obj2: SavedObjectsBulkUpdateObject = { + type: 'index-pattern', + id: 'logstash-*', + attributes: { title: 'Test Two' }, + }; + + test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect(bulkUpdateSuccess(client, repository, registry, [obj1, obj2])).rejects.toThrow( + checkAuthError + ); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propogates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(bulkUpdateSuccess(client, repository, registry, [obj1, obj2])).rejects.toThrow( + enforceError + ); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const objects = [obj1, obj2]; + const result = await bulkUpdateSuccess(client, repository, registry, objects); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + saved_objects: objects.map((obj) => expectUpdateResult(obj)), + }); + }); + + test(`calls checkAuthorization with type, actions, and namespace`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2], { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['bulk_update']; + const expectedSpaces = new Set([namespace]); + const expectedTypes = new Set([obj1.type, obj2.type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls checkAuthorization with type, actions, namespace, and object namespaces`, async () => { + const objA = { + ...obj1, + namespace: 'ns-1', // object namespace + }; + const objB = { + ...obj2, + namespace: 'ns-2', // object namespace + }; + + setupCheckAuthorized(mockSecurityExt); + + await bulkUpdateSuccess(client, repository, registry, [objA, objB], { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['bulk_update']; + const expectedSpaces = new Set([namespace, objA.namespace, objB.namespace]); + const expectedTypes = new Set([objA.type, objB.type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + const objA = { + ...obj1, + namespace: 'ns-1', // object namespace + }; + const objB = { + ...obj2, + namespace: 'ns-2', // object namespace + }; + + setupCheckAuthorized(mockSecurityExt); + + await bulkUpdateSuccess(client, repository, registry, [objA, objB], { + namespace, + }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'bulk_update', + }) + ); + + const expectedTypesAndSpaces = new Map([ + [objA.type, new Set([namespace, objA.namespace])], + [objB.type, new Set([namespace, objB.namespace])], + ]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`calls redactNamespaces with authorization map`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const objects = [obj1, obj2]; + await bulkUpdateSuccess(client, repository, registry, objects, { namespace }); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.redactNamespaces).toHaveBeenCalledTimes(2); + + objects.forEach((obj, i) => { + const { savedObject, typeMap } = mockSecurityExt.redactNamespaces.mock.calls[i][0]; + expect(savedObject).toEqual( + expect.objectContaining({ + type: obj.type, + id: obj.id, + namespaces: [namespace], + }) + ); + expect(typeMap).toBe(authMap); + }); + }); + + test(`adds audit event per object when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + const objects = [obj1, obj2]; + await bulkUpdateSuccess(client, repository, registry, objects, { namespace }); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE, + savedObject: { type: obj.type, id: obj.id }, + outcome: 'unknown', + }); + }); + }); + + test(`adds audit event per object when not successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + const objects = [obj1, obj2]; + await expect( + bulkUpdateSuccess(client, repository, registry, objects, { namespace }) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE, + savedObject: { type: obj.type, id: obj.id }, + error: enforceError, + }); + }); + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts new file mode 100644 index 00000000000000..b44c0db9410461 --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts @@ -0,0 +1,852 @@ +/* + * 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 { + pointInTimeFinderMock, + mockGetCurrentTime, + mockPreflightCheckForCreate, + mockUpdateObjectsSpaces, + mockGetSearchDsl, + mockCollectMultiNamespaceReferences, + mockInternalBulkResolve, +} from './repository.test.mock'; + +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; + +import { SavedObjectsRepository } from './repository'; +import { loggerMock } from '@kbn/logging-mocks'; +import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; +import { + SavedObjectsResolveResponse, + SavedObjectsBulkUpdateObject, +} from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { + ISavedObjectsSpacesExtension, + ISavedObjectsSecurityExtension, +} from '@kbn/core-saved-objects-server'; +import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; +import { kibanaMigratorMock } from '../mocks'; +import { extensionsMock } from './extensions.test.mock'; +import { + createRegistry, + createDocumentMigrator, + mappings, + DEFAULT_SPACE, + createSpySerializer, + mockTimestamp, + CUSTOM_INDEX_TYPE, + getMockGetResponse, + updateSuccess, + deleteSuccess, + removeReferencesToSuccess, + MULTI_NAMESPACE_ISOLATED_TYPE, + checkConflictsSuccess, + MULTI_NAMESPACE_TYPE, + bulkGetSuccess, + bulkCreateSuccess, + bulkUpdateSuccess, + findSuccess, + setupCheckUnauthorized, + generateIndexPatternSearchResults, +} from './repository.common.test'; + +// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository +// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. + +const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; + +describe('SavedObjectsRepository Spaces Extension', () => { + let client: ReturnType; + let repository: SavedObjectsRepository; + let migrator: ReturnType; + let logger: ReturnType; + let serializer: jest.Mocked; + let mockSpacesExt: jest.Mocked; + let mockSecurityExt: jest.Mocked; + + const registry = createRegistry(); + const documentMigrator = createDocumentMigrator(registry); + + // const currentSpace = 'foo-namespace'; + const defaultOptions = { ignore: [404], maxRetries: 0, meta: true }; // These are just the hard-coded options passed in via the repo + + const instantiateRepository = () => { + const allTypes = registry.getAllTypes().map((type) => type.name); + const allowedTypes = [...new Set(allTypes.filter((type) => !registry.isHidden(type)))]; + + // @ts-expect-error must use the private constructor to use the mocked serializer + return new SavedObjectsRepository({ + index: '.kibana-test', + mappings, + client, + migrator, + typeRegistry: registry, + serializer, + allowedTypes, + logger, + extensions: { spacesExtension: mockSpacesExt, securityExtension: mockSecurityExt }, + }); + }; + + const availableSpaces = [ + { id: 'default', name: '', disabledFeatures: [] }, + { id: 'ns-1', name: '', disabledFeatures: [] }, + { id: 'ns-2', name: '', disabledFeatures: [] }, + { id: 'ns-3', name: '', disabledFeatures: [] }, + { id: 'ns-4', name: '', disabledFeatures: [] }, + ]; + + [ + { id: DEFAULT_SPACE, expectedNamespace: undefined }, + { id: 'ns-1', expectedNamespace: 'ns-1' }, + ].forEach((currentSpace) => { + describe(`${currentSpace.id} space`, () => { + beforeEach(() => { + pointInTimeFinderMock.mockClear(); + client = elasticsearchClientMock.createElasticsearchClient(); + migrator = kibanaMigratorMock.create(); + documentMigrator.prepareMigrations(); + migrator.migrateDocument = jest.fn().mockImplementation(documentMigrator.migrate); + migrator.runMigrations = jest.fn().mockResolvedValue([{ status: 'skipped' }]); + logger = loggerMock.create(); + + // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation + serializer = createSpySerializer(registry); + + // create a mock saved objects spaces extension + mockSpacesExt = extensionsMock.createSpacesExtension(); + + mockGetCurrentTime.mockReturnValue(mockTimestamp); + mockGetSearchDsl.mockClear(); + + repository = instantiateRepository(); + + mockSpacesExt.getCurrentNamespace.mockImplementation((namespace: string | undefined) => { + if (namespace) { + throw SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED); + } + return currentSpace.expectedNamespace; + }); + + mockSpacesExt.getSearchableNamespaces.mockImplementation( + (namespaces: string[] | undefined): Promise => { + if (!namespaces) { + return Promise.resolve(['current-space'] as string[]); + } else if (!namespaces.length) { + return Promise.resolve(namespaces); + } + + if (namespaces?.includes('*')) { + return Promise.resolve(availableSpaces.map((space) => space.id)); + } else { + return Promise.resolve( + namespaces?.filter((namespace) => + availableSpaces.some((space) => space.id === namespace) + ) + ); + } + } + ); + }); + + describe('#get', () => { + test(`throws error if options.namespace is specified`, async () => { + // Just makes sure the error propagates from the extension through the repo call + await expect(repository.get('foo', '', { namespace: 'bar' })).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith('bar'); + }); + + test(`supplements id with the current namespace`, async () => { + const type = CUSTOM_INDEX_TYPE; + const id = 'some-id'; + + const response = getMockGetResponse(registry, { + type, + id, + }); + + client.get.mockResponseOnce(response); + await repository.get(type, id); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(client.get).toHaveBeenCalledTimes(1); + expect(client.get).toHaveBeenCalledWith( + expect.objectContaining({ + id: `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${type}:${id}`, + }), + defaultOptions + ); + }); + }); + + describe('#update', () => { + test(`throws error if options.namespace is specified`, async () => { + // Just makes sure the error propogstes from the extension through the repo call + await expect( + repository.update('foo', 'some-id', { attr: 'value' }, { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + const type = CUSTOM_INDEX_TYPE; + const id = 'some-id'; + + await updateSuccess( + client, + repository, + registry, + type, + id, + {}, + { upsert: true }, + { mockGetResponseValue: { found: false } as estypes.GetResponse } + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(client.update).toHaveBeenCalledTimes(1); + expect(client.update).toHaveBeenCalledWith( + expect.objectContaining({ + id: `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${type}:${id}`, + body: expect.objectContaining({ + upsert: expect.objectContaining( + currentSpace.expectedNamespace + ? { + namespace: currentSpace.expectedNamespace, + } + : {} + ), + }), + }), + { maxRetries: 0 } + ); + }); + }); + + describe('#create', () => { + test(`throws error if options.namespace is specified`, async () => { + await expect( + repository.create('foo', { attr: 'value' }, { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + const type = CUSTOM_INDEX_TYPE; + const attributes = { attr: 'value' }; + + await repository.create(type, { attr: 'value' }); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(client.create).toHaveBeenCalledTimes(1); + const regex = new RegExp( + `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${type}:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}` + ); + expect(client.create).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(regex), + body: expect.objectContaining( + currentSpace.expectedNamespace + ? { + namespace: currentSpace.expectedNamespace, + type: CUSTOM_INDEX_TYPE, + customIndex: attributes, + } + : { type: CUSTOM_INDEX_TYPE, customIndex: attributes } + ), + }), + { maxRetries: 0, meta: true } + ); + }); + }); + + describe('#delete', () => { + test(`throws error if options.namespace is specified`, async () => { + await expect( + repository.delete('foo', 'some-id', { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements id with the current namespace`, async () => { + const type = CUSTOM_INDEX_TYPE; + const id = 'some-id'; + + await deleteSuccess(client, repository, registry, type, id); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(client.delete).toHaveBeenCalledTimes(1); + const regex = new RegExp( + `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${type}:${id}` + ); + expect(client.delete).toHaveBeenCalledWith( + expect.objectContaining({ + id: expect.stringMatching(regex), + }), + { ignore: [404], maxRetries: 0, meta: true } + ); + }); + }); + + describe('#removeReferencesTo', () => { + test(`throws error if options.namespace is specified`, async () => { + await expect( + repository.removeReferencesTo('foo', 'some-id', { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + const type = CUSTOM_INDEX_TYPE; + const id = 'some-id'; + + const query = { query: 1, aggregations: 2 }; + mockGetSearchDsl.mockReturnValue(query); + + await removeReferencesToSuccess(client, repository, type, id); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(client.updateByQuery).toHaveBeenCalledTimes(1); + expect(mockGetSearchDsl).toHaveBeenCalledTimes(1); + expect(mockGetSearchDsl).toHaveBeenCalledWith( + mappings, + registry, + expect.objectContaining({ + namespaces: currentSpace.expectedNamespace + ? [currentSpace.expectedNamespace] + : undefined, + hasReference: { type, id }, + }) + ); + }); + }); + + describe('#checkConflicts', () => { + test(`throws error if options.namespace is specified`, async () => { + await expect( + repository.checkConflicts(undefined, { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + const obj1 = { type: CUSTOM_INDEX_TYPE, id: 'one' }; + const obj2 = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: 'two' }; + + await checkConflictsSuccess(client, repository, registry, [obj1, obj2]); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(client.mget).toHaveBeenCalledTimes(1); + expect(client.mget).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + docs: expect.arrayContaining([ + expect.objectContaining({ + _id: `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${obj1.type}:${obj1.id}`, + }), + expect.objectContaining({ + _id: `${obj2.type}:${obj2.id}`, + }), + ]), + }), + }), + { ignore: [404], maxRetries: 0, meta: true } + ); + }); + }); + + describe('#updateObjectSpaces', () => { + afterEach(() => { + mockUpdateObjectsSpaces.mockReset(); + }); + + test(`throws error if options.namespace is specified`, async () => { + await expect( + repository.updateObjectsSpaces([], [], [], { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + const obj1 = { type: CUSTOM_INDEX_TYPE, id: 'one' }; + const obj2 = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: 'two' }; + const spacesToAdd = ['space-x']; + const spacesToRemove = ['space-y']; + + await repository.updateObjectsSpaces([obj1, obj2], spacesToAdd, spacesToRemove); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockUpdateObjectsSpaces).toHaveBeenCalledTimes(1); + expect(mockUpdateObjectsSpaces).toHaveBeenCalledWith( + expect.objectContaining({ + objects: [obj1, obj2], + options: { + namespace: currentSpace.expectedNamespace + ? currentSpace.expectedNamespace + : undefined, + }, + }) + ); + }); + }); + + describe('#collectMultiNamespaceReferences', () => { + afterEach(() => { + mockCollectMultiNamespaceReferences.mockReset(); + }); + + test(`throws error if options.namespace is specified`, async () => { + await expect( + repository.collectMultiNamespaceReferences([], { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + const obj1 = { type: CUSTOM_INDEX_TYPE, id: 'one' }; + const obj2 = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: 'two' }; + + await repository.collectMultiNamespaceReferences([obj1, obj2]); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockCollectMultiNamespaceReferences).toHaveBeenCalledTimes(1); + expect(mockCollectMultiNamespaceReferences).toHaveBeenCalledWith( + expect.objectContaining({ + objects: [obj1, obj2], + options: { + namespace: currentSpace.expectedNamespace + ? currentSpace.expectedNamespace + : undefined, + }, + }) + ); + }); + }); + + describe('#openPointInTimeForType', () => { + test(`propogates options.namespaces: ['*']`, async () => { + await repository.openPointInTimeForType(CUSTOM_INDEX_TYPE, { namespaces: ['*'] }); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledTimes(1); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledWith(['*']); + }); + + test(`supplements options with the current namespace`, async () => { + await repository.openPointInTimeForType(CUSTOM_INDEX_TYPE); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledTimes(1); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledWith(undefined); // will resolve current space + }); + }); + + describe('#resolve', () => { + afterEach(() => { + mockInternalBulkResolve.mockReset(); + }); + + test(`throws error if options.namespace is specified`, async () => { + await expect( + repository.resolve('foo', 'some-id', { namespace: 'bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + const type = CUSTOM_INDEX_TYPE; + const id = 'some-id'; + + const expectedResult: SavedObjectsResolveResponse = { + saved_object: { type, id, attributes: {}, references: [] }, + outcome: 'exactMatch', + }; + mockInternalBulkResolve.mockResolvedValue({ resolved_objects: [expectedResult] }); + await repository.resolve(type, id); + expect(mockInternalBulkResolve).toHaveBeenCalledTimes(1); + expect(mockInternalBulkResolve).toHaveBeenCalledWith( + expect.objectContaining({ + objects: [{ type, id }], + options: { + namespace: currentSpace.expectedNamespace + ? currentSpace.expectedNamespace + : undefined, + }, + }) + ); + }); + }); + + describe('#bulkGet', () => { + const obj1: SavedObject = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Testing' }, + references: [ + { + name: 'ref_0', + type: 'test', + id: '1', + }, + ], + originId: 'some-origin-id', // only one of the results has an originId, this is intentional to test both a positive and negative case + }; + const obj2: SavedObject = { + type: MULTI_NAMESPACE_TYPE, + id: 'logstash-*', + attributes: { title: 'Testing' }, + references: [ + { + name: 'ref_0', + type: 'test', + id: '2', + }, + ], + }; + + test(`throws error if options.namespace is specified`, async () => { + await expect( + bulkGetSuccess(client, repository, registry, [obj1, obj2], { namespace: 'foo-bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('foo-bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + await bulkGetSuccess(client, repository, registry, [obj1, obj2]); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockSpacesExt.getSearchableNamespaces).not.toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalledTimes(1); + expect(client.mget).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + docs: expect.arrayContaining([ + expect.objectContaining({ + _id: `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${obj1.type}:${obj1.id}`, + }), + expect.objectContaining({ + _id: `${obj2.type}:${obj2.id}`, + }), + ]), + }), + }), + { ignore: [404], maxRetries: 0, meta: true } + ); + }); + + test(`calls getSearchableNamespaces with '*' when object namespaces includes '*'`, async () => { + await bulkGetSuccess(client, repository, registry, [ + obj1, + { ...obj2, namespaces: ['*'] }, + ]); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockSpacesExt.getSearchableNamespaces).toHaveBeenCalledTimes(1); + expect(mockSpacesExt.getSearchableNamespaces).toHaveBeenCalledWith(['*']); + }); + }); + + describe('#bulkCreate', () => { + beforeEach(() => { + mockPreflightCheckForCreate.mockReset(); + mockPreflightCheckForCreate.mockImplementation(({ objects }) => { + return Promise.resolve(objects.map(({ type, id }) => ({ type, id }))); // respond with no errors by default + }); + }); + + const obj1 = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + references: [{ name: 'ref_0', type: 'test', id: '1' }], + }; + const obj2 = { + type: MULTI_NAMESPACE_TYPE, + id: 'logstash-*', + attributes: { title: 'Test Two' }, + references: [{ name: 'ref_0', type: 'test', id: '2' }], + }; + + test(`throws error if options.namespace is specified`, async () => { + await expect( + bulkCreateSuccess(client, repository, [obj1, obj2], { namespace: 'foo-bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('foo-bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + await bulkCreateSuccess(client, repository, [obj1, obj2]); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockSpacesExt.getSearchableNamespaces).not.toHaveBeenCalled(); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + expect.objectContaining({ + create: expect.objectContaining({ + _id: `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${obj1.type}:${obj1.id}`, + }), + }), + expect.objectContaining({ + create: expect.objectContaining({ + _id: `${obj2.type}:${obj2.id}`, + }), + }), + ]), + }), + { maxRetries: 0 } + ); + }); + }); + + describe('#bulkUpdate', () => { + const obj1: SavedObjectsBulkUpdateObject = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + }; + const obj2: SavedObjectsBulkUpdateObject = { + type: MULTI_NAMESPACE_TYPE, + id: 'logstash-*', + attributes: { title: 'Test Two' }, + }; + + test(`throws error if options.namespace is specified`, async () => { + await expect( + bulkUpdateSuccess(client, repository, registry, [obj1, obj2], { namespace: 'foo-bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('foo-bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + await bulkUpdateSuccess( + client, + repository, + registry, + [obj1, obj2], + undefined, + undefined, + currentSpace.expectedNamespace + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockSpacesExt.getSearchableNamespaces).not.toHaveBeenCalled(); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + expect.objectContaining({ + update: expect.objectContaining({ + _id: `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${obj1.type}:${obj1.id}`, + }), + }), + expect.objectContaining({ + doc: expect.objectContaining({ + config: obj1.attributes, + }), + }), + expect.objectContaining({ + update: expect.objectContaining({ + _id: `${obj2.type}:${obj2.id}`, + }), + }), + expect.objectContaining({ + doc: expect.objectContaining({ + multiNamespaceType: obj2.attributes, + }), + }), + ]), + }), + { maxRetries: 0 } + ); + }); + }); + + describe('#bulkResolve', () => { + afterEach(() => { + mockInternalBulkResolve.mockReset(); + }); + + test(`throws error if options.namespace is specified`, async () => { + await expect(repository.bulkResolve([], { namespace: 'foo-bar' })).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('foo-bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + mockInternalBulkResolve.mockResolvedValue({ + resolved_objects: [ + { + saved_object: { type: 'mock', id: 'mock-object', attributes: {}, references: [] }, + outcome: 'exactMatch', + }, + { + type: 'obj-type', + id: 'obj-id-2', + error: SavedObjectsErrorHelpers.createGenericNotFoundError('obj-type', 'obj-id-2'), + }, + ], + }); + const objects = [ + { type: 'obj-type', id: 'obj-id-1' }, + { type: 'obj-type', id: 'obj-id-2' }, + ]; + await repository.bulkResolve(objects); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockSpacesExt.getSearchableNamespaces).not.toHaveBeenCalled(); + expect(mockInternalBulkResolve).toHaveBeenCalledTimes(1); + expect(mockInternalBulkResolve).toHaveBeenCalledWith( + expect.objectContaining({ + options: { + namespace: currentSpace.expectedNamespace + ? `${currentSpace.expectedNamespace}` + : undefined, + }, + }) + ); + }); + }); + + describe('#find', () => { + test(`supplements internal parameters with options.type and options.namespaces`, async () => { + const type = 'index-pattern'; + const spaceOverride = 'ns-4'; + await findSuccess(client, repository, { type, namespaces: [spaceOverride] }); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledTimes(1); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledWith([spaceOverride]); + expect(mockGetSearchDsl).toHaveBeenCalledWith( + mappings, + registry, + expect.objectContaining({ + namespaces: [spaceOverride], + type: [type], + }) + ); + }); + + test(`propogates options.namespaces: ['*']`, async () => { + const type = 'index-pattern'; + await findSuccess(client, repository, { type, namespaces: ['*'] }); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledTimes(1); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledWith(['*']); + }); + + test(`supplements options with the current namespace`, async () => { + const type = 'index-pattern'; + await findSuccess(client, repository, { type }); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledTimes(1); + expect(mockSpacesExt.getSearchableNamespaces).toBeCalledWith(undefined); // will resolve current space + }); + }); + }); + }); + + describe(`with security extension`, () => { + beforeEach(() => { + pointInTimeFinderMock.mockClear(); + client = elasticsearchClientMock.createElasticsearchClient(); + migrator = kibanaMigratorMock.create(); + documentMigrator.prepareMigrations(); + migrator.migrateDocument = jest.fn().mockImplementation(documentMigrator.migrate); + migrator.runMigrations = jest.fn().mockResolvedValue([{ status: 'skipped' }]); + logger = loggerMock.create(); + // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation + serializer = createSpySerializer(registry); + // create a mock extensions + mockSpacesExt = extensionsMock.createSpacesExtension(); + mockSecurityExt = extensionsMock.createSecurityExtension(); + mockGetCurrentTime.mockReturnValue(mockTimestamp); + mockGetSearchDsl.mockClear(); + repository = instantiateRepository(); + mockSpacesExt.getSearchableNamespaces.mockImplementation( + (namespaces: string[] | undefined): Promise => { + if (!namespaces) { + return Promise.resolve([] as string[]); + } else if (!namespaces.length) { + return Promise.resolve(namespaces); + } + if (namespaces?.includes('*')) { + return Promise.resolve(availableSpaces.map((space) => space.id)); + } else { + return Promise.resolve( + namespaces?.filter((namespace) => + availableSpaces.some((space) => space.id === namespace) + ) + ); + } + } + ); + }); + + describe(`#find`, () => { + test(`returns empty result if user is unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + const type = 'index-pattern'; + const spaceOverride = 'ns-4'; + const generatedResults = generateIndexPatternSearchResults(spaceOverride); + client.search.mockResponseOnce(generatedResults); + const result = await repository.find({ type, namespaces: [spaceOverride] }); + expect(result).toEqual(expect.objectContaining({ total: 0 })); + }); + }); + }); +}); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts index 9ca156605d6385..a9c1871e2488e1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts @@ -24,6 +24,7 @@ jest.mock('./collect_multi_namespace_references', () => ({ export const mockInternalBulkResolve = jest.fn() as jest.MockedFunction; jest.mock('./internal_bulk_resolve', () => ({ + ...jest.requireActual('./internal_bulk_resolve'), internalBulkResolve: mockInternalBulkResolve, })); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index d9a65f984c222c..c5cb46665a977c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -22,7 +22,7 @@ import { import type { Payload } from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { schema } from '@kbn/config-schema'; + import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-common'; import type { SavedObjectsBaseOptions, @@ -41,21 +41,15 @@ import type { SavedObjectsDeleteOptions, SavedObjectsOpenPointInTimeOptions, SavedObjectsResolveResponse, - SavedObjectsUpdateOptions, SavedObjectsCollectMultiNamespaceReferencesObject, SavedObjectsCollectMultiNamespaceReferencesResponse, SavedObjectsUpdateObjectsSpacesObject, SavedObjectsUpdateObjectsSpacesOptions, - SavedObjectsBulkDeleteObject, - SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; import type { - SavedObjectsType, SavedObjectsRawDoc, SavedObjectsRawDocSource, SavedObjectUnsanitizedDoc, - SavedObjectsMappingProperties, - SavedObjectsTypeMappingDefinition, } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers, @@ -65,7 +59,6 @@ import { SavedObjectsRepository } from './repository'; import { PointInTimeFinder } from './point_in_time_finder'; import { loggerMock } from '@kbn/logging-mocks'; import { - SavedObjectTypeRegistry, SavedObjectsSerializer, encodeHitVersion, LEGACY_URL_ALIAS_TYPE, @@ -76,319 +69,100 @@ import * as esKuery from '@kbn/es-query'; import { errors as EsErrors } from '@elastic/elasticsearch'; import { InternalBulkResolveError } from './internal_bulk_resolve'; +import { + KIBANA_VERSION, + CUSTOM_INDEX_TYPE, + NAMESPACE_AGNOSTIC_TYPE, + MULTI_NAMESPACE_TYPE, + MULTI_NAMESPACE_ISOLATED_TYPE, + HIDDEN_TYPE, + mockVersionProps, + mockTimestampFields, + mockTimestamp, + mappings, + mockVersion, + bulkGetSuccess, + createRegistry, + createDocumentMigrator, + getMockGetResponse, + getMockMgetResponse, + TypeIdTuple, + createSpySerializer, + bulkCreateSuccess, + bulkUpdateSuccess, + getMockBulkCreateResponse, + bulkGet, + getMockBulkUpdateResponse, + updateSuccess, + mockUpdateResponse, + expectErrorResult, + expectErrorInvalidType, + expectErrorNotFound, + expectErrorConflict, + expectError, + generateIndexPatternSearchResults, + findSuccess, + deleteSuccess, + removeReferencesToSuccess, + checkConflicts, + checkConflictsSuccess, + getSuccess, + createBadRequestErrorPayload, + createUnsupportedTypeErrorPayload, + createConflictErrorPayload, + createGenericNotFoundErrorPayload, + expectCreateResult, + expectUpdateResult, +} from './repository.common.test'; + const { nodeTypes } = esKuery; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. -interface TypeIdTuple { - id: string; - type: string; -} - interface ExpectedErrorResult { type: string; id: string; error: Record; } -type ErrorPayload = Error & Payload; - -const createBadRequestError = (reason?: string) => - SavedObjectsErrorHelpers.createBadRequestError(reason).output.payload as ErrorPayload; -const createConflictError = (type: string, id: string, reason?: string) => - SavedObjectsErrorHelpers.createConflictError(type, id, reason).output.payload as ErrorPayload; -const createGenericNotFoundError = (type: string | null = null, id: string | null = null) => - SavedObjectsErrorHelpers.createGenericNotFoundError(type, id).output.payload as ErrorPayload; -const createUnsupportedTypeError = (type: string) => - SavedObjectsErrorHelpers.createUnsupportedTypeError(type).output.payload as ErrorPayload; - describe('SavedObjectsRepository', () => { let client: ReturnType; - let savedObjectsRepository: SavedObjectsRepository; + let repository: SavedObjectsRepository; let migrator: ReturnType; let logger: ReturnType; let serializer: jest.Mocked; - const mockTimestamp = '2017-08-14T15:49:14.886Z'; - const mockTimestampFields = { updated_at: mockTimestamp }; - const mockVersionProps = { _seq_no: 1, _primary_term: 1 }; - const mockVersion = encodeHitVersion(mockVersionProps); - - const KIBANA_VERSION = '2.0.0'; - const CUSTOM_INDEX_TYPE = 'customIndex'; - /** This type has namespaceType: 'agnostic'. */ - const NAMESPACE_AGNOSTIC_TYPE = 'globalType'; - /** - * This type has namespaceType: 'multiple'. - * - * That means that the object is serialized with a globally unique ID across namespaces. It also means that the object is shareable across - * namespaces. - **/ - const MULTI_NAMESPACE_TYPE = 'multiNamespaceType'; - /** - * This type has namespaceType: 'multiple-isolated'. - * - * That means that the object is serialized with a globally unique ID across namespaces. It also means that the object is NOT shareable - * across namespaces. This distinction only matters when using the `collectMultiNamespaceReferences` or `updateObjectsSpaces` APIs, or - * when using the `initialNamespaces` argument with the `create` and `bulkCreate` APIs. Those allow you to define or change what - * namespaces an object exists in. - * - * In a nutshell, this type is more restrictive than `MULTI_NAMESPACE_TYPE`, so we use `MULTI_NAMESPACE_ISOLATED_TYPE` for any test cases - * where `MULTI_NAMESPACE_TYPE` would also satisfy the test case. - **/ - const MULTI_NAMESPACE_ISOLATED_TYPE = 'multiNamespaceIsolatedType'; - /** This type has namespaceType: 'multiple', and it uses a custom index. */ - const MULTI_NAMESPACE_CUSTOM_INDEX_TYPE = 'multiNamespaceTypeCustomIndex'; - const HIDDEN_TYPE = 'hiddenType'; - - const mappings: SavedObjectsTypeMappingDefinition = { - properties: { - config: { - properties: { - otherField: { - type: 'keyword', - }, - }, - }, - 'index-pattern': { - properties: { - someField: { - type: 'keyword', - }, - }, - }, - dashboard: { - properties: { - otherField: { - type: 'keyword', - }, - }, - }, - [CUSTOM_INDEX_TYPE]: { - properties: { - otherField: { - type: 'keyword', - }, - }, - }, - [NAMESPACE_AGNOSTIC_TYPE]: { - properties: { - yetAnotherField: { - type: 'keyword', - }, - }, - }, - [MULTI_NAMESPACE_TYPE]: { - properties: { - evenYetAnotherField: { - type: 'keyword', - }, - }, - }, - [MULTI_NAMESPACE_ISOLATED_TYPE]: { - properties: { - evenYetAnotherField: { - type: 'keyword', - }, - }, - }, - [MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]: { - properties: { - evenYetAnotherField: { - type: 'keyword', - }, - }, - }, - [HIDDEN_TYPE]: { - properties: { - someField: { - type: 'keyword', - }, - }, - }, - }, - }; - - const createType = (type: string, parts: Partial = {}): SavedObjectsType => ({ - name: type, - hidden: false, - namespaceType: 'single', - mappings: { - properties: mappings.properties[type].properties! as SavedObjectsMappingProperties, - }, - migrations: { '1.1.1': (doc) => doc }, - ...parts, - }); + const registry = createRegistry(); + const documentMigrator = createDocumentMigrator(registry); - const registry = new SavedObjectTypeRegistry(); - registry.registerType(createType('config')); - registry.registerType(createType('index-pattern')); - registry.registerType( - createType('dashboard', { - schemas: { - '8.0.0-testing': schema.object({ - title: schema.maybe(schema.string()), - otherField: schema.maybe(schema.string()), - }), - }, - }) - ); - registry.registerType(createType(CUSTOM_INDEX_TYPE, { indexPattern: 'custom' })); - registry.registerType(createType(NAMESPACE_AGNOSTIC_TYPE, { namespaceType: 'agnostic' })); - registry.registerType(createType(MULTI_NAMESPACE_TYPE, { namespaceType: 'multiple' })); - registry.registerType( - createType(MULTI_NAMESPACE_ISOLATED_TYPE, { namespaceType: 'multiple-isolated' }) - ); - registry.registerType( - createType(MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, { - namespaceType: 'multiple', - indexPattern: 'custom', - }) - ); - registry.registerType( - createType(HIDDEN_TYPE, { - hidden: true, - namespaceType: 'agnostic', - }) - ); - - const getMockGetResponse = ( - { - type, - id, - references, - namespace: objectNamespace, - originId, - }: { - type: string; - id: string; - namespace?: string; - originId?: string; - references?: SavedObjectReference[]; - }, - namespace?: string | string[] - ) => { - let namespaces; - if (objectNamespace) { - namespaces = [objectNamespace]; - } else if (namespace) { - namespaces = Array.isArray(namespace) ? namespace : [namespace]; - } else { - namespaces = ['default']; - } - const namespaceId = namespaces[0] === 'default' ? undefined : namespaces[0]; - - return { - // NOTE: Elasticsearch returns more fields (_index, _type) but the SavedObjectsRepository method ignores these - found: true, - _id: `${ - registry.isSingleNamespace(type) && namespaceId ? `${namespaceId}:` : '' - }${type}:${id}`, - ...mockVersionProps, - _source: { - ...(registry.isSingleNamespace(type) && { namespace: namespaceId }), - ...(registry.isMultiNamespace(type) && { namespaces }), - ...(originId && { originId }), - type, - [type]: { title: 'Testing' }, - references, - specialProperty: 'specialValue', - ...mockTimestampFields, - } as SavedObjectsRawDocSource, - } as estypes.GetResponse; - }; - - const getMockMgetResponse = ( - objects: Array, - namespace?: string - ) => - ({ - docs: objects.map((obj) => - obj.found === false ? obj : getMockGetResponse(obj, obj.initialNamespaces ?? namespace) - ), - } as estypes.MgetResponse); - - expect.extend({ - toBeDocumentWithoutError(received, type, id) { - if (received.type === type && received.id === id && !received.error) { - return { message: () => `expected type and id not to match without error`, pass: true }; - } else { - return { message: () => `expected type and id to match without error`, pass: false }; - } - }, - }); const expectSuccess = ({ type, id }: { type: string; id: string }) => { // @ts-expect-error TS is not aware of the extension return expect.toBeDocumentWithoutError(type, id); }; - const expectError = ({ type, id }: { type: string; id: string }) => ({ - type, - id, - error: expect.any(Object), - }); - - const expectErrorResult = ( - { type, id }: TypeIdTuple, - error: Record, - overrides: Record = {} - ): ExpectedErrorResult => ({ - type, - id, - error: { ...error, ...overrides }, - }); - const expectErrorNotFound = (obj: TypeIdTuple, overrides?: Record) => - expectErrorResult(obj, createGenericNotFoundError(obj.type, obj.id), overrides); - const expectErrorConflict = (obj: TypeIdTuple, overrides?: Record) => - expectErrorResult(obj, createConflictError(obj.type, obj.id), overrides); - const expectErrorInvalidType = (obj: TypeIdTuple, overrides?: Record) => - expectErrorResult(obj, createUnsupportedTypeError(obj.type), overrides); - const expectMigrationArgs = (args: unknown, contains = true, n = 1) => { const obj = contains ? expect.objectContaining(args) : expect.not.objectContaining(args); expect(migrator.migrateDocument).toHaveBeenNthCalledWith(n, obj); }; - const createSpySerializer = () => { - const spyInstance = { - isRawSavedObject: jest.fn(), - rawToSavedObject: jest.fn(), - savedObjectToRaw: jest.fn(), - generateRawId: jest.fn(), - generateRawLegacyUrlAliasId: jest.fn(), - trimIdPrefix: jest.fn(), - }; - const realInstance = new SavedObjectsSerializer(registry); - Object.keys(spyInstance).forEach((key) => { - // @ts-expect-error no proper way to do this with typing support - spyInstance[key].mockImplementation((...args) => realInstance[key](...args)); - }); - - return spyInstance as unknown as jest.Mocked; - }; - beforeEach(() => { pointInTimeFinderMock.mockClear(); client = elasticsearchClientMock.createElasticsearchClient(); migrator = kibanaMigratorMock.create(); - migrator.migrateDocument.mockImplementation((doc) => ({ - ...doc, - migrationVersion: { [doc.type]: '1.1.1' }, - coreMigrationVersion: KIBANA_VERSION, - })); - + documentMigrator.prepareMigrations(); + migrator.migrateDocument = jest.fn().mockImplementation(documentMigrator.migrate); migrator.runMigrations = jest.fn().mockResolvedValue([{ status: 'skipped' }]); logger = loggerMock.create(); // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation - serializer = createSpySerializer(); + serializer = createSpySerializer(registry); const allTypes = registry.getAllTypes().map((type) => type.name); const allowedTypes = [...new Set(allTypes.filter((type) => !registry.isHidden(type)))]; // @ts-expect-error must use the private constructor to use the mocked serializer - savedObjectsRepository = new SavedObjectsRepository({ + repository = new SavedObjectsRepository({ index: '.kibana-test', mappings, client, @@ -436,42 +210,6 @@ describe('SavedObjectsRepository', () => { }; const namespace = 'foo-namespace'; - const getMockBulkCreateResponse = ( - objects: SavedObjectsBulkCreateObject[], - namespace?: string - ) => { - return { - errors: false, - took: 1, - items: objects.map(({ type, id, originId, attributes, references, migrationVersion }) => ({ - create: { - // status: 1, - // _index: '.kibana', - _id: `${namespace ? `${namespace}:` : ''}${type}:${id}`, - _source: { - [type]: attributes, - type, - namespace, - ...(originId && { originId }), - references, - ...mockTimestampFields, - migrationVersion: migrationVersion || { [type]: '1.1.1' }, - }, - ...mockVersionProps, - }, - })), - } as unknown as estypes.BulkResponse; - }; - - const bulkCreateSuccess = async ( - objects: SavedObjectsBulkCreateObject[], - options?: SavedObjectsCreateOptions - ) => { - const response = getMockBulkCreateResponse(objects, options?.namespace); - client.bulk.mockResponse(response); - return await savedObjectsRepository.bulkCreate(objects, options); - }; - // bulk create calls have two objects for each source -- the action, and the source const expectClientCallArgsAction = ( objects: Array<{ type: string; id?: string; if_primary_term?: string; if_seq_no?: string }>, @@ -518,28 +256,15 @@ describe('SavedObjectsRepository', () => { }), ]; - const expectSuccessResult = (obj: { - type: string; - namespace?: string; - namespaces?: string[]; - }) => ({ - ...obj, - migrationVersion: { [obj.type]: '1.1.1' }, - coreMigrationVersion: KIBANA_VERSION, - version: mockVersion, - namespaces: obj.namespaces ?? [obj.namespace ?? 'default'], - ...mockTimestampFields, - }); - describe('client calls', () => { it(`should use the ES bulk action by default`, async () => { - await bulkCreateSuccess([obj1, obj2]); + await bulkCreateSuccess(client, repository, [obj1, obj2]); expect(client.bulk).toHaveBeenCalledTimes(1); }); it(`should use the preflightCheckForCreate action before bulk action for any types that are multi-namespace, when id is defined`, async () => { const objects = [obj1, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }]; - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); expect(client.bulk).toHaveBeenCalledTimes(1); expect(mockPreflightCheckForCreate).toHaveBeenCalledTimes(1); expect(mockPreflightCheckForCreate).toHaveBeenCalledWith( @@ -558,23 +283,25 @@ describe('SavedObjectsRepository', () => { it(`should use the ES create method if ID is undefined and overwrite=true`, async () => { const objects = [obj1, obj2].map((obj) => ({ ...obj, id: undefined })); - await bulkCreateSuccess(objects, { overwrite: true }); + await bulkCreateSuccess(client, repository, objects, { overwrite: true }); expectClientCallArgsAction(objects, { method: 'create' }); }); it(`should use the ES create method if ID is undefined and overwrite=false`, async () => { const objects = [obj1, obj2].map((obj) => ({ ...obj, id: undefined })); - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); expectClientCallArgsAction(objects, { method: 'create' }); }); it(`should use the ES index method if ID is defined and overwrite=true`, async () => { - await bulkCreateSuccess([obj1, obj2], { overwrite: true }); + await bulkCreateSuccess(client, repository, [obj1, obj2], { overwrite: true }); expectClientCallArgsAction([obj1, obj2], { method: 'index' }); }); it(`should use the ES index method with version if ID and version are defined and overwrite=true`, async () => { await bulkCreateSuccess( + client, + repository, [ { ...obj1, @@ -595,12 +322,12 @@ describe('SavedObjectsRepository', () => { }); it(`should use the ES create method if ID is defined and overwrite=false`, async () => { - await bulkCreateSuccess([obj1, obj2]); + await bulkCreateSuccess(client, repository, [obj1, obj2]); expectClientCallArgsAction([obj1, obj2], { method: 'create' }); }); it(`formats the ES request`, async () => { - await bulkCreateSuccess([obj1, obj2]); + await bulkCreateSuccess(client, repository, [obj1, obj2]); const body = [...expectObjArgs(obj1), ...expectObjArgs(obj2)]; expect(client.bulk).toHaveBeenCalledWith( expect.objectContaining({ body }), @@ -610,7 +337,7 @@ describe('SavedObjectsRepository', () => { describe('originId', () => { it(`returns error if originId is set for non-multi-namespace type`, async () => { - const result = await savedObjectsRepository.bulkCreate([ + const result = await repository.bulkCreate([ { ...obj1, originId: 'some-originId' }, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE, originId: 'some-originId' }, ]); @@ -631,7 +358,7 @@ describe('SavedObjectsRepository', () => { { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }, ]; - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); const expected = expect.not.objectContaining({ originId: expect.anything() }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -658,7 +385,7 @@ describe('SavedObjectsRepository', () => { { ...obj1, type: MULTI_NAMESPACE_TYPE, originId: 'some-originId' }, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE, originId: 'some-originId' }, ]; - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); const expected = expect.objectContaining({ originId: 'some-originId' }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -673,7 +400,7 @@ describe('SavedObjectsRepository', () => { { ...obj1, type: MULTI_NAMESPACE_TYPE, originId: undefined }, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE, originId: undefined }, ]; - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); const expected = expect.not.objectContaining({ originId: expect.anything() }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -687,7 +414,7 @@ describe('SavedObjectsRepository', () => { { ...obj1, type: MULTI_NAMESPACE_TYPE }, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }, ]; - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); const expected = expect.objectContaining({ originId: 'existing-originId' }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -699,7 +426,7 @@ describe('SavedObjectsRepository', () => { }); it(`adds namespace to request body for any types that are single-namespace`, async () => { - await bulkCreateSuccess([obj1, obj2], { namespace }); + await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace }); const expected = expect.objectContaining({ namespace }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -709,7 +436,7 @@ describe('SavedObjectsRepository', () => { }); it(`normalizes options.namespace from 'default' to undefined`, async () => { - await bulkCreateSuccess([obj1, obj2], { namespace: 'default' }); + await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace: 'default' }); const expected = expect.not.objectContaining({ namespace: 'default' }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -723,7 +450,7 @@ describe('SavedObjectsRepository', () => { { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }, ]; - await bulkCreateSuccess(objects, { namespace }); + await bulkCreateSuccess(client, repository, objects, { namespace }); const expected = expect.not.objectContaining({ namespace: expect.anything() }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -744,7 +471,7 @@ describe('SavedObjectsRepository', () => { existingDocument: { _id: o2.id!, _source: { namespaces: ['*'], type: o2.type } }, // second object does have an existing document to overwrite }, ]); - await bulkCreateSuccess(objects, { namespace, overwrite: true }); + await bulkCreateSuccess(client, repository, objects, { namespace, overwrite: true }); const expected1 = expect.objectContaining({ namespaces: [namespace ?? 'default'] }); const expected2 = expect.objectContaining({ namespaces: ['*'] }); const body = [expect.any(Object), expected1, expect.any(Object), expected2]; @@ -781,7 +508,7 @@ describe('SavedObjectsRepository', () => { }, }, ]); - await bulkCreateSuccess(objects, { namespace, overwrite: true }); + await bulkCreateSuccess(client, repository, objects, { namespace, overwrite: true }); const body = [ { index: expect.objectContaining({ _id: `${ns2}:dashboard:${o1.id}` }) }, expect.objectContaining({ namespace: ns2 }), @@ -817,7 +544,7 @@ describe('SavedObjectsRepository', () => { it(`normalizes initialNamespaces from 'default' to undefined`, async () => { const test = async (namespace?: string) => { const objects = [{ ...obj1, type: 'dashboard', initialNamespaces: ['default'] }]; - await bulkCreateSuccess(objects, { namespace, overwrite: true }); + await bulkCreateSuccess(client, repository, objects, { namespace, overwrite: true }); const body = [ { index: expect.objectContaining({ _id: `dashboard:${obj1.id}` }) }, expect.not.objectContaining({ namespace: 'default' }), @@ -835,7 +562,7 @@ describe('SavedObjectsRepository', () => { it(`doesn't add namespaces to request body for any types that are not multi-namespace`, async () => { const test = async (namespace?: string) => { const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; - await bulkCreateSuccess(objects, { namespace, overwrite: true }); + await bulkCreateSuccess(client, repository, objects, { namespace, overwrite: true }); const expected = expect.not.objectContaining({ namespaces: expect.anything() }); const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -849,7 +576,7 @@ describe('SavedObjectsRepository', () => { }); it(`defaults to a refresh setting of wait_for`, async () => { - await bulkCreateSuccess([obj1, obj2]); + await bulkCreateSuccess(client, repository, [obj1, obj2]); expect(client.bulk).toHaveBeenCalledWith( expect.objectContaining({ refresh: 'wait_for' }), expect.anything() @@ -857,7 +584,7 @@ describe('SavedObjectsRepository', () => { }); it(`should use default index`, async () => { - await bulkCreateSuccess([obj1, obj2]); + await bulkCreateSuccess(client, repository, [obj1, obj2]); expectClientCallArgsAction([obj1, obj2], { method: 'create', _index: '.kibana-test_8.0.0-testing', @@ -865,7 +592,11 @@ describe('SavedObjectsRepository', () => { }); it(`should use custom index`, async () => { - await bulkCreateSuccess([obj1, obj2].map((x) => ({ ...x, type: CUSTOM_INDEX_TYPE }))); + await bulkCreateSuccess( + client, + repository, + [obj1, obj2].map((x) => ({ ...x, type: CUSTOM_INDEX_TYPE })) + ); expectClientCallArgsAction([obj1, obj2], { method: 'create', _index: 'custom_8.0.0-testing', @@ -874,13 +605,13 @@ describe('SavedObjectsRepository', () => { it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { const getId = (type: string, id: string = '') => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) - await bulkCreateSuccess([obj1, obj2], { namespace }); + await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace }); expectClientCallArgsAction([obj1, obj2], { method: 'create', getId }); }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { const getId = (type: string, id: string = '') => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) - await bulkCreateSuccess([obj1, obj2]); + await bulkCreateSuccess(client, repository, [obj1, obj2]); expectClientCallArgsAction([obj1, obj2], { method: 'create', getId }); }); @@ -890,7 +621,7 @@ describe('SavedObjectsRepository', () => { { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }, ]; - await bulkCreateSuccess(objects, { namespace }); + await bulkCreateSuccess(client, repository, objects, { namespace }); expectClientCallArgsAction(objects, { method: 'create', getId }); }); }); @@ -924,7 +655,7 @@ describe('SavedObjectsRepository', () => { client.bulk.mockResponseOnce(response); const objects = [obj1, obj, obj2]; - const result = await savedObjectsRepository.bulkCreate(objects); + const result = await repository.bulkCreate(objects); expect(client.bulk).toHaveBeenCalled(); const objCall = isBulkError ? expectObjArgs(obj) : []; const body = [...expectObjArgs(obj1), ...objCall, ...expectObjArgs(obj2)]; @@ -939,8 +670,8 @@ describe('SavedObjectsRepository', () => { it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.bulkCreate([obj3], { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.bulkCreate([obj3], { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`returns error when initialNamespaces is used with a space-agnostic object`, async () => { @@ -950,7 +681,9 @@ describe('SavedObjectsRepository', () => { undefined, expectErrorResult( obj, - createBadRequestError('"initialNamespaces" cannot be used on space-agnostic types') + createBadRequestErrorPayload( + '"initialNamespaces" cannot be used on space-agnostic types' + ) ) ); }); @@ -962,7 +695,7 @@ describe('SavedObjectsRepository', () => { undefined, expectErrorResult( obj, - createBadRequestError('"initialNamespaces" must be a non-empty array of strings') + createBadRequestErrorPayload('"initialNamespaces" must be a non-empty array of strings') ) ); }); @@ -975,7 +708,7 @@ describe('SavedObjectsRepository', () => { undefined, expectErrorResult( obj, - createBadRequestError( + createBadRequestErrorPayload( '"initialNamespaces" can only specify a single space when used with space-isolated types' ) ) @@ -1025,7 +758,7 @@ describe('SavedObjectsRepository', () => { client.bulk.mockResponseOnce(bulkResponse); const options = { overwrite: true }; - const result = await savedObjectsRepository.bulkCreate(objects, options); + const result = await repository.bulkCreate(objects, options); expect(mockPreflightCheckForCreate).toHaveBeenCalled(); expect(mockPreflightCheckForCreate).toHaveBeenCalledWith( expect.objectContaining({ @@ -1065,7 +798,7 @@ describe('SavedObjectsRepository', () => { const response = getMockBulkCreateResponse([obj3]); client.bulk.mockResponseOnce(response); - const result = await savedObjectsRepository.bulkCreate([ + const result = await repository.bulkCreate([ obj3, // @ts-expect-error - Title should be a string and is intentionally malformed for testing { ...obj3, id: 'three-again', attributes: { title: 123 } }, @@ -1088,7 +821,7 @@ describe('SavedObjectsRepository', () => { it(`migrates the docs and serializes the migrated docs`, async () => { migrator.migrateDocument.mockImplementation(mockMigrateDocument); const modifiedObj1 = { ...obj1, coreMigrationVersion: '8.0.0' }; - await bulkCreateSuccess([modifiedObj1, obj2]); + await bulkCreateSuccess(client, repository, [modifiedObj1, obj2]); const docs = [modifiedObj1, obj2].map((x) => ({ ...x, ...mockTimestampFields })); expectMigrationArgs(docs[0], true, 1); expectMigrationArgs(docs[1], true, 2); @@ -1099,13 +832,13 @@ describe('SavedObjectsRepository', () => { }); it(`adds namespace to body when providing namespace for single-namespace type`, async () => { - await bulkCreateSuccess([obj1, obj2], { namespace }); + await bulkCreateSuccess(client, repository, [obj1, obj2], { namespace }); expectMigrationArgs({ namespace }, true, 1); expectMigrationArgs({ namespace }, true, 2); }); it(`doesn't add namespace to body when providing no namespace for single-namespace type`, async () => { - await bulkCreateSuccess([obj1, obj2]); + await bulkCreateSuccess(client, repository, [obj1, obj2]); expectMigrationArgs({ namespace: expect.anything() }, false, 1); expectMigrationArgs({ namespace: expect.anything() }, false, 2); }); @@ -1115,7 +848,7 @@ describe('SavedObjectsRepository', () => { { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }, ]; - await bulkCreateSuccess(objects, { namespace }); + await bulkCreateSuccess(client, repository, objects, { namespace }); expectMigrationArgs({ namespace: expect.anything() }, false, 1); expectMigrationArgs({ namespace: expect.anything() }, false, 2); }); @@ -1125,7 +858,7 @@ describe('SavedObjectsRepository', () => { ...obj, type: MULTI_NAMESPACE_ISOLATED_TYPE, })); - await bulkCreateSuccess(objects, { namespace }); + await bulkCreateSuccess(client, repository, objects, { namespace }); expectMigrationArgs({ namespaces: [namespace] }, true, 1); expectMigrationArgs({ namespaces: [namespace] }, true, 2); }); @@ -1135,14 +868,14 @@ describe('SavedObjectsRepository', () => { ...obj, type: MULTI_NAMESPACE_ISOLATED_TYPE, })); - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); expectMigrationArgs({ namespaces: ['default'] }, true, 1); expectMigrationArgs({ namespaces: ['default'] }, true, 2); }); it(`doesn't add namespaces to body when not using multi-namespace type`, async () => { const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; - await bulkCreateSuccess(objects); + await bulkCreateSuccess(client, repository, objects); expectMigrationArgs({ namespaces: expect.anything() }, false, 1); expectMigrationArgs({ namespaces: expect.anything() }, false, 2); }); @@ -1150,9 +883,9 @@ describe('SavedObjectsRepository', () => { describe('returns', () => { it(`formats the ES response`, async () => { - const result = await bulkCreateSuccess([obj1, obj2]); + const result = await bulkCreateSuccess(client, repository, [obj1, obj2]); expect(result).toEqual({ - saved_objects: [obj1, obj2].map((x) => expectSuccessResult(x)), + saved_objects: [obj1, obj2].map((x) => expectCreateResult(x)), }); }); @@ -1167,10 +900,10 @@ describe('SavedObjectsRepository', () => { const objects = [obj1, obj, obj2]; const response = getMockBulkCreateResponse([obj1, obj2]); client.bulk.mockResponseOnce(response); - const result = await savedObjectsRepository.bulkCreate(objects); + const result = await repository.bulkCreate(objects); expect(client.bulk).toHaveBeenCalledTimes(1); expect(result).toEqual({ - saved_objects: [expectSuccessResult(obj1), expectError(obj), expectSuccessResult(obj2)], + saved_objects: [expectCreateResult(obj1), expectError(obj), expectCreateResult(obj2)], }); }); @@ -1185,7 +918,7 @@ describe('SavedObjectsRepository', () => { client.bulk.mockResponseOnce(response); // Bulk create one object with id unspecified, and one with id specified - const result = await savedObjectsRepository.bulkCreate([{ ...obj1, id: undefined }, obj2], { + const result = await repository.bulkCreate([{ ...obj1, id: undefined }, obj2], { namespace, }); @@ -1245,22 +978,6 @@ describe('SavedObjectsRepository', () => { }; const namespace = 'foo-namespace'; - const bulkGet = async ( - objects: SavedObjectsBulkGetObject[], - options?: SavedObjectsBaseOptions - ) => - savedObjectsRepository.bulkGet( - objects.map(({ type, id, namespaces }) => ({ type, id, namespaces })), // bulkGet only uses type, id, and optionally namespaces - options - ); - const bulkGetSuccess = async (objects: SavedObject[], options?: SavedObjectsBaseOptions) => { - const response = getMockMgetResponse(objects, options?.namespace); - client.mget.mockResponseOnce(response); - const result = await bulkGet(objects, options); - expect(client.mget).toHaveBeenCalledTimes(1); - return result; - }; - const _expectClientCallArgs = ( objects: TypeIdTuple[], { @@ -1286,33 +1003,33 @@ describe('SavedObjectsRepository', () => { describe('client calls', () => { it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { const getId = (type: string, id: string) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) - await bulkGetSuccess([obj1, obj2], { namespace }); + await bulkGetSuccess(client, repository, registry, [obj1, obj2], { namespace }); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`prepends namespace to the id when providing namespaces for single-namespace type`, async () => { const getId = (type: string, id: string) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) const objects = [obj1, obj2].map((obj) => ({ ...obj, namespaces: [namespace] })); - await bulkGetSuccess(objects, { namespace: 'some-other-ns' }); + await bulkGetSuccess(client, repository, registry, objects, { namespace: 'some-other-ns' }); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) - await bulkGetSuccess([obj1, obj2]); + await bulkGetSuccess(client, repository, registry, [obj1, obj2]); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`normalizes options.namespace from 'default' to undefined`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) - await bulkGetSuccess([obj1, obj2], { namespace: 'default' }); + await bulkGetSuccess(client, repository, registry, [obj1, obj2], { namespace: 'default' }); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) let objects = [obj1, obj2].map((obj) => ({ ...obj, type: NAMESPACE_AGNOSTIC_TYPE })); - await bulkGetSuccess(objects, { namespace }); + await bulkGetSuccess(client, repository, registry, objects, { namespace }); _expectClientCallArgs(objects, { getId }); client.mget.mockClear(); @@ -1320,7 +1037,7 @@ describe('SavedObjectsRepository', () => { ...obj, type: MULTI_NAMESPACE_ISOLATED_TYPE, })); - await bulkGetSuccess(objects, { namespace }); + await bulkGetSuccess(client, repository, registry, objects, { namespace }); _expectClientCallArgs(objects, { getId }); }); }); @@ -1336,16 +1053,16 @@ describe('SavedObjectsRepository', () => { // mock the bulk error for only the second object mockGetBulkOperationError.mockReturnValueOnce(undefined); mockGetBulkOperationError.mockReturnValueOnce(expectedErrorResult.error as Payload); - response = getMockMgetResponse([obj1, obj, obj2]); + response = getMockMgetResponse(registry, [obj1, obj, obj2]); } else { - response = getMockMgetResponse([obj1, obj2]); + response = getMockMgetResponse(registry, [obj1, obj2]); } client.mget.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); const objects = [obj1, obj, obj2]; - const result = await bulkGet(objects); + const result = await bulkGet(repository, objects); expect(client.mget).toHaveBeenCalled(); expect(result).toEqual({ saved_objects: [expectSuccess(obj1), expectedErrorResult, expectSuccess(obj2)], @@ -1355,8 +1072,8 @@ describe('SavedObjectsRepository', () => { it(`throws when options.namespace is '*'`, async () => { const obj = { type: 'dashboard', id: 'three' }; await expect( - savedObjectsRepository.bulkGet([obj], { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.bulkGet([obj], { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`returns error when namespaces is used with a space-agnostic object`, async () => { @@ -1366,7 +1083,7 @@ describe('SavedObjectsRepository', () => { false, expectErrorResult( obj, - createBadRequestError('"namespaces" cannot be used on space-agnostic types') + createBadRequestErrorPayload('"namespaces" cannot be used on space-agnostic types') ) ); }); @@ -1379,7 +1096,7 @@ describe('SavedObjectsRepository', () => { false, expectErrorResult( obj, - createBadRequestError( + createBadRequestErrorPayload( '"namespaces" can only specify a single space when used with space-isolated types' ) ) @@ -1447,17 +1164,17 @@ describe('SavedObjectsRepository', () => { }); it(`returns early for empty objects argument`, async () => { - const result = await bulkGet([]); + const result = await bulkGet(repository, []); expect(result).toEqual({ saved_objects: [] }); expect(client.mget).not.toHaveBeenCalled(); }); it(`formats the ES response`, async () => { - const response = getMockMgetResponse([obj1, obj2]); + const response = getMockMgetResponse(registry, [obj1, obj2]); client.mget.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); - const result = await bulkGet([obj1, obj2]); + const result = await bulkGet(repository, [obj1, obj2]); expect(client.mget).toHaveBeenCalledTimes(1); expect(result).toEqual({ saved_objects: [ @@ -1474,7 +1191,7 @@ describe('SavedObjectsRepository', () => { }); it(`handles a mix of successful gets and errors`, async () => { - const response = getMockMgetResponse([obj1, obj2]); + const response = getMockMgetResponse(registry, [obj1, obj2]); client.mget.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -1484,7 +1201,7 @@ describe('SavedObjectsRepository', () => { attributes: {}, references: [], }; - const result = await bulkGet([obj1, obj, obj2]); + const result = await bulkGet(repository, [obj1, obj, obj2]); expect(client.mget).toHaveBeenCalledTimes(1); expect(result).toEqual({ saved_objects: [ @@ -1508,7 +1225,7 @@ describe('SavedObjectsRepository', () => { attributes: {}, references: [], }; - const result = await bulkGetSuccess([obj1, obj]); + const { result } = await bulkGetSuccess(client, repository, registry, [obj1, obj]); expect(result).toEqual({ saved_objects: [ expect.objectContaining({ namespaces: ['default'] }), @@ -1543,7 +1260,7 @@ describe('SavedObjectsRepository', () => { { type: 'obj-type', id: 'obj-id-1' }, { type: 'obj-type', id: 'obj-id-2' }, ]; - await expect(savedObjectsRepository.bulkResolve(objects)).resolves.toEqual({ + await expect(repository.bulkResolve(objects)).resolves.toEqual({ resolved_objects: [ { saved_object: { type: 'mock', id: 'mock-object', attributes: {}, references: [] }, @@ -1571,7 +1288,7 @@ describe('SavedObjectsRepository', () => { const error = new Error('Oh no!'); mockInternalBulkResolve.mockRejectedValue(error); - await expect(savedObjectsRepository.resolve('some-type', 'some-id')).rejects.toEqual(error); + await expect(repository.resolve('some-type', 'some-id')).rejects.toEqual(error); }); }); @@ -1590,47 +1307,6 @@ describe('SavedObjectsRepository', () => { const originId = 'some-origin-id'; const namespace = 'foo-namespace'; - const getMockBulkUpdateResponse = ( - objects: TypeIdTuple[], - options?: SavedObjectsBulkUpdateOptions, - includeOriginId?: boolean - ) => - ({ - items: objects.map(({ type, id }) => ({ - update: { - _id: `${ - registry.isSingleNamespace(type) && options?.namespace ? `${options?.namespace}:` : '' - }${type}:${id}`, - ...mockVersionProps, - get: { - _source: { - // "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the - // operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response. - ...(includeOriginId && { originId }), - }, - }, - result: 'updated', - }, - })), - } as estypes.BulkResponse); - - const bulkUpdateSuccess = async ( - objects: SavedObjectsBulkUpdateObject[], - options?: SavedObjectsBulkUpdateOptions, - includeOriginId?: boolean - ) => { - const multiNamespaceObjects = objects.filter(({ type }) => registry.isMultiNamespace(type)); - if (multiNamespaceObjects?.length) { - const response = getMockMgetResponse(multiNamespaceObjects, options?.namespace); - client.mget.mockResponseOnce(response); - } - const response = getMockBulkUpdateResponse(objects, options, includeOriginId); - client.bulk.mockResponseOnce(response); - const result = await savedObjectsRepository.bulkUpdate(objects, options); - expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0); - return result; - }; - // bulk create calls have two objects for each source -- the action, and the source const expectClientCallArgsAction = ( objects: TypeIdTuple[], @@ -1675,13 +1351,13 @@ describe('SavedObjectsRepository', () => { describe('client calls', () => { it(`should use the ES bulk action by default`, async () => { - await bulkUpdateSuccess([obj1, obj2]); + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2]); expect(client.bulk).toHaveBeenCalled(); }); it(`should use the ES mget action before bulk action for any types that are multi-namespace`, async () => { const objects = [obj1, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }]; - await bulkUpdateSuccess(objects); + await bulkUpdateSuccess(client, repository, registry, objects); expect(client.bulk).toHaveBeenCalled(); expect(client.mget).toHaveBeenCalled(); @@ -1695,7 +1371,7 @@ describe('SavedObjectsRepository', () => { }); it(`formats the ES request`, async () => { - await bulkUpdateSuccess([obj1, obj2]); + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2]); const body = [...expectObjArgs(obj1), ...expectObjArgs(obj2)]; expect(client.bulk).toHaveBeenCalledWith( expect.objectContaining({ body }), @@ -1705,7 +1381,7 @@ describe('SavedObjectsRepository', () => { it(`formats the ES request for any types that are multi-namespace`, async () => { const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - await bulkUpdateSuccess([obj1, _obj2]); + await bulkUpdateSuccess(client, repository, registry, [obj1, _obj2]); const body = [...expectObjArgs(obj1), ...expectObjArgs(_obj2)]; expect(client.bulk).toHaveBeenCalledWith( expect.objectContaining({ body }), @@ -1715,12 +1391,12 @@ describe('SavedObjectsRepository', () => { it(`doesnt call Elasticsearch if there are no valid objects to update`, async () => { const objects = [obj1, obj2].map((x) => ({ ...x, type: 'unknownType' })); - await savedObjectsRepository.bulkUpdate(objects); + await repository.bulkUpdate(objects); expect(client.bulk).toHaveBeenCalledTimes(0); }); it(`defaults to no references`, async () => { - await bulkUpdateSuccess([obj1, obj2]); + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2]); const expected = { doc: expect.not.objectContaining({ references: expect.anything() }) }; const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -1732,7 +1408,7 @@ describe('SavedObjectsRepository', () => { it(`accepts custom references array`, async () => { const test = async (references: SavedObjectReference[]) => { const objects = [obj1, obj2].map((obj) => ({ ...obj, references })); - await bulkUpdateSuccess(objects); + await bulkUpdateSuccess(client, repository, registry, objects); const expected = { doc: expect.objectContaining({ references }) }; const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -1748,9 +1424,8 @@ describe('SavedObjectsRepository', () => { it(`doesn't accept custom references if not an array`, async () => { const test = async (references: unknown) => { - const objects = [obj1, obj2].map((obj) => ({ ...obj, references })); - // @ts-expect-error references is unknown - await bulkUpdateSuccess(objects); + const objects = [obj1, obj2]; // .map((obj) => ({ ...obj })); + await bulkUpdateSuccess(client, repository, registry, objects); const expected = { doc: expect.not.objectContaining({ references: expect.anything() }) }; const body = [expect.any(Object), expected, expect.any(Object), expected]; expect(client.bulk).toHaveBeenCalledWith( @@ -1766,7 +1441,7 @@ describe('SavedObjectsRepository', () => { }); it(`defaults to a refresh setting of wait_for`, async () => { - await bulkUpdateSuccess([obj1, obj2]); + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2]); expect(client.bulk).toHaveBeenCalledWith( expect.objectContaining({ refresh: 'wait_for' }), expect.anything() @@ -1775,7 +1450,7 @@ describe('SavedObjectsRepository', () => { it(`defaults to no version for types that are not multi-namespace`, async () => { const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; - await bulkUpdateSuccess(objects); + await bulkUpdateSuccess(client, repository, registry, objects); expectClientCallArgsAction(objects, { method: 'update' }); }); @@ -1786,19 +1461,19 @@ describe('SavedObjectsRepository', () => { { ...obj1, version }, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE, version }, ]; - await bulkUpdateSuccess(objects); + await bulkUpdateSuccess(client, repository, registry, objects); const overrides = { if_seq_no: 100, if_primary_term: 200 }; expectClientCallArgsAction(objects, { method: 'update', overrides }); }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { const getId = (type: string, id: string) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) - await bulkUpdateSuccess([obj1, obj2], { namespace }); + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2], { namespace }); expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); jest.clearAllMocks(); // test again with object namespace string that supersedes the operation's namespace ID - await bulkUpdateSuccess([ + await bulkUpdateSuccess(client, repository, registry, [ { ...obj1, namespace }, { ...obj2, namespace }, ]); @@ -1807,12 +1482,15 @@ describe('SavedObjectsRepository', () => { it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) - await bulkUpdateSuccess([obj1, obj2]); + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2]); expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); jest.clearAllMocks(); // test again with object namespace string that supersedes the operation's namespace ID await bulkUpdateSuccess( + client, + repository, + registry, [ { ...obj1, namespace: 'default' }, { ...obj2, namespace: 'default' }, @@ -1824,7 +1502,9 @@ describe('SavedObjectsRepository', () => { it(`normalizes options.namespace from 'default' to undefined`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; - await bulkUpdateSuccess([obj1, obj2], { namespace: 'default' }); + await bulkUpdateSuccess(client, repository, registry, [obj1, obj2], { + namespace: 'default', + }); expectClientCallArgsAction([obj1, obj2], { method: 'update', getId }); }); @@ -1833,18 +1513,18 @@ describe('SavedObjectsRepository', () => { const _obj1 = { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }; const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - await bulkUpdateSuccess([_obj1], { namespace }); + await bulkUpdateSuccess(client, repository, registry, [_obj1], { namespace }); expectClientCallArgsAction([_obj1], { method: 'update', getId }); client.bulk.mockClear(); - await bulkUpdateSuccess([_obj2], { namespace }); + await bulkUpdateSuccess(client, repository, registry, [_obj2], { namespace }); expectClientCallArgsAction([_obj2], { method: 'update', getId }); jest.clearAllMocks(); // test again with object namespace string that supersedes the operation's namespace ID - await bulkUpdateSuccess([{ ..._obj1, namespace }]); + await bulkUpdateSuccess(client, repository, registry, [{ ..._obj1, namespace }]); expectClientCallArgsAction([_obj1], { method: 'update', getId }); client.bulk.mockClear(); - await bulkUpdateSuccess([{ ..._obj2, namespace }]); + await bulkUpdateSuccess(client, repository, registry, [{ ..._obj2, namespace }]); expectClientCallArgsAction([_obj2], { method: 'update', getId }); }); }); @@ -1866,7 +1546,7 @@ describe('SavedObjectsRepository', () => { expectedErrorResult: ExpectedErrorResult ) => { const objects = [obj1, obj, obj2]; - const mockResponse = getMockBulkUpdateResponse(objects); + const mockResponse = getMockBulkUpdateResponse(registry, objects); if (isBulkError) { // mock the bulk error for only the second object mockGetBulkOperationError.mockReturnValueOnce(undefined); @@ -1874,7 +1554,7 @@ describe('SavedObjectsRepository', () => { } client.bulk.mockResponseOnce(mockResponse); - const result = await savedObjectsRepository.bulkUpdate(objects); + const result = await repository.bulkUpdate(objects); expect(client.bulk).toHaveBeenCalled(); const objCall = isBulkError ? expectObjArgs(obj) : []; const body = [...expectObjArgs(obj1), ...objCall, ...expectObjArgs(obj2)]; @@ -1895,10 +1575,10 @@ describe('SavedObjectsRepository', () => { ) => { client.mget.mockResponseOnce(mgetResponse, { statusCode: mgetOptions?.statusCode }); - const bulkResponse = getMockBulkUpdateResponse([obj1, obj2], { namespace }); + const bulkResponse = getMockBulkUpdateResponse(registry, [obj1, obj2], { namespace }); client.bulk.mockResponseOnce(bulkResponse); - const result = await savedObjectsRepository.bulkUpdate([obj1, _obj, obj2], options); + const result = await repository.bulkUpdate([obj1, _obj, obj2], options); expect(client.bulk).toHaveBeenCalled(); expect(client.mget).toHaveBeenCalled(); const body = [...expectObjArgs(obj1), ...expectObjArgs(obj2)]; @@ -1914,8 +1594,8 @@ describe('SavedObjectsRepository', () => { it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.bulkUpdate([obj], { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.bulkUpdate([obj], { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`returns error when type is invalid`, async () => { @@ -1933,26 +1613,27 @@ describe('SavedObjectsRepository', () => { await bulkUpdateError( _obj, false, - expectErrorResult(obj, createBadRequestError('"namespace" cannot be "*"')) + expectErrorResult(obj, createBadRequestErrorPayload('"namespace" cannot be "*"')) ); }); it(`returns error when ES is unable to find the document (mget)`, async () => { const _obj = { ...obj, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; - const mgetResponse = getMockMgetResponse([_obj]); + const mgetResponse = getMockMgetResponse(registry, [_obj]); await bulkUpdateMultiError([obj1, _obj, obj2], undefined, mgetResponse); }); it(`returns error when ES is unable to find the index (mget)`, async () => { const _obj = { ...obj, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - await bulkUpdateMultiError([obj1, _obj, obj2], { namespace }, {} as estypes.MgetResponse, { + const mgetResponse = getMockMgetResponse(registry, [_obj]); + await bulkUpdateMultiError([obj1, _obj, obj2], { namespace }, mgetResponse, { statusCode: 404, }); }); it(`returns error when there is a conflict with an existing multi-namespace saved object (mget)`, async () => { const _obj = { ...obj, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - const mgetResponse = getMockMgetResponse([_obj], 'bar-namespace'); + const mgetResponse = getMockMgetResponse(registry, [_obj], 'bar-namespace'); await bulkUpdateMultiError([obj1, _obj, obj2], { namespace }, mgetResponse); }); @@ -1967,33 +1648,18 @@ describe('SavedObjectsRepository', () => { }); describe('returns', () => { - const expectSuccessResult = ({ - type, - id, - attributes, - references, - }: SavedObjectsBulkUpdateObject) => ({ - type, - id, - attributes, - references, - version: mockVersion, - namespaces: ['default'], - ...mockTimestampFields, - }); - it(`formats the ES response`, async () => { - const response = await bulkUpdateSuccess([obj1, obj2]); + const response = await bulkUpdateSuccess(client, repository, registry, [obj1, obj2]); expect(response).toEqual({ - saved_objects: [obj1, obj2].map(expectSuccessResult), + saved_objects: [obj1, obj2].map(expectUpdateResult), }); }); it(`includes references`, async () => { const objects = [obj1, obj2].map((obj) => ({ ...obj, references })); - const response = await bulkUpdateSuccess(objects); + const response = await bulkUpdateSuccess(client, repository, registry, objects); expect(response).toEqual({ - saved_objects: objects.map(expectSuccessResult), + saved_objects: objects.map(expectUpdateResult), }); }); @@ -2004,559 +1670,566 @@ describe('SavedObjectsRepository', () => { attributes: {}, }; const objects = [obj1, obj, obj2]; - const mockResponse = getMockBulkUpdateResponse(objects); + const mockResponse = getMockBulkUpdateResponse(registry, objects); client.bulk.mockResponseOnce(mockResponse); - const result = await savedObjectsRepository.bulkUpdate(objects); + const result = await repository.bulkUpdate(objects); expect(client.bulk).toHaveBeenCalledTimes(1); expect(result).toEqual({ - saved_objects: [expectSuccessResult(obj1), expectError(obj), expectSuccessResult(obj2)], + saved_objects: [expectUpdateResult(obj1), expectError(obj), expectUpdateResult(obj2)], }); }); it(`includes namespaces property for single-namespace and multi-namespace documents`, async () => { const obj: SavedObjectsBulkUpdateObject = { type: MULTI_NAMESPACE_ISOLATED_TYPE, - id: 'three', - attributes: {}, - }; - const result = await bulkUpdateSuccess([obj1, obj]); - expect(result).toEqual({ - saved_objects: [ - expect.objectContaining({ namespaces: expect.any(Array) }), - expect.objectContaining({ namespaces: expect.any(Array) }), - ], - }); - }); - - it(`includes originId property if present in cluster call response`, async () => { - const obj: SavedObjectsBulkUpdateObject = { - type: MULTI_NAMESPACE_ISOLATED_TYPE, - id: 'three', - attributes: {}, - }; - const result = await bulkUpdateSuccess([obj1, obj], {}, true); - expect(result).toEqual({ - saved_objects: [ - expect.objectContaining({ originId }), - expect.objectContaining({ originId }), - ], - }); - }); - }); - }); - - describe('#bulkDelete', () => { - const obj1: SavedObjectsBulkDeleteObject = { - type: 'config', - id: '6.0.0-alpha1', - }; - const obj2: SavedObjectsBulkDeleteObject = { - type: 'index-pattern', - id: 'logstash-*', - }; - - const namespace = 'foo-namespace'; - - const createNamespaceAwareGetId = (type: string, id: string) => - `${registry.isSingleNamespace(type) && namespace ? `${namespace}:` : ''}${type}:${id}`; - - const getMockEsBulkDeleteResponse = ( - objects: TypeIdTuple[], - options?: SavedObjectsBulkDeleteOptions - ) => - ({ - items: objects.map(({ type, id }) => ({ - // es response returns more fields than what we're interested in. - delete: { - _id: `${ - registry.isSingleNamespace(type) && options?.namespace ? `${options?.namespace}:` : '' - }${type}:${id}`, - ...mockVersionProps, - result: 'deleted', - }, - })), - } as estypes.BulkResponse); - - const repositoryBulkDeleteSuccess = async ( - objects: SavedObjectsBulkDeleteObject[] = [], - options?: SavedObjectsBulkDeleteOptions, - internalOptions: { - mockMGetResponseWithObject?: { initialNamespaces: string[]; type: string; id: string }; - } = {} - ) => { - const multiNamespaceObjects = objects.filter(({ type }) => { - return registry.isMultiNamespace(type); - }); - - const { mockMGetResponseWithObject } = internalOptions; - if (multiNamespaceObjects.length > 0) { - const mockedMGetResponse = mockMGetResponseWithObject - ? getMockMgetResponse([mockMGetResponseWithObject], options?.namespace) - : getMockMgetResponse(multiNamespaceObjects, options?.namespace); - client.mget.mockResponseOnce(mockedMGetResponse); - } - const mockedEsBulkDeleteResponse = getMockEsBulkDeleteResponse(objects, options); - - client.bulk.mockResponseOnce(mockedEsBulkDeleteResponse); - const result = await savedObjectsRepository.bulkDelete(objects, options); - - expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0); - return result; - }; - - // bulk delete calls only has one object for each source -- the action - const expectClientCallBulkDeleteArgsAction = ( - objects: TypeIdTuple[], - { - method, - _index = expect.any(String), - getId = () => expect.any(String), - overrides = {}, - }: { - method: string; - _index?: string; - getId?: (type: string, id: string) => string; - overrides?: Record; - } - ) => { - const body = []; - for (const { type, id } of objects) { - body.push({ - [method]: { - _index, - _id: getId(type, id), - ...overrides, - }, - }); - } - - expect(client.bulk).toHaveBeenCalledWith( - expect.objectContaining({ body }), - expect.anything() - ); - }; - - const createBulkDeleteFailStatus = ({ - type, - id, - error, - }: { - type: string; - id: string; - error?: ExpectedErrorResult['error']; - }) => ({ - type, - id, - success: false, - error: error ?? createBadRequestError(), - }); - - const createBulkDeleteSuccessStatus = ({ type, id }: { type: string; id: string }) => ({ - type, - id, - success: true, - }); - - // mocks a combination of success, error results for hidden and unknown object object types. - const repositoryBulkDeleteError = async ( - obj: SavedObjectsBulkDeleteObject, - isBulkError: boolean, - expectedErrorResult: ExpectedErrorResult - ) => { - const objects = [obj1, obj, obj2]; - const mockedBulkDeleteResponse = getMockEsBulkDeleteResponse(objects); - if (isBulkError) { - mockGetBulkOperationError.mockReturnValueOnce(undefined); - mockGetBulkOperationError.mockReturnValueOnce(expectedErrorResult.error as Payload); - } - client.bulk.mockResponseOnce(mockedBulkDeleteResponse); - - const result = await savedObjectsRepository.bulkDelete(objects); - expect(client.bulk).toHaveBeenCalled(); - expect(result).toEqual({ - statuses: [ - createBulkDeleteSuccessStatus(obj1), - createBulkDeleteFailStatus({ ...obj, error: expectedErrorResult.error }), - createBulkDeleteSuccessStatus(obj2), - ], - }); - }; - - const expectClientCallArgsAction = ( - objects: TypeIdTuple[], - { - method, - _index = expect.any(String), - getId = () => expect.any(String), - overrides = {}, - }: { - method: string; - _index?: string; - getId?: (type: string, id: string) => string; - overrides?: Record; - } - ) => { - const body = []; - for (const { type, id } of objects) { - body.push({ - [method]: { - _index, - _id: getId(type, id), - ...overrides, - }, - }); - } - expect(client.bulk).toHaveBeenCalledWith( - expect.objectContaining({ body }), - expect.anything() - ); - }; - - const bulkDeleteMultiNamespaceError = async ( - [obj1, _obj, obj2]: SavedObjectsBulkDeleteObject[], - options: SavedObjectsBulkDeleteOptions | undefined, - mgetResponse: estypes.MgetResponse, - mgetOptions?: { statusCode?: number } - ) => { - const getId = (type: string, id: string) => `${options?.namespace}:${type}:${id}`; - // mock the response for the not found doc - client.mget.mockResponseOnce(mgetResponse, { statusCode: mgetOptions?.statusCode }); - // get a mocked response for the valid docs - const bulkResponse = getMockEsBulkDeleteResponse([obj1, obj2], { namespace }); - client.bulk.mockResponseOnce(bulkResponse); - - const result = await savedObjectsRepository.bulkDelete([obj1, _obj, obj2], options); - expect(client.bulk).toHaveBeenCalledTimes(1); - expect(client.mget).toHaveBeenCalledTimes(1); - - expectClientCallArgsAction([obj1, obj2], { method: 'delete', getId }); - expect(result).toEqual({ - statuses: [ - createBulkDeleteSuccessStatus(obj1), - { ...expectErrorNotFound(_obj), success: false }, - createBulkDeleteSuccessStatus(obj2), - ], - }); - }; - - beforeEach(() => { - mockDeleteLegacyUrlAliases.mockClear(); - mockDeleteLegacyUrlAliases.mockResolvedValue(); - }); - - describe('client calls', () => { - it(`should use the ES bulk action by default`, async () => { - await repositoryBulkDeleteSuccess([obj1, obj2]); - expect(client.bulk).toHaveBeenCalled(); - }); - - it(`should use the ES mget action before bulk action for any types that are multi-namespace`, async () => { - const objects = [obj1, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }]; - await repositoryBulkDeleteSuccess(objects); - expect(client.bulk).toHaveBeenCalled(); - expect(client.mget).toHaveBeenCalled(); - - const docs = [ - expect.objectContaining({ _id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${obj2.id}` }), - ]; - expect(client.mget).toHaveBeenCalledWith( - expect.objectContaining({ body: { docs } }), - expect.anything() - ); - }); - - it(`should not use the ES bulk action when there are no valid documents to delete`, async () => { - const objects = [obj1, obj2].map((x) => ({ ...x, type: 'unknownType' })); - await savedObjectsRepository.bulkDelete(objects); - expect(client.bulk).toHaveBeenCalledTimes(0); - }); - - it(`formats the ES request`, async () => { - const getId = createNamespaceAwareGetId; - await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); - expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - }); - - it(`formats the ES request for any types that are multi-namespace`, async () => { - const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - const getId = createNamespaceAwareGetId; - await repositoryBulkDeleteSuccess([obj1, _obj2], { namespace }); - expectClientCallBulkDeleteArgsAction([obj1, _obj2], { method: 'delete', getId }); - }); - - it(`defaults to a refresh setting of wait_for`, async () => { - await repositoryBulkDeleteSuccess([obj1, obj2]); - expect(client.bulk).toHaveBeenCalledWith( - expect.objectContaining({ refresh: 'wait_for' }), - expect.anything() - ); - }); - - it(`does not include the version of the existing document when not using a multi-namespace type`, async () => { - const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; - await repositoryBulkDeleteSuccess(objects); - expectClientCallBulkDeleteArgsAction(objects, { method: 'delete' }); - }); - - it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - const getId = createNamespaceAwareGetId; - await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); - expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - }); - - it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - const getId = (type: string, id: string) => `${type}:${id}`; - await repositoryBulkDeleteSuccess([obj1, obj2]); - expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - }); - - it(`normalizes options.namespace from 'default' to undefined`, async () => { - const getId = (type: string, id: string) => `${type}:${id}`; - await repositoryBulkDeleteSuccess([obj1, obj2], { namespace: 'default' }); - expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - }); - - it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - const getId = (type: string, id: string) => `${type}:${id}`; // not expecting namespace prefix; - const _obj1 = { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }; - const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - - await repositoryBulkDeleteSuccess([_obj1, _obj2], { namespace }); - expectClientCallBulkDeleteArgsAction([_obj1, _obj2], { method: 'delete', getId }); - }); - }); - - describe('legacy URL aliases', () => { - it(`doesn't delete legacy URL aliases for single-namespace object types`, async () => { - await repositoryBulkDeleteSuccess([obj1, obj2]); - expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); - }); - - it(`deletes legacy URL aliases for multi-namespace object types (all spaces)`, async () => { - const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - const internalOptions = { - mockMGetResponseWithObject: { - ...testObject, - initialNamespaces: [ALL_NAMESPACES_STRING], - }, - }; - await repositoryBulkDeleteSuccess( - [testObject], - { namespace, force: true }, - internalOptions - ); - expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( - expect.objectContaining({ - type: MULTI_NAMESPACE_TYPE, - id: testObject.id, - namespaces: [], - deleteBehavior: 'exclusive', - }) - ); - }); - - it(`deletes legacy URL aliases for multi-namespace object types (specific space)`, async () => { - const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - const internalOptions = { - mockMGetResponseWithObject: { - ...testObject, - initialNamespaces: [namespace], - }, - }; - // specifically test against the current namespace - await repositoryBulkDeleteSuccess([testObject], { namespace }, internalOptions); - expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( - expect.objectContaining({ - type: MULTI_NAMESPACE_TYPE, - id: testObject.id, - namespaces: [namespace], - deleteBehavior: 'inclusive', - }) - ); - }); - - it(`deletes legacy URL aliases for multi-namespace object types shared to many specific spaces`, async () => { - const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - const initialTestObjectNamespaces = [namespace, 'bar-namespace']; - const internalOptions = { - mockMGetResponseWithObject: { - ...testObject, - initialNamespaces: initialTestObjectNamespaces, - }, - }; - // specifically test against named spaces ('*' is handled specifically, this assures we also take care of named spaces) - await repositoryBulkDeleteSuccess( - [testObject], - { namespace, force: true }, - internalOptions - ); - expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( - expect.objectContaining({ - type: MULTI_NAMESPACE_TYPE, - id: testObject.id, - namespaces: initialTestObjectNamespaces, - deleteBehavior: 'inclusive', - }) - ); - }); - - it(`logs a message when deleteLegacyUrlAliases returns an error`, async () => { - const testObject = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: obj1.id }; - - client.mget.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - getMockMgetResponse([testObject], namespace) - ) - ); - const mockedBulkResponse = getMockEsBulkDeleteResponse([testObject], { namespace }); - client.bulk.mockResolvedValueOnce(mockedBulkResponse); - - mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); - - await savedObjectsRepository.bulkDelete([testObject], { namespace }); - - expect(client.mget).toHaveBeenCalledTimes(1); - expect(logger.error).toHaveBeenCalledTimes(1); - expect(logger.error).toHaveBeenCalledWith( - 'Unable to delete aliases when deleting an object: Oh no!' - ); - }); - }); - - describe('errors', () => { - it(`throws an error when options.namespace is '*'`, async () => { - await expect( - savedObjectsRepository.bulkDelete([obj1], { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); - }); - - it(`throws an error when client bulk response is not defined`, async () => { - client.mget.mockResolvedValueOnce( - elasticsearchClientMock.createSuccessTransportRequestPromise( - getMockMgetResponse([obj1], namespace) - ) - ); - const mockedBulkResponse = undefined; - // we have to cast here to test the assumption we always get a response. - client.bulk.mockResponseOnce(mockedBulkResponse as unknown as estypes.BulkResponse); - await expect(savedObjectsRepository.bulkDelete([obj1], { namespace })).rejects.toThrowError( - 'Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined' - ); - }); - - it(`returns an error for the object when the object's type is invalid`, async () => { - const unknownObjType = { ...obj1, type: 'unknownType' }; - await repositoryBulkDeleteError( - unknownObjType, - false, - expectErrorInvalidType(unknownObjType) - ); - }); - - it(`returns an error for an object when the object's type is hidden`, async () => { - const hiddenObject = { ...obj1, type: HIDDEN_TYPE }; - await repositoryBulkDeleteError(hiddenObject, false, expectErrorInvalidType(hiddenObject)); - }); - - it(`returns an error when ES is unable to find the document during mget`, async () => { - const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; - const mgetResponse = getMockMgetResponse([notFoundObj], namespace); - await bulkDeleteMultiNamespaceError([obj1, notFoundObj, obj2], { namespace }, mgetResponse); - }); - - it(`returns an error when ES is unable to find the index during mget`, async () => { - const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; - await bulkDeleteMultiNamespaceError( - [obj1, notFoundObj, obj2], - { namespace }, - {} as estypes.MgetResponse, - { - statusCode: 404, - } - ); - }); - - it(`returns an error when the type is multi-namespace and the document exists, but not in this namespace`, async () => { - const obj = { - type: MULTI_NAMESPACE_ISOLATED_TYPE, - id: 'three', - namespace: 'bar-namespace', - }; - const mgetResponse = getMockMgetResponse([obj], namespace); - await bulkDeleteMultiNamespaceError([obj1, obj, obj2], { namespace }, mgetResponse); - }); - - it(`returns an error when the type is multi-namespace and the document has multiple namespaces and the force option is not enabled`, async () => { - const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - const internalOptions = { - mockMGetResponseWithObject: { - ...testObject, - initialNamespaces: [namespace, 'bar-namespace'], - }, - }; - const result = await repositoryBulkDeleteSuccess( - [testObject], - { namespace }, - internalOptions - ); - expect(result.statuses[0]).toStrictEqual( - createBulkDeleteFailStatus({ - ...testObject, - error: createBadRequestError( - 'Unable to delete saved object that exists in multiple namespaces, use the "force" option to delete it anyway' - ), - }) - ); - }); - - it(`returns an error when the type is multi-namespace and the document has all namespaces and the force option is not enabled`, async () => { - const testObject = { ...obj1, type: ALL_NAMESPACES_STRING }; - const internalOptions = { - mockMGetResponseWithObject: { - ...testObject, - initialNamespaces: [namespace, 'bar-namespace'], - }, - }; - const result = await repositoryBulkDeleteSuccess( - [testObject], - { namespace }, - internalOptions - ); - expect(result.statuses[0]).toStrictEqual( - createBulkDeleteFailStatus({ - ...testObject, - error: createBadRequestError("Unsupported saved object type: '*'"), - }) - ); - }); - }); - - describe('returns', () => { - it(`returns early for empty objects argument`, async () => { - await savedObjectsRepository.bulkDelete([], { namespace }); - expect(client.bulk).toHaveBeenCalledTimes(0); - }); - - it(`formats the ES response`, async () => { - const response = await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); - expect(response).toEqual({ - statuses: [obj1, obj2].map(createBulkDeleteSuccessStatus), + id: 'three', + attributes: {}, + }; + const result = await bulkUpdateSuccess(client, repository, registry, [obj1, obj]); + expect(result).toEqual({ + saved_objects: [ + expect.objectContaining({ namespaces: expect.any(Array) }), + expect.objectContaining({ namespaces: expect.any(Array) }), + ], }); }); - it(`handles a mix of successful deletes and errors`, async () => { - const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; - await bulkDeleteMultiNamespaceError( - [obj1, notFoundObj, obj2], - { namespace }, - {} as estypes.MgetResponse, - { statusCode: 404 } + it(`includes originId property if present in cluster call response`, async () => { + const obj: SavedObjectsBulkUpdateObject = { + type: MULTI_NAMESPACE_ISOLATED_TYPE, + id: 'three', + attributes: {}, + }; + const result = await bulkUpdateSuccess( + client, + repository, + registry, + [obj1, obj], + {}, + originId ); + expect(result).toEqual({ + saved_objects: [ + expect.objectContaining({ originId }), + expect.objectContaining({ originId }), + ], + }); }); }); }); + // describe('#bulkDelete', () => { + // const obj1: SavedObjectsBulkDeleteObject = { + // type: 'config', + // id: '6.0.0-alpha1', + // }; + // const obj2: SavedObjectsBulkDeleteObject = { + // type: 'index-pattern', + // id: 'logstash-*', + // }; + + // const namespace = 'foo-namespace'; + + // const createNamespaceAwareGetId = (type: string, id: string) => + // `${registry.isSingleNamespace(type) && namespace ? `${namespace}:` : ''}${type}:${id}`; + + // const getMockEsBulkDeleteResponse = ( + // objects: TypeIdTuple[], + // options?: SavedObjectsBulkDeleteOptions + // ) => + // ({ + // items: objects.map(({ type, id }) => ({ + // // es response returns more fields than what we're interested in. + // delete: { + // _id: `${ + // registry.isSingleNamespace(type) && options?.namespace ? `${options?.namespace}:` : '' + // }${type}:${id}`, + // ...mockVersionProps, + // result: 'deleted', + // }, + // })), + // } as estypes.BulkResponse); + + // const repositoryBulkDeleteSuccess = async ( + // objects: SavedObjectsBulkDeleteObject[] = [], + // options?: SavedObjectsBulkDeleteOptions, + // internalOptions: { + // mockMGetResponseWithObject?: { initialNamespaces: string[]; type: string; id: string }; + // } = {} + // ) => { + // const multiNamespaceObjects = objects.filter(({ type }) => { + // return registry.isMultiNamespace(type); + // }); + + // const { mockMGetResponseWithObject } = internalOptions; + // if (multiNamespaceObjects.length > 0) { + // const mockedMGetResponse = mockMGetResponseWithObject + // ? getMockMgetResponse([mockMGetResponseWithObject], options?.namespace) + // : getMockMgetResponse(multiNamespaceObjects, options?.namespace); + // client.mget.mockResponseOnce(mockedMGetResponse); + // } + // const mockedEsBulkDeleteResponse = getMockEsBulkDeleteResponse(objects, options); + + // client.bulk.mockResponseOnce(mockedEsBulkDeleteResponse); + // const result = await savedObjectsRepository.bulkDelete(objects, options); + + // expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0); + // return result; + // }; + + // // bulk delete calls only has one object for each source -- the action + // const expectClientCallBulkDeleteArgsAction = ( + // objects: TypeIdTuple[], + // { + // method, + // _index = expect.any(String), + // getId = () => expect.any(String), + // overrides = {}, + // }: { + // method: string; + // _index?: string; + // getId?: (type: string, id: string) => string; + // overrides?: Record; + // } + // ) => { + // const body = []; + // for (const { type, id } of objects) { + // body.push({ + // [method]: { + // _index, + // _id: getId(type, id), + // ...overrides, + // }, + // }); + // } + + // expect(client.bulk).toHaveBeenCalledWith( + // expect.objectContaining({ body }), + // expect.anything() + // ); + // }; + + // const createBulkDeleteFailStatus = ({ + // type, + // id, + // error, + // }: { + // type: string; + // id: string; + // error?: ExpectedErrorResult['error']; + // }) => ({ + // type, + // id, + // success: false, + // error: error ?? createBadRequestError(), + // }); + + // const createBulkDeleteSuccessStatus = ({ type, id }: { type: string; id: string }) => ({ + // type, + // id, + // success: true, + // }); + + // // mocks a combination of success, error results for hidden and unknown object object types. + // const repositoryBulkDeleteError = async ( + // obj: SavedObjectsBulkDeleteObject, + // isBulkError: boolean, + // expectedErrorResult: ExpectedErrorResult + // ) => { + // const objects = [obj1, obj, obj2]; + // const mockedBulkDeleteResponse = getMockEsBulkDeleteResponse(objects); + // if (isBulkError) { + // mockGetBulkOperationError.mockReturnValueOnce(undefined); + // mockGetBulkOperationError.mockReturnValueOnce(expectedErrorResult.error as Payload); + // } + // client.bulk.mockResponseOnce(mockedBulkDeleteResponse); + + // const result = await savedObjectsRepository.bulkDelete(objects); + // expect(client.bulk).toHaveBeenCalled(); + // expect(result).toEqual({ + // statuses: [ + // createBulkDeleteSuccessStatus(obj1), + // createBulkDeleteFailStatus({ ...obj, error: expectedErrorResult.error }), + // createBulkDeleteSuccessStatus(obj2), + // ], + // }); + // }; + + // const expectClientCallArgsAction = ( + // objects: TypeIdTuple[], + // { + // method, + // _index = expect.any(String), + // getId = () => expect.any(String), + // overrides = {}, + // }: { + // method: string; + // _index?: string; + // getId?: (type: string, id: string) => string; + // overrides?: Record; + // } + // ) => { + // const body = []; + // for (const { type, id } of objects) { + // body.push({ + // [method]: { + // _index, + // _id: getId(type, id), + // ...overrides, + // }, + // }); + // } + // expect(client.bulk).toHaveBeenCalledWith( + // expect.objectContaining({ body }), + // expect.anything() + // ); + // }; + + // const bulkDeleteMultiNamespaceError = async ( + // [obj1, _obj, obj2]: SavedObjectsBulkDeleteObject[], + // options: SavedObjectsBulkDeleteOptions | undefined, + // mgetResponse: estypes.MgetResponse, + // mgetOptions?: { statusCode?: number } + // ) => { + // const getId = (type: string, id: string) => `${options?.namespace}:${type}:${id}`; + // // mock the response for the not found doc + // client.mget.mockResponseOnce(mgetResponse, { statusCode: mgetOptions?.statusCode }); + // // get a mocked response for the valid docs + // const bulkResponse = getMockEsBulkDeleteResponse([obj1, obj2], { namespace }); + // client.bulk.mockResponseOnce(bulkResponse); + + // const result = await savedObjectsRepository.bulkDelete([obj1, _obj, obj2], options); + // expect(client.bulk).toHaveBeenCalledTimes(1); + // expect(client.mget).toHaveBeenCalledTimes(1); + + // expectClientCallArgsAction([obj1, obj2], { method: 'delete', getId }); + // expect(result).toEqual({ + // statuses: [ + // createBulkDeleteSuccessStatus(obj1), + // { ...expectErrorNotFound(_obj), success: false }, + // createBulkDeleteSuccessStatus(obj2), + // ], + // }); + // }; + + // beforeEach(() => { + // mockDeleteLegacyUrlAliases.mockClear(); + // mockDeleteLegacyUrlAliases.mockResolvedValue(); + // }); + + // describe('client calls', () => { + // it(`should use the ES bulk action by default`, async () => { + // await repositoryBulkDeleteSuccess([obj1, obj2]); + // expect(client.bulk).toHaveBeenCalled(); + // }); + + // it(`should use the ES mget action before bulk action for any types that are multi-namespace`, async () => { + // const objects = [obj1, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }]; + // await repositoryBulkDeleteSuccess(objects); + // expect(client.bulk).toHaveBeenCalled(); + // expect(client.mget).toHaveBeenCalled(); + + // const docs = [ + // expect.objectContaining({ _id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${obj2.id}` }), + // ]; + // expect(client.mget).toHaveBeenCalledWith( + // expect.objectContaining({ body: { docs } }), + // expect.anything() + // ); + // }); + + // it(`should not use the ES bulk action when there are no valid documents to delete`, async () => { + // const objects = [obj1, obj2].map((x) => ({ ...x, type: 'unknownType' })); + // await savedObjectsRepository.bulkDelete(objects); + // expect(client.bulk).toHaveBeenCalledTimes(0); + // }); + + // it(`formats the ES request`, async () => { + // const getId = createNamespaceAwareGetId; + // await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); + // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + // }); + + // it(`formats the ES request for any types that are multi-namespace`, async () => { + // const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; + // const getId = createNamespaceAwareGetId; + // await repositoryBulkDeleteSuccess([obj1, _obj2], { namespace }); + // expectClientCallBulkDeleteArgsAction([obj1, _obj2], { method: 'delete', getId }); + // }); + + // it(`defaults to a refresh setting of wait_for`, async () => { + // await repositoryBulkDeleteSuccess([obj1, obj2]); + // expect(client.bulk).toHaveBeenCalledWith( + // expect.objectContaining({ refresh: 'wait_for' }), + // expect.anything() + // ); + // }); + + // it(`does not include the version of the existing document when not using a multi-namespace type`, async () => { + // const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; + // await repositoryBulkDeleteSuccess(objects); + // expectClientCallBulkDeleteArgsAction(objects, { method: 'delete' }); + // }); + + // it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { + // const getId = createNamespaceAwareGetId; + // await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); + // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + // }); + + // it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { + // const getId = (type: string, id: string) => `${type}:${id}`; + // await repositoryBulkDeleteSuccess([obj1, obj2]); + // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + // }); + + // it(`normalizes options.namespace from 'default' to undefined`, async () => { + // const getId = (type: string, id: string) => `${type}:${id}`; + // await repositoryBulkDeleteSuccess([obj1, obj2], { namespace: 'default' }); + // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + // }); + + // it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { + // const getId = (type: string, id: string) => `${type}:${id}`; // not expecting namespace prefix; + // const _obj1 = { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }; + // const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; + + // await repositoryBulkDeleteSuccess([_obj1, _obj2], { namespace }); + // expectClientCallBulkDeleteArgsAction([_obj1, _obj2], { method: 'delete', getId }); + // }); + // }); + + // describe('legacy URL aliases', () => { + // it(`doesn't delete legacy URL aliases for single-namespace object types`, async () => { + // await repositoryBulkDeleteSuccess([obj1, obj2]); + // expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); + // }); + + // it(`deletes legacy URL aliases for multi-namespace object types (all spaces)`, async () => { + // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + // const internalOptions = { + // mockMGetResponseWithObject: { + // ...testObject, + // initialNamespaces: [ALL_NAMESPACES_STRING], + // }, + // }; + // await repositoryBulkDeleteSuccess( + // [testObject], + // { namespace, force: true }, + // internalOptions + // ); + // expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + // expect.objectContaining({ + // type: MULTI_NAMESPACE_TYPE, + // id: testObject.id, + // namespaces: [], + // deleteBehavior: 'exclusive', + // }) + // ); + // }); + + // it(`deletes legacy URL aliases for multi-namespace object types (specific space)`, async () => { + // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + // const internalOptions = { + // mockMGetResponseWithObject: { + // ...testObject, + // initialNamespaces: [namespace], + // }, + // }; + // // specifically test against the current namespace + // await repositoryBulkDeleteSuccess([testObject], { namespace }, internalOptions); + // expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + // expect.objectContaining({ + // type: MULTI_NAMESPACE_TYPE, + // id: testObject.id, + // namespaces: [namespace], + // deleteBehavior: 'inclusive', + // }) + // ); + // }); + + // it(`deletes legacy URL aliases for multi-namespace object types shared to many specific spaces`, async () => { + // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + // const initialTestObjectNamespaces = [namespace, 'bar-namespace']; + // const internalOptions = { + // mockMGetResponseWithObject: { + // ...testObject, + // initialNamespaces: initialTestObjectNamespaces, + // }, + // }; + // // specifically test against named spaces ('*' is handled specifically, this assures we also take care of named spaces) + // await repositoryBulkDeleteSuccess( + // [testObject], + // { namespace, force: true }, + // internalOptions + // ); + // expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + // expect.objectContaining({ + // type: MULTI_NAMESPACE_TYPE, + // id: testObject.id, + // namespaces: initialTestObjectNamespaces, + // deleteBehavior: 'inclusive', + // }) + // ); + // }); + + // it(`logs a message when deleteLegacyUrlAliases returns an error`, async () => { + // const testObject = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: obj1.id }; + + // client.mget.mockResolvedValueOnce( + // elasticsearchClientMock.createSuccessTransportRequestPromise( + // getMockMgetResponse([testObject], namespace) + // ) + // ); + // const mockedBulkResponse = getMockEsBulkDeleteResponse([testObject], { namespace }); + // client.bulk.mockResolvedValueOnce(mockedBulkResponse); + + // mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); + + // await savedObjectsRepository.bulkDelete([testObject], { namespace }); + + // expect(client.mget).toHaveBeenCalledTimes(1); + // expect(logger.error).toHaveBeenCalledTimes(1); + // expect(logger.error).toHaveBeenCalledWith( + // 'Unable to delete aliases when deleting an object: Oh no!' + // ); + // }); + // }); + + // describe('errors', () => { + // it(`throws an error when options.namespace is '*'`, async () => { + // await expect( + // savedObjectsRepository.bulkDelete([obj1], { namespace: ALL_NAMESPACES_STRING }) + // ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + // }); + + // it(`throws an error when client bulk response is not defined`, async () => { + // client.mget.mockResolvedValueOnce( + // elasticsearchClientMock.createSuccessTransportRequestPromise( + // getMockMgetResponse([obj1], namespace) + // ) + // ); + // const mockedBulkResponse = undefined; + // // we have to cast here to test the assumption we always get a response. + // client.bulk.mockResponseOnce(mockedBulkResponse as unknown as estypes.BulkResponse); + // await expect(savedObjectsRepository.bulkDelete([obj1], { namespace })).rejects.toThrowError( + // 'Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined' + // ); + // }); + + // it(`returns an error for the object when the object's type is invalid`, async () => { + // const unknownObjType = { ...obj1, type: 'unknownType' }; + // await repositoryBulkDeleteError( + // unknownObjType, + // false, + // expectErrorInvalidType(unknownObjType) + // ); + // }); + + // it(`returns an error for an object when the object's type is hidden`, async () => { + // const hiddenObject = { ...obj1, type: HIDDEN_TYPE }; + // await repositoryBulkDeleteError(hiddenObject, false, expectErrorInvalidType(hiddenObject)); + // }); + + // it(`returns an error when ES is unable to find the document during mget`, async () => { + // const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + // const mgetResponse = getMockMgetResponse([notFoundObj], namespace); + // await bulkDeleteMultiNamespaceError([obj1, notFoundObj, obj2], { namespace }, mgetResponse); + // }); + + // it(`returns an error when ES is unable to find the index during mget`, async () => { + // const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + // await bulkDeleteMultiNamespaceError( + // [obj1, notFoundObj, obj2], + // { namespace }, + // {} as estypes.MgetResponse, + // { + // statusCode: 404, + // } + // ); + // }); + + // it(`returns an error when the type is multi-namespace and the document exists, but not in this namespace`, async () => { + // const obj = { + // type: MULTI_NAMESPACE_ISOLATED_TYPE, + // id: 'three', + // namespace: 'bar-namespace', + // }; + // const mgetResponse = getMockMgetResponse([obj], namespace); + // await bulkDeleteMultiNamespaceError([obj1, obj, obj2], { namespace }, mgetResponse); + // }); + + // it(`returns an error when the type is multi-namespace and the document has multiple namespaces and the force option is not enabled`, async () => { + // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + // const internalOptions = { + // mockMGetResponseWithObject: { + // ...testObject, + // initialNamespaces: [namespace, 'bar-namespace'], + // }, + // }; + // const result = await repositoryBulkDeleteSuccess( + // [testObject], + // { namespace }, + // internalOptions + // ); + // expect(result.statuses[0]).toStrictEqual( + // createBulkDeleteFailStatus({ + // ...testObject, + // error: createBadRequestError( + // 'Unable to delete saved object that exists in multiple namespaces, use the "force" option to delete it anyway' + // ), + // }) + // ); + // }); + + // it(`returns an error when the type is multi-namespace and the document has all namespaces and the force option is not enabled`, async () => { + // const testObject = { ...obj1, type: ALL_NAMESPACES_STRING }; + // const internalOptions = { + // mockMGetResponseWithObject: { + // ...testObject, + // initialNamespaces: [namespace, 'bar-namespace'], + // }, + // }; + // const result = await repositoryBulkDeleteSuccess( + // [testObject], + // { namespace }, + // internalOptions + // ); + // expect(result.statuses[0]).toStrictEqual( + // createBulkDeleteFailStatus({ + // ...testObject, + // error: createBadRequestError("Unsupported saved object type: '*'"), + // }) + // ); + // }); + // }); + + // describe('returns', () => { + // it(`returns early for empty objects argument`, async () => { + // await savedObjectsRepository.bulkDelete([], { namespace }); + // expect(client.bulk).toHaveBeenCalledTimes(0); + // }); + + // it(`formats the ES response`, async () => { + // const response = await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); + // expect(response).toEqual({ + // statuses: [obj1, obj2].map(createBulkDeleteSuccessStatus), + // }); + // }); + + // it(`handles a mix of successful deletes and errors`, async () => { + // const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + // await bulkDeleteMultiNamespaceError( + // [obj1, notFoundObj, obj2], + // { namespace }, + // {} as estypes.MgetResponse, + // { statusCode: 404 } + // ); + // }); + // }); + // }); + describe('#checkConflicts', () => { const obj1 = { type: 'dashboard', id: 'one' }; const obj2 = { type: 'dashboard', id: 'two' }; @@ -2567,24 +2240,6 @@ describe('SavedObjectsRepository', () => { const obj7 = { type: NAMESPACE_AGNOSTIC_TYPE, id: 'seven' }; const namespace = 'foo-namespace'; - const checkConflicts = async (objects: TypeIdTuple[], options?: SavedObjectsBaseOptions) => - savedObjectsRepository.checkConflicts( - objects.map(({ type, id }) => ({ type, id })), // checkConflicts only uses type and id - options - ); - const checkConflictsSuccess = async ( - objects: TypeIdTuple[], - options?: SavedObjectsBaseOptions - ) => { - const response = getMockMgetResponse(objects, options?.namespace); - client.mget.mockResolvedValue( - elasticsearchClientMock.createSuccessTransportRequestPromise(response) - ); - const result = await checkConflicts(objects, options); - expect(client.mget).toHaveBeenCalledTimes(1); - return result; - }; - const _expectClientCallArgs = ( objects: TypeIdTuple[], { @@ -2609,32 +2264,34 @@ describe('SavedObjectsRepository', () => { describe('client calls', () => { it(`doesn't make a cluster call if the objects array is empty`, async () => { - await checkConflicts([]); + await checkConflicts(repository, []); expect(client.mget).not.toHaveBeenCalled(); }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { const getId = (type: string, id: string) => `${namespace}:${type}:${id}`; // test that the raw document ID equals this (e.g., has a namespace prefix) - await checkConflictsSuccess([obj1, obj2], { namespace }); + await checkConflictsSuccess(client, repository, registry, [obj1, obj2], { namespace }); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) - await checkConflictsSuccess([obj1, obj2]); + await checkConflictsSuccess(client, repository, registry, [obj1, obj2]); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`normalizes options.namespace from 'default' to undefined`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) - await checkConflictsSuccess([obj1, obj2], { namespace: 'default' }); + await checkConflictsSuccess(client, repository, registry, [obj1, obj2], { + namespace: 'default', + }); _expectClientCallArgs([obj1, obj2], { getId }); }); it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { const getId = (type: string, id: string) => `${type}:${id}`; // test that the raw document ID equals this (e.g., does not have a namespace prefix) // obj3 is multi-namespace, and obj6 is namespace-agnostic - await checkConflictsSuccess([obj3, obj6], { namespace }); + await checkConflictsSuccess(client, repository, registry, [obj3, obj6], { namespace }); _expectClientCallArgs([obj3, obj6], { getId }); }); }); @@ -2642,8 +2299,8 @@ describe('SavedObjectsRepository', () => { describe('errors', () => { it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.checkConflicts([obj1], { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.checkConflicts([obj1], { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); }); @@ -2654,12 +2311,12 @@ describe('SavedObjectsRepository', () => { const objects = [unknownTypeObj, hiddenTypeObj, obj1, obj2, obj3, obj4, obj5, obj6, obj7]; const response = { docs: [ - getMockGetResponse(obj1), + getMockGetResponse(registry, obj1), { found: false }, - getMockGetResponse(obj3), - getMockGetResponse({ ...obj4, namespace: 'bar-namespace' }), + getMockGetResponse(registry, obj3), + getMockGetResponse(registry, { ...obj4, namespace: 'bar-namespace' }), { found: false }, - getMockGetResponse(obj6), + getMockGetResponse(registry, obj6), { found: false }, ], } as estypes.MgetResponse; @@ -2667,24 +2324,24 @@ describe('SavedObjectsRepository', () => { elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); - const result = await checkConflicts(objects); + const result = await checkConflicts(repository, objects); expect(client.mget).toHaveBeenCalledTimes(1); expect(result).toEqual({ errors: [ - { ...unknownTypeObj, error: createUnsupportedTypeError(unknownTypeObj.type) }, - { ...hiddenTypeObj, error: createUnsupportedTypeError(hiddenTypeObj.type) }, - { ...obj1, error: createConflictError(obj1.type, obj1.id) }, + { ...unknownTypeObj, error: createUnsupportedTypeErrorPayload(unknownTypeObj.type) }, + { ...hiddenTypeObj, error: createUnsupportedTypeErrorPayload(hiddenTypeObj.type) }, + { ...obj1, error: createConflictErrorPayload(obj1.type, obj1.id) }, // obj2 was not found so it does not result in a conflict error - { ...obj3, error: createConflictError(obj3.type, obj3.id) }, + { ...obj3, error: createConflictErrorPayload(obj3.type, obj3.id) }, { ...obj4, error: { - ...createConflictError(obj4.type, obj4.id), + ...createConflictErrorPayload(obj4.type, obj4.id), metadata: { isNotOverwritable: true }, }, }, // obj5 was not found so it does not result in a conflict error - { ...obj6, error: createConflictError(obj6.type, obj6.id) }, + { ...obj6, error: createConflictErrorPayload(obj6.type, obj6.id) }, // obj7 was not found so it does not result in a conflict error ], }); @@ -2725,7 +2382,7 @@ describe('SavedObjectsRepository', () => { attributes: T, options?: SavedObjectsCreateOptions ) => { - return await savedObjectsRepository.create(type, attributes, options); + return await repository.create(type, attributes, options); }; describe('client calls', () => { @@ -2825,9 +2482,11 @@ describe('SavedObjectsRepository', () => { for (const objType of [type, NAMESPACE_AGNOSTIC_TYPE]) { it(`throws an error if originId is set for non-multi-namespace type`, async () => { await expect( - savedObjectsRepository.create(objType, attributes, { originId: 'some-originId' }) + repository.create(objType, attributes, { originId: 'some-originId' }) ).rejects.toThrowError( - createBadRequestError('"originId" can only be set for multi-namespace object types') + createBadRequestErrorPayload( + '"originId" can only be set for multi-namespace object types' + ) ); }); } @@ -3025,13 +2684,13 @@ describe('SavedObjectsRepository', () => { const ns2 = 'bar-namespace'; const ns3 = 'baz-namespace'; // first object does not get passed in to preflightCheckForCreate at all - await savedObjectsRepository.create('dashboard', attributes, { + await repository.create('dashboard', attributes, { id, namespace, initialNamespaces: [ns2], }); // second object does not have an existing document to overwrite - await savedObjectsRepository.create(MULTI_NAMESPACE_TYPE, attributes, { + await repository.create(MULTI_NAMESPACE_TYPE, attributes, { id, namespace, initialNamespaces: [ns2, ns3], @@ -3046,7 +2705,7 @@ describe('SavedObjectsRepository', () => { }, // third object does have an existing document to overwrite }, ]); - await savedObjectsRepository.create(MULTI_NAMESPACE_ISOLATED_TYPE, attributes, { + await repository.create(MULTI_NAMESPACE_ISOLATED_TYPE, attributes, { id, namespace, initialNamespaces: [ns2], @@ -3097,7 +2756,7 @@ describe('SavedObjectsRepository', () => { }); it(`normalizes initialNamespaces from 'default' to undefined`, async () => { - await savedObjectsRepository.create('dashboard', attributes, { + await repository.create('dashboard', attributes, { id, namespace, initialNamespaces: ['default'], @@ -3132,28 +2791,28 @@ describe('SavedObjectsRepository', () => { describe('errors', () => { it(`throws when options.initialNamespaces is used with a space-agnostic object`, async () => { await expect( - savedObjectsRepository.create(NAMESPACE_AGNOSTIC_TYPE, attributes, { + repository.create(NAMESPACE_AGNOSTIC_TYPE, attributes, { initialNamespaces: [namespace], }) ).rejects.toThrowError( - createBadRequestError('"initialNamespaces" cannot be used on space-agnostic types') + createBadRequestErrorPayload('"initialNamespaces" cannot be used on space-agnostic types') ); }); it(`throws when options.initialNamespaces is empty`, async () => { await expect( - savedObjectsRepository.create(MULTI_NAMESPACE_TYPE, attributes, { initialNamespaces: [] }) + repository.create(MULTI_NAMESPACE_TYPE, attributes, { initialNamespaces: [] }) ).rejects.toThrowError( - createBadRequestError('"initialNamespaces" must be a non-empty array of strings') + createBadRequestErrorPayload('"initialNamespaces" must be a non-empty array of strings') ); }); it(`throws when options.initialNamespaces is used with a space-isolated object and does not specify a single space`, async () => { const doTest = async (objType: string, initialNamespaces?: string[]) => { await expect( - savedObjectsRepository.create(objType, attributes, { initialNamespaces }) + repository.create(objType, attributes, { initialNamespaces }) ).rejects.toThrowError( - createBadRequestError( + createBadRequestErrorPayload( '"initialNamespaces" can only specify a single space when used with space-isolated types' ) ); @@ -3166,27 +2825,27 @@ describe('SavedObjectsRepository', () => { it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.create(type, attributes, { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.create(type, attributes, { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`throws when type is invalid`, async () => { - await expect(savedObjectsRepository.create('unknownType', attributes)).rejects.toThrowError( - createUnsupportedTypeError('unknownType') + await expect(repository.create('unknownType', attributes)).rejects.toThrowError( + createUnsupportedTypeErrorPayload('unknownType') ); expect(client.create).not.toHaveBeenCalled(); }); it(`throws when type is hidden`, async () => { - await expect(savedObjectsRepository.create(HIDDEN_TYPE, attributes)).rejects.toThrowError( - createUnsupportedTypeError(HIDDEN_TYPE) + await expect(repository.create(HIDDEN_TYPE, attributes)).rejects.toThrowError( + createUnsupportedTypeErrorPayload(HIDDEN_TYPE) ); expect(client.create).not.toHaveBeenCalled(); }); it(`throws when schema validation fails`, async () => { await expect( - savedObjectsRepository.create('dashboard', { title: 123 }) + repository.create('dashboard', { title: 123 }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"[attributes.title]: expected value of type [string] but got [number]: Bad Request"` ); @@ -3198,12 +2857,12 @@ describe('SavedObjectsRepository', () => { { type: MULTI_NAMESPACE_ISOLATED_TYPE, id, error: { type: 'unresolvableConflict' } }, // error type and metadata dont matter ]); await expect( - savedObjectsRepository.create(MULTI_NAMESPACE_ISOLATED_TYPE, attributes, { + repository.create(MULTI_NAMESPACE_ISOLATED_TYPE, attributes, { id, overwrite: true, namespace, }) - ).rejects.toThrowError(createConflictError(MULTI_NAMESPACE_ISOLATED_TYPE, id)); + ).rejects.toThrowError(createConflictErrorPayload(MULTI_NAMESPACE_ISOLATED_TYPE, id)); expect(mockPreflightCheckForCreate).toHaveBeenCalled(); }); @@ -3307,26 +2966,6 @@ describe('SavedObjectsRepository', () => { const id = 'logstash-*'; const namespace = 'foo-namespace'; - const deleteSuccess = async ( - type: string, - id: string, - options?: SavedObjectsDeleteOptions, - internalOptions: { mockGetResponseValue?: estypes.GetResponse } = {} - ) => { - const { mockGetResponseValue } = internalOptions; - if (registry.isMultiNamespace(type)) { - const mockGetResponse = - mockGetResponseValue ?? getMockGetResponse({ type, id }, options?.namespace); - client.get.mockResponseOnce(mockGetResponse); - } - client.delete.mockResponseOnce({ - result: 'deleted', - } as estypes.DeleteResponse); - const result = await savedObjectsRepository.delete(type, id, options); - expect(client.get).toHaveBeenCalledTimes(registry.isMultiNamespace(type) ? 1 : 0); - return result; - }; - beforeEach(() => { mockDeleteLegacyUrlAliases.mockClear(); mockDeleteLegacyUrlAliases.mockResolvedValue(); @@ -3334,19 +2973,19 @@ describe('SavedObjectsRepository', () => { describe('client calls', () => { it(`should use the ES delete action when not using a multi-namespace type`, async () => { - await deleteSuccess(type, id); + await deleteSuccess(client, repository, registry, type, id); expect(client.get).not.toHaveBeenCalled(); expect(client.delete).toHaveBeenCalledTimes(1); }); it(`should use ES get action then delete action when using a multi-namespace type`, async () => { - await deleteSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id); + await deleteSuccess(client, repository, registry, MULTI_NAMESPACE_ISOLATED_TYPE, id); expect(client.get).toHaveBeenCalledTimes(1); expect(client.delete).toHaveBeenCalledTimes(1); }); it(`does not includes the version of the existing document when using a multi-namespace type`, async () => { - await deleteSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id); + await deleteSuccess(client, repository, registry, MULTI_NAMESPACE_ISOLATED_TYPE, id); const versionProperties = { if_seq_no: mockVersionProps._seq_no, if_primary_term: mockVersionProps._primary_term, @@ -3358,7 +2997,7 @@ describe('SavedObjectsRepository', () => { }); it(`defaults to a refresh setting of wait_for`, async () => { - await deleteSuccess(type, id); + await deleteSuccess(client, repository, registry, type, id); expect(client.delete).toHaveBeenCalledWith( expect.objectContaining({ refresh: 'wait_for' }), expect.anything() @@ -3366,7 +3005,7 @@ describe('SavedObjectsRepository', () => { }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - await deleteSuccess(type, id, { namespace }); + await deleteSuccess(client, repository, registry, type, id, { namespace }); expect(client.delete).toHaveBeenCalledWith( expect.objectContaining({ id: `${namespace}:${type}:${id}` }), expect.anything() @@ -3374,7 +3013,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - await deleteSuccess(type, id); + await deleteSuccess(client, repository, registry, type, id); expect(client.delete).toHaveBeenCalledWith( expect.objectContaining({ id: `${type}:${id}` }), expect.anything() @@ -3382,7 +3021,7 @@ describe('SavedObjectsRepository', () => { }); it(`normalizes options.namespace from 'default' to undefined`, async () => { - await deleteSuccess(type, id, { namespace: 'default' }); + await deleteSuccess(client, repository, registry, type, id, { namespace: 'default' }); expect(client.delete).toHaveBeenCalledWith( expect.objectContaining({ id: `${type}:${id}` }), expect.anything() @@ -3390,14 +3029,18 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - await deleteSuccess(NAMESPACE_AGNOSTIC_TYPE, id, { namespace }); + await deleteSuccess(client, repository, registry, NAMESPACE_AGNOSTIC_TYPE, id, { + namespace, + }); expect(client.delete).toHaveBeenCalledWith( expect.objectContaining({ id: `${NAMESPACE_AGNOSTIC_TYPE}:${id}` }), expect.anything() ); client.delete.mockClear(); - await deleteSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }); + await deleteSuccess(client, repository, registry, MULTI_NAMESPACE_ISOLATED_TYPE, id, { + namespace, + }); expect(client.delete).toHaveBeenCalledWith( expect.objectContaining({ id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${id}` }), expect.anything() @@ -3407,7 +3050,7 @@ describe('SavedObjectsRepository', () => { describe('legacy URL aliases', () => { it(`doesn't delete legacy URL aliases for single-namespace object types`, async () => { - await deleteSuccess(type, id, { namespace }); + await deleteSuccess(client, repository, registry, type, id, { namespace }); expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); }); @@ -3417,11 +3060,20 @@ describe('SavedObjectsRepository', () => { it(`deletes legacy URL aliases for multi-namespace object types (all spaces)`, async () => { const internalOptions = { mockGetResponseValue: getMockGetResponse( + registry, { type: MULTI_NAMESPACE_TYPE, id }, ALL_NAMESPACES_STRING ), }; - await deleteSuccess(MULTI_NAMESPACE_TYPE, id, { namespace, force: true }, internalOptions); + await deleteSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_TYPE, + id, + { namespace, force: true }, + internalOptions + ); expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( expect.objectContaining({ type: MULTI_NAMESPACE_TYPE, @@ -3433,7 +3085,7 @@ describe('SavedObjectsRepository', () => { }); it(`deletes legacy URL aliases for multi-namespace object types (specific spaces)`, async () => { - await deleteSuccess(MULTI_NAMESPACE_TYPE, id, { namespace }); // this function mocks a preflight response with the given namespace by default + await deleteSuccess(client, repository, registry, MULTI_NAMESPACE_TYPE, id, { namespace }); // this function mocks a preflight response with the given namespace by default expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( expect.objectContaining({ type: MULTI_NAMESPACE_TYPE, @@ -3447,7 +3099,7 @@ describe('SavedObjectsRepository', () => { it(`logs a message when deleteLegacyUrlAliases returns an error`, async () => { client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise( - getMockGetResponse({ type: MULTI_NAMESPACE_ISOLATED_TYPE, id, namespace }) + getMockGetResponse(registry, { type: MULTI_NAMESPACE_ISOLATED_TYPE, id, namespace }) ) ); client.delete.mockResolvedValueOnce( @@ -3456,7 +3108,7 @@ describe('SavedObjectsRepository', () => { } as estypes.DeleteResponse) ); mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); - await savedObjectsRepository.delete(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }); + await repository.delete(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }); expect(client.get).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenCalledTimes(1); expect(logger.error).toHaveBeenCalledWith( @@ -3471,15 +3123,15 @@ describe('SavedObjectsRepository', () => { id: string, options?: SavedObjectsDeleteOptions ) => { - await expect(savedObjectsRepository.delete(type, id, options)).rejects.toThrowError( - createGenericNotFoundError(type, id) + await expect(repository.delete(type, id, options)).rejects.toThrowError( + createGenericNotFoundErrorPayload(type, id) ); }; it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.delete(type, id, { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.delete(type, id, { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`throws when type is invalid`, async () => { @@ -3513,7 +3165,11 @@ describe('SavedObjectsRepository', () => { }); it(`throws when the type is multi-namespace and the document exists, but not in this namespace`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_ISOLATED_TYPE, id }, namespace); + const response = getMockGetResponse( + registry, + { type: MULTI_NAMESPACE_ISOLATED_TYPE, id }, + namespace + ); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -3524,13 +3180,17 @@ describe('SavedObjectsRepository', () => { }); it(`throws when the type is multi-namespace and the document has multiple namespaces and the force option is not enabled`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_ISOLATED_TYPE, id, namespace }); + const response = getMockGetResponse(registry, { + type: MULTI_NAMESPACE_ISOLATED_TYPE, + id, + namespace, + }); response._source!.namespaces = [namespace, 'bar-namespace']; client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); await expect( - savedObjectsRepository.delete(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }) + repository.delete(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }) ).rejects.toThrowError( 'Unable to delete saved object that exists in multiple namespaces, use the `force` option to delete it anyway' ); @@ -3538,13 +3198,17 @@ describe('SavedObjectsRepository', () => { }); it(`throws when the type is multi-namespace and the document has all namespaces and the force option is not enabled`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_ISOLATED_TYPE, id, namespace }); + const response = getMockGetResponse(registry, { + type: MULTI_NAMESPACE_ISOLATED_TYPE, + id, + namespace, + }); response._source!.namespaces = ['*']; client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); await expect( - savedObjectsRepository.delete(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }) + repository.delete(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }) ).rejects.toThrowError( 'Unable to delete saved object that exists in multiple namespaces, use the `force` option to delete it anyway' ); @@ -3578,7 +3242,7 @@ describe('SavedObjectsRepository', () => { result: 'something unexpected' as estypes.Result, } as estypes.DeleteResponse) ); - await expect(savedObjectsRepository.delete(type, id)).rejects.toThrowError( + await expect(repository.delete(type, id)).rejects.toThrowError( 'Unexpected Elasticsearch DELETE response' ); expect(client.delete).toHaveBeenCalledTimes(1); @@ -3587,7 +3251,7 @@ describe('SavedObjectsRepository', () => { describe('returns', () => { it(`returns an empty object on success`, async () => { - const result = await deleteSuccess(type, id); + const result = await deleteSuccess(client, repository, registry, type, id); expect(result).toEqual({}); }); }); @@ -3616,7 +3280,7 @@ describe('SavedObjectsRepository', () => { options?: SavedObjectsDeleteByNamespaceOptions ) => { client.updateByQuery.mockResponseOnce(mockUpdateResults); - const result = await savedObjectsRepository.deleteByNamespace(namespace, options); + const result = await repository.deleteByNamespace(namespace, options); expect(mockGetSearchDsl).toHaveBeenCalledTimes(1); expect(client.updateByQuery).toHaveBeenCalledTimes(1); return result; @@ -3643,7 +3307,7 @@ describe('SavedObjectsRepository', () => { it(`throws when namespace is not a string or is '*'`, async () => { const test = async (namespace: unknown) => { // @ts-expect-error namespace is unknown - await expect(savedObjectsRepository.deleteByNamespace(namespace)).rejects.toThrowError( + await expect(repository.deleteByNamespace(namespace)).rejects.toThrowError( `namespace is required, and must be a string` ); expect(client.updateByQuery).not.toHaveBeenCalled(); @@ -3683,24 +3347,16 @@ describe('SavedObjectsRepository', () => { const type = 'type'; const id = 'id'; const defaultOptions = {}; - const updatedCount = 42; - const removeReferencesToSuccess = async (options = defaultOptions) => { - client.updateByQuery.mockResponseOnce({ - updated: updatedCount, - }); - return await savedObjectsRepository.removeReferencesTo(type, id, options); - }; - describe('client calls', () => { it('should use the ES updateByQuery action', async () => { - await removeReferencesToSuccess(); + await removeReferencesToSuccess(client, repository, type, id); expect(client.updateByQuery).toHaveBeenCalledTimes(1); }); it('uses the correct default `refresh` value', async () => { - await removeReferencesToSuccess(); + await removeReferencesToSuccess(client, repository, type, id); expect(client.updateByQuery).toHaveBeenCalledWith( expect.objectContaining({ refresh: true, @@ -3712,7 +3368,7 @@ describe('SavedObjectsRepository', () => { it('merges output of getSearchDsl into es request body', async () => { const query = { query: 1, aggregations: 2 }; mockGetSearchDsl.mockReturnValue(query); - await removeReferencesToSuccess({ type }); + await removeReferencesToSuccess(client, repository, type, id, { type }); expect(client.updateByQuery).toHaveBeenCalledWith( expect.objectContaining({ @@ -3723,7 +3379,7 @@ describe('SavedObjectsRepository', () => { }); it('should set index to all known SO indices on the request', async () => { - await removeReferencesToSuccess(); + await removeReferencesToSuccess(client, repository, type, id); expect(client.updateByQuery).toHaveBeenCalledWith( expect.objectContaining({ index: ['.kibana-test_8.0.0-testing', 'custom_8.0.0-testing'], @@ -3735,7 +3391,7 @@ describe('SavedObjectsRepository', () => { it('should use the `refresh` option in the request', async () => { const refresh = Symbol(); - await removeReferencesToSuccess({ refresh }); + await removeReferencesToSuccess(client, repository, type, id, { refresh }); expect(client.updateByQuery).toHaveBeenCalledWith( expect.objectContaining({ refresh, @@ -3745,7 +3401,7 @@ describe('SavedObjectsRepository', () => { }); it('should pass the correct parameters to the update script', async () => { - await removeReferencesToSuccess(); + await removeReferencesToSuccess(client, repository, type, id); expect(client.updateByQuery).toHaveBeenCalledWith( expect.objectContaining({ body: expect.objectContaining({ @@ -3764,12 +3420,12 @@ describe('SavedObjectsRepository', () => { describe('search dsl', () => { it(`passes mappings and registry to getSearchDsl`, async () => { - await removeReferencesToSuccess(); + await removeReferencesToSuccess(client, repository, type, id); expect(mockGetSearchDsl).toHaveBeenCalledWith(mappings, registry, expect.anything()); }); it('passes namespace to getSearchDsl', async () => { - await removeReferencesToSuccess({ namespace: 'some-ns' }); + await removeReferencesToSuccess(client, repository, type, id, { namespace: 'some-ns' }); expect(mockGetSearchDsl).toHaveBeenCalledWith( mappings, registry, @@ -3780,7 +3436,7 @@ describe('SavedObjectsRepository', () => { }); it('passes hasReference to getSearchDsl', async () => { - await removeReferencesToSuccess(); + await removeReferencesToSuccess(client, repository, type, id); expect(mockGetSearchDsl).toHaveBeenCalledWith( mappings, registry, @@ -3794,7 +3450,7 @@ describe('SavedObjectsRepository', () => { }); it('passes all known types to getSearchDsl', async () => { - await removeReferencesToSuccess(); + await removeReferencesToSuccess(client, repository, type, id); expect(mockGetSearchDsl).toHaveBeenCalledWith( mappings, registry, @@ -3807,7 +3463,7 @@ describe('SavedObjectsRepository', () => { describe('returns', () => { it('returns the updated count from the ES response', async () => { - const response = await removeReferencesToSuccess(); + const response = await removeReferencesToSuccess(client, repository, type, id); expect(response.updated).toBe(updatedCount); }); }); @@ -3822,109 +3478,27 @@ describe('SavedObjectsRepository', () => { ], }); - await expect( - savedObjectsRepository.removeReferencesTo(type, id, defaultOptions) - ).rejects.toThrowError(createConflictError(type, id)); + await expect(repository.removeReferencesTo(type, id, defaultOptions)).rejects.toThrowError( + createConflictErrorPayload(type, id) + ); }); }); }); describe('#find', () => { - const generateSearchResults = (namespace?: string) => { - return { - took: 1, - timed_out: false, - _shards: {} as any, - hits: { - total: 4, - hits: [ - { - _index: '.kibana', - _id: `${namespace ? `${namespace}:` : ''}index-pattern:logstash-*`, - _score: 1, - ...mockVersionProps, - _source: { - namespace, - originId: 'some-origin-id', // only one of the results has an originId, this is intentional to test both a positive and negative case - type: 'index-pattern', - ...mockTimestampFields, - 'index-pattern': { - title: 'logstash-*', - timeFieldName: '@timestamp', - notExpandable: true, - }, - }, - }, - { - _index: '.kibana', - _id: `${namespace ? `${namespace}:` : ''}config:6.0.0-alpha1`, - _score: 2, - ...mockVersionProps, - _source: { - namespace, - type: 'config', - ...mockTimestampFields, - config: { - buildNum: 8467, - defaultIndex: 'logstash-*', - }, - }, - }, - { - _index: '.kibana', - _id: `${namespace ? `${namespace}:` : ''}index-pattern:stocks-*`, - _score: 3, - ...mockVersionProps, - _source: { - namespace, - type: 'index-pattern', - ...mockTimestampFields, - 'index-pattern': { - title: 'stocks-*', - timeFieldName: '@timestamp', - notExpandable: true, - }, - }, - }, - { - _index: '.kibana', - _id: `${NAMESPACE_AGNOSTIC_TYPE}:something`, - _score: 4, - ...mockVersionProps, - _source: { - type: NAMESPACE_AGNOSTIC_TYPE, - ...mockTimestampFields, - [NAMESPACE_AGNOSTIC_TYPE]: { - name: 'bar', - }, - }, - }, - ], - }, - } as estypes.SearchResponse; - }; - const type = 'index-pattern'; const namespace = 'foo-namespace'; - const findSuccess = async (options: SavedObjectsFindOptions, namespace?: string) => { - client.search.mockResponseOnce(generateSearchResults(namespace)); - const result = await savedObjectsRepository.find(options); - expect(mockGetSearchDsl).toHaveBeenCalledTimes(1); - expect(client.search).toHaveBeenCalledTimes(1); - return result; - }; - describe('client calls', () => { it(`should use the ES search action`, async () => { - await findSuccess({ type }); + await findSuccess(client, repository, { type }); expect(client.search).toHaveBeenCalledTimes(1); }); it(`merges output of getSearchDsl into es request body`, async () => { const query = { query: 1, aggregations: 2 }; mockGetSearchDsl.mockReturnValue(query); - await findSuccess({ type }); + await findSuccess(client, repository, { type }); expect(client.search).toHaveBeenCalledWith( expect.objectContaining({ @@ -3935,7 +3509,7 @@ describe('SavedObjectsRepository', () => { }); it(`accepts per_page/page`, async () => { - await findSuccess({ type, perPage: 10, page: 6 }); + await findSuccess(client, repository, { type, perPage: 10, page: 6 }); expect(client.search).toHaveBeenCalledWith( expect.objectContaining({ size: 10, @@ -3946,7 +3520,7 @@ describe('SavedObjectsRepository', () => { }); it(`accepts preference`, async () => { - await findSuccess({ type, preference: 'pref' }); + await findSuccess(client, repository, { type, preference: 'pref' }); expect(client.search).toHaveBeenCalledWith( expect.objectContaining({ preference: 'pref', @@ -3956,7 +3530,7 @@ describe('SavedObjectsRepository', () => { }); it(`can filter by fields`, async () => { - await findSuccess({ type, fields: ['title'] }); + await findSuccess(client, repository, { type, fields: ['title'] }); expect(client.search).toHaveBeenCalledWith( expect.objectContaining({ body: expect.objectContaining({ @@ -3979,7 +3553,7 @@ describe('SavedObjectsRepository', () => { }); it(`should set rest_total_hits_as_int to true on a request`, async () => { - await findSuccess({ type }); + await findSuccess(client, repository, { type }); expect(client.search).toHaveBeenCalledWith( expect.objectContaining({ rest_total_hits_as_int: true, @@ -3990,7 +3564,7 @@ describe('SavedObjectsRepository', () => { it(`should not make a client call when attempting to find only invalid or hidden types`, async () => { const test = async (types: string | string[]) => { - await savedObjectsRepository.find({ type: types }); + await repository.find({ type: types }); expect(client.search).not.toHaveBeenCalled(); }; @@ -4003,50 +3577,30 @@ describe('SavedObjectsRepository', () => { describe('errors', () => { it(`throws when type is not defined`, async () => { // @ts-expect-error type should be defined - await expect(savedObjectsRepository.find({})).rejects.toThrowError( + await expect(repository.find({})).rejects.toThrowError( 'options.type must be a string or an array of strings' ); expect(client.search).not.toHaveBeenCalled(); }); it(`throws when namespaces is an empty array`, async () => { - await expect( - savedObjectsRepository.find({ type: 'foo', namespaces: [] }) - ).rejects.toThrowError('options.namespaces cannot be an empty array'); - expect(client.search).not.toHaveBeenCalled(); - }); - - it(`throws when type is not falsy and typeToNamespacesMap is defined`, async () => { - await expect( - savedObjectsRepository.find({ type: 'foo', typeToNamespacesMap: new Map() }) - ).rejects.toThrowError( - 'options.type must be an empty string when options.typeToNamespacesMap is used' + await expect(repository.find({ type: 'foo', namespaces: [] })).rejects.toThrowError( + 'options.namespaces cannot be an empty array' ); expect(client.search).not.toHaveBeenCalled(); }); - it(`throws when type is not an empty array and typeToNamespacesMap is defined`, async () => { - const test = async (args: SavedObjectsFindOptions) => { - await expect(savedObjectsRepository.find(args)).rejects.toThrowError( - 'options.namespaces must be an empty array when options.typeToNamespacesMap is used' - ); - expect(client.search).not.toHaveBeenCalled(); - }; - await test({ type: '', typeToNamespacesMap: new Map() }); - await test({ type: '', namespaces: ['some-ns'], typeToNamespacesMap: new Map() }); - }); - it(`throws when searchFields is defined but not an array`, async () => { await expect( // @ts-expect-error searchFields is an array - savedObjectsRepository.find({ type, searchFields: 'string' }) + repository.find({ type, searchFields: 'string' }) ).rejects.toThrowError('options.searchFields must be an array'); expect(client.search).not.toHaveBeenCalled(); }); it(`throws when fields is defined but not an array`, async () => { // @ts-expect-error fields is an array - await expect(savedObjectsRepository.find({ type, fields: 'string' })).rejects.toThrowError( + await expect(repository.find({ type, fields: 'string' })).rejects.toThrowError( 'options.fields must be an array' ); expect(client.search).not.toHaveBeenCalled(); @@ -4054,7 +3608,7 @@ describe('SavedObjectsRepository', () => { it(`throws when a preference is provided with pit`, async () => { await expect( - savedObjectsRepository.find({ type: 'foo', pit: { id: 'abc123' }, preference: 'hi' }) + repository.find({ type: 'foo', pit: { id: 'abc123' }, preference: 'hi' }) ).rejects.toThrowError('options.preference must be excluded when options.pit is used'); expect(client.search).not.toHaveBeenCalled(); }); @@ -4075,7 +3629,7 @@ describe('SavedObjectsRepository', () => { filter: 'dashboard.attributes.otherField:<', }; - await expect(savedObjectsRepository.find(findOpts)).rejects.toMatchInlineSnapshot(` + await expect(repository.find(findOpts)).rejects.toMatchInlineSnapshot(` [Error: KQLSyntaxError: Expected "(", "{", value, whitespace but "<" found. dashboard.attributes.otherField:< --------------------------------^: Bad Request] @@ -4087,13 +3641,13 @@ describe('SavedObjectsRepository', () => { describe('returns', () => { it(`formats the ES response when there is no namespace`, async () => { - const noNamespaceSearchResults = generateSearchResults(); + const noNamespaceSearchResults = generateIndexPatternSearchResults(); client.search.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(noNamespaceSearchResults) ); const count = noNamespaceSearchResults.hits.hits.length; - const response = await savedObjectsRepository.find({ type }); + const response = await repository.find({ type }); expect(response.total).toBe(count); expect(response.saved_objects).toHaveLength(count); @@ -4114,13 +3668,13 @@ describe('SavedObjectsRepository', () => { }); it(`formats the ES response when there is a namespace`, async () => { - const namespacedSearchResults = generateSearchResults(namespace); + const namespacedSearchResults = generateIndexPatternSearchResults(namespace); client.search.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(namespacedSearchResults) ); const count = namespacedSearchResults.hits.hits.length; - const response = await savedObjectsRepository.find({ type, namespaces: [namespace] }); + const response = await repository.find({ type, namespaces: [namespace] }); expect(response.total).toBe(count); expect(response.saved_objects).toHaveLength(count); @@ -4142,7 +3696,7 @@ describe('SavedObjectsRepository', () => { it(`should return empty results when attempting to find only invalid or hidden types`, async () => { const test = async (types: string | string[]) => { - const result = await savedObjectsRepository.find({ type: types }); + const result = await repository.find({ type: types }); expect(result).toEqual(expect.objectContaining({ saved_objects: [] })); expect(client.search).not.toHaveBeenCalled(); }; @@ -4151,28 +3705,12 @@ describe('SavedObjectsRepository', () => { await test(HIDDEN_TYPE); await test(['unknownType', HIDDEN_TYPE]); }); - - it(`should return empty results when attempting to find only invalid or hidden types using typeToNamespacesMap`, async () => { - const test = async (types: string[]) => { - const result = await savedObjectsRepository.find({ - typeToNamespacesMap: new Map(types.map((x) => [x, undefined])), - type: '', - namespaces: [], - }); - expect(result).toEqual(expect.objectContaining({ saved_objects: [] })); - expect(client.search).not.toHaveBeenCalled(); - }; - - await test(['unknownType']); - await test([HIDDEN_TYPE]); - await test(['unknownType', HIDDEN_TYPE]); - }); }); describe('search dsl', () => { const commonOptions: SavedObjectsFindOptions = { - type: [type], // cannot be used when `typeToNamespacesMap` is present - namespaces: [namespace], // cannot be used when `typeToNamespacesMap` is present + type: [type], + namespaces: [namespace], search: 'foo*', searchFields: ['foo'], sortField: 'name', @@ -4189,46 +3727,17 @@ describe('SavedObjectsRepository', () => { }; it(`passes mappings, registry, and search options to getSearchDsl`, async () => { - await findSuccess(commonOptions, namespace); + await findSuccess(client, repository, commonOptions, namespace); expect(mockGetSearchDsl).toHaveBeenCalledWith(mappings, registry, commonOptions); }); - it(`accepts typeToNamespacesMap`, async () => { - const relevantOpts = { - ...commonOptions, - type: '', - namespaces: [], - typeToNamespacesMap: new Map([[type, [namespace]]]), // can only be used when `type` is falsy and `namespaces` is an empty array - }; - - await findSuccess(relevantOpts, namespace); - expect(mockGetSearchDsl).toHaveBeenCalledWith(mappings, registry, { - ...relevantOpts, - type: [type], - }); - }); - - it('search for the right fields when typeToNamespacesMap is set', async () => { - const relevantOpts = { - ...commonOptions, - fields: ['title'], - type: '', - namespaces: [], - typeToNamespacesMap: new Map([[type, [namespace]]]), - }; - - await findSuccess(relevantOpts, namespace); - const esOptions = client.search.mock.calls[0][0]; - expect(esOptions?._source ?? []).toContain('index-pattern.title'); - }); - it(`accepts hasReferenceOperator`, async () => { const relevantOpts: SavedObjectsFindOptions = { ...commonOptions, hasReferenceOperator: 'AND', }; - await findSuccess(relevantOpts, namespace); + await findSuccess(client, repository, relevantOpts, namespace); expect(mockGetSearchDsl).toHaveBeenCalledWith(mappings, registry, { ...relevantOpts, hasReferenceOperator: 'AND', @@ -4241,7 +3750,7 @@ describe('SavedObjectsRepository', () => { searchAfter: ['1', 'a'], }; - await findSuccess(relevantOpts, namespace); + await findSuccess(client, repository, relevantOpts, namespace); expect(mockGetSearchDsl).toHaveBeenCalledWith(mappings, registry, { ...relevantOpts, searchAfter: ['1', 'a'], @@ -4254,7 +3763,7 @@ describe('SavedObjectsRepository', () => { pit: { id: 'abc123', keepAlive: '2m' }, }; - await findSuccess(relevantOpts, namespace); + await findSuccess(client, repository, relevantOpts, namespace); expect(mockGetSearchDsl).toHaveBeenCalledWith(mappings, registry, { ...relevantOpts, pit: { id: 'abc123', keepAlive: '2m' }, @@ -4277,7 +3786,7 @@ describe('SavedObjectsRepository', () => { filter: 'dashboard.attributes.otherField: *', }; - await findSuccess(findOpts, namespace); + await findSuccess(client, repository, findOpts, namespace); const { kueryNode } = mockGetSearchDsl.mock.calls[0][2]; expect(kueryNode).toMatchInlineSnapshot(` Object { @@ -4314,7 +3823,7 @@ describe('SavedObjectsRepository', () => { filter: nodeTypes.function.buildNode('is', `dashboard.attributes.otherField`, '*'), }; - await findSuccess(findOpts, namespace); + await findSuccess(client, repository, findOpts, namespace); const { kueryNode } = mockGetSearchDsl.mock.calls[0][2]; expect(kueryNode).toMatchInlineSnapshot(` Object { @@ -4337,7 +3846,7 @@ describe('SavedObjectsRepository', () => { it(`supports multiple types`, async () => { const types = ['config', 'index-pattern']; - await findSuccess({ type: types }); + await findSuccess(client, repository, { type: types }); expect(mockGetSearchDsl).toHaveBeenCalledWith( mappings, @@ -4350,7 +3859,7 @@ describe('SavedObjectsRepository', () => { it(`filters out invalid types`, async () => { const types = ['config', 'unknownType', 'index-pattern']; - await findSuccess({ type: types }); + await findSuccess(client, repository, { type: types }); expect(mockGetSearchDsl).toHaveBeenCalledWith( mappings, @@ -4363,7 +3872,7 @@ describe('SavedObjectsRepository', () => { it(`filters out hidden types`, async () => { const types = ['config', HIDDEN_TYPE, 'index-pattern']; - await findSuccess({ type: types }); + await findSuccess(client, repository, { type: types }); expect(mockGetSearchDsl).toHaveBeenCalledWith( mappings, @@ -4382,36 +3891,14 @@ describe('SavedObjectsRepository', () => { const namespace = 'foo-namespace'; const originId = 'some-origin-id'; - const getSuccess = async ( - type: string, - id: string, - options?: SavedObjectsBaseOptions, - includeOriginId?: boolean - ) => { - const response = getMockGetResponse( - { - type, - id, - // "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the - // operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response. - ...(includeOriginId && { originId }), - }, - options?.namespace - ); - client.get.mockResponseOnce(response); - const result = await savedObjectsRepository.get(type, id, options); - expect(client.get).toHaveBeenCalledTimes(1); - return result; - }; - describe('client calls', () => { it(`should use the ES get action`, async () => { - await getSuccess(type, id); + await getSuccess(client, repository, registry, type, id); expect(client.get).toHaveBeenCalledTimes(1); }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - await getSuccess(type, id, { namespace }); + await getSuccess(client, repository, registry, type, id, { namespace }); expect(client.get).toHaveBeenCalledWith( expect.objectContaining({ id: `${namespace}:${type}:${id}`, @@ -4421,7 +3908,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - await getSuccess(type, id); + await getSuccess(client, repository, registry, type, id); expect(client.get).toHaveBeenCalledWith( expect.objectContaining({ id: `${type}:${id}`, @@ -4431,7 +3918,7 @@ describe('SavedObjectsRepository', () => { }); it(`normalizes options.namespace from 'default' to undefined`, async () => { - await getSuccess(type, id, { namespace: 'default' }); + await getSuccess(client, repository, registry, type, id, { namespace: 'default' }); expect(client.get).toHaveBeenCalledWith( expect.objectContaining({ id: `${type}:${id}`, @@ -4441,7 +3928,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - await getSuccess(NAMESPACE_AGNOSTIC_TYPE, id, { namespace }); + await getSuccess(client, repository, registry, NAMESPACE_AGNOSTIC_TYPE, id, { namespace }); expect(client.get).toHaveBeenCalledWith( expect.objectContaining({ id: `${NAMESPACE_AGNOSTIC_TYPE}:${id}`, @@ -4450,7 +3937,9 @@ describe('SavedObjectsRepository', () => { ); client.get.mockClear(); - await getSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, { namespace }); + await getSuccess(client, repository, registry, MULTI_NAMESPACE_ISOLATED_TYPE, id, { + namespace, + }); expect(client.get).toHaveBeenCalledWith( expect.objectContaining({ id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${id}`, @@ -4466,15 +3955,15 @@ describe('SavedObjectsRepository', () => { id: string, options?: SavedObjectsBaseOptions ) => { - await expect(savedObjectsRepository.get(type, id, options)).rejects.toThrowError( - createGenericNotFoundError(type, id) + await expect(repository.get(type, id, options)).rejects.toThrowError( + createGenericNotFoundErrorPayload(type, id) ); }; it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.get(type, id, { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.get(type, id, { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`throws when type is invalid`, async () => { @@ -4508,7 +3997,11 @@ describe('SavedObjectsRepository', () => { }); it(`throws when type is multi-namespace and the document exists, but not in this namespace`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_ISOLATED_TYPE, id }, namespace); + const response = getMockGetResponse( + registry, + { type: MULTI_NAMESPACE_ISOLATED_TYPE, id }, + namespace + ); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -4521,7 +4014,7 @@ describe('SavedObjectsRepository', () => { describe('returns', () => { it(`formats the ES response`, async () => { - const result = await getSuccess(type, id); + const result = await getSuccess(client, repository, registry, type, id); expect(result).toEqual({ id, type, @@ -4536,21 +4029,27 @@ describe('SavedObjectsRepository', () => { }); it(`includes namespaces if type is multi-namespace`, async () => { - const result = await getSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id); + const result = await getSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_ISOLATED_TYPE, + id + ); expect(result).toMatchObject({ namespaces: expect.any(Array), }); }); it(`include namespaces if type is not multi-namespace`, async () => { - const result = await getSuccess(type, id); + const result = await getSuccess(client, repository, registry, type, id); expect(result).toMatchObject({ namespaces: ['default'], }); }); it(`includes originId property if present in cluster call response`, async () => { - const result = await getSuccess(type, id, {}, true); + const result = await getSuccess(client, repository, registry, type, id, {}, originId); expect(result).toMatchObject({ originId }); }); }); @@ -4568,9 +4067,7 @@ describe('SavedObjectsRepository', () => { }; mockInternalBulkResolve.mockResolvedValue({ resolved_objects: [expectedResult] }); - await expect(savedObjectsRepository.resolve('obj-type', 'obj-id')).resolves.toEqual( - expectedResult - ); + await expect(repository.resolve('obj-type', 'obj-id')).resolves.toEqual(expectedResult); expect(mockInternalBulkResolve).toHaveBeenCalledTimes(1); expect(mockInternalBulkResolve).toHaveBeenCalledWith( expect.objectContaining({ objects: [{ type: 'obj-type', id: 'obj-id' }] }) @@ -4582,14 +4079,14 @@ describe('SavedObjectsRepository', () => { const expectedResult: InternalBulkResolveError = { type: 'obj-type', id: 'obj-id', error }; mockInternalBulkResolve.mockResolvedValue({ resolved_objects: [expectedResult] }); - await expect(savedObjectsRepository.resolve('foo', '2')).rejects.toEqual(error); + await expect(repository.resolve('foo', '2')).rejects.toEqual(error); }); it('throws when internalBulkResolve throws', async () => { const error = new Error('Oh no!'); mockInternalBulkResolve.mockRejectedValue(error); - await expect(savedObjectsRepository.resolve('foo', '2')).rejects.toEqual(error); + await expect(repository.resolve('foo', '2')).rejects.toEqual(error); }); }); @@ -4611,7 +4108,7 @@ describe('SavedObjectsRepository', () => { const isMultiNamespace = registry.isMultiNamespace(type); if (isMultiNamespace) { const response = - mockGetResponseValue ?? getMockGetResponse({ type, id }, options?.namespace); + mockGetResponseValue ?? getMockGetResponse(registry, { type, id }, options?.namespace); client.get.mockResponseOnce(response); } @@ -4639,7 +4136,7 @@ describe('SavedObjectsRepository', () => { }; }); - const result = await savedObjectsRepository.incrementCounter(type, id, fields, options); + const result = await repository.incrementCounter(type, id, fields, options); expect(client.get).toHaveBeenCalledTimes(isMultiNamespace ? 1 : 0); return result; }; @@ -4776,24 +4273,24 @@ describe('SavedObjectsRepository', () => { id: string, field: Array ) => { - await expect(savedObjectsRepository.incrementCounter(type, id, field)).rejects.toThrowError( - createUnsupportedTypeError(type) + await expect(repository.incrementCounter(type, id, field)).rejects.toThrowError( + createUnsupportedTypeErrorPayload(type) ); }; it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.incrementCounter(type, id, counterFields, { + repository.incrementCounter(type, id, counterFields, { namespace: ALL_NAMESPACES_STRING, }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`throws when type is not a string`, async () => { const test = async (type: unknown) => { await expect( // @ts-expect-error type is supposed to be a string - savedObjectsRepository.incrementCounter(type, id, counterFields) + repository.incrementCounter(type, id, counterFields) ).rejects.toThrowError(`"type" argument must be a string`); expect(client.update).not.toHaveBeenCalled(); }; @@ -4805,9 +4302,9 @@ describe('SavedObjectsRepository', () => { }); it(`throws when id is empty`, async () => { - await expect( - savedObjectsRepository.incrementCounter(type, '', counterFields) - ).rejects.toThrowError(createBadRequestError('id cannot be empty')); + await expect(repository.incrementCounter(type, '', counterFields)).rejects.toThrowError( + createBadRequestErrorPayload('id cannot be empty') + ); expect(client.update).not.toHaveBeenCalled(); }); @@ -4815,7 +4312,7 @@ describe('SavedObjectsRepository', () => { const test = async (field: unknown[]) => { await expect( // @ts-expect-error field is of wrong type - savedObjectsRepository.incrementCounter(type, id, field) + repository.incrementCounter(type, id, field) ).rejects.toThrowError( `"counterFields" argument must be of type Array` ); @@ -4842,6 +4339,7 @@ describe('SavedObjectsRepository', () => { it(`throws when there is a conflict with an existing multi-namespace saved object (get)`, async () => { const response = getMockGetResponse( + registry, { type: MULTI_NAMESPACE_ISOLATED_TYPE, id }, 'bar-namespace' ); @@ -4849,15 +4347,10 @@ describe('SavedObjectsRepository', () => { elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); await expect( - savedObjectsRepository.incrementCounter( - MULTI_NAMESPACE_ISOLATED_TYPE, - id, - counterFields, - { - namespace, - } - ) - ).rejects.toThrowError(createConflictError(MULTI_NAMESPACE_ISOLATED_TYPE, id)); + repository.incrementCounter(MULTI_NAMESPACE_ISOLATED_TYPE, id, counterFields, { + namespace, + }) + ).rejects.toThrowError(createConflictErrorPayload(MULTI_NAMESPACE_ISOLATED_TYPE, id)); expect(client.get).toHaveBeenCalledTimes(1); expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); expect(client.update).not.toHaveBeenCalled(); @@ -4873,13 +4366,10 @@ describe('SavedObjectsRepository', () => { { type: 'foo', id: 'bar', error: { type: 'aliasConflict' } }, ]); await expect( - savedObjectsRepository.incrementCounter( - MULTI_NAMESPACE_ISOLATED_TYPE, - id, - counterFields, - { namespace } - ) - ).rejects.toThrowError(createConflictError(MULTI_NAMESPACE_ISOLATED_TYPE, id)); + repository.incrementCounter(MULTI_NAMESPACE_ISOLATED_TYPE, id, counterFields, { + namespace, + }) + ).rejects.toThrowError(createConflictErrorPayload(MULTI_NAMESPACE_ISOLATED_TYPE, id)); expect(client.get).toHaveBeenCalledTimes(1); expect(mockPreflightCheckForCreate).toHaveBeenCalledTimes(1); expect(client.update).not.toHaveBeenCalled(); @@ -4949,7 +4439,7 @@ describe('SavedObjectsRepository', () => { }; }); - const response = await savedObjectsRepository.incrementCounter( + const response = await repository.incrementCounter( 'config', '6.0.0-alpha1', ['buildNum', 'apiCallsCount'], @@ -5027,55 +4517,6 @@ describe('SavedObjectsRepository', () => { ]; const originId = 'some-origin-id'; - const mockUpdateResponse = ( - type: string, - id: string, - options?: SavedObjectsUpdateOptions, - includeOriginId?: boolean - ) => { - client.update.mockResponseOnce( - { - _id: `${type}:${id}`, - ...mockVersionProps, - result: 'updated', - // don't need the rest of the source for test purposes, just the namespace and namespaces attributes - get: { - _source: { - namespaces: [options?.namespace ?? 'default'], - namespace: options?.namespace, - - // "includeOriginId" is not an option for the operation; however, if the existing saved object contains an originId attribute, the - // operation will return it in the result. This flag is just used for test purposes to modify the mock cluster call response. - ...(includeOriginId && { originId }), - }, - }, - } as estypes.UpdateResponse, - { statusCode: 200 } - ); - }; - - const updateSuccess = async ( - type: string, - id: string, - attributes: T, - options?: SavedObjectsUpdateOptions, - internalOptions: { - includeOriginId?: boolean; - mockGetResponseValue?: estypes.GetResponse; - } = {} - ) => { - const { mockGetResponseValue, includeOriginId } = internalOptions; - if (registry.isMultiNamespace(type)) { - const mockGetResponse = - mockGetResponseValue ?? getMockGetResponse({ type, id }, options?.namespace); - client.get.mockResponseOnce(mockGetResponse, { statusCode: 200 }); - } - mockUpdateResponse(type, id, options, includeOriginId); - const result = await savedObjectsRepository.update(type, id, attributes, options); - expect(client.get).toHaveBeenCalledTimes(registry.isMultiNamespace(type) ? 1 : 0); - return result; - }; - beforeEach(() => { mockPreflightCheckForCreate.mockReset(); mockPreflightCheckForCreate.mockImplementation(({ objects }) => { @@ -5085,14 +4526,21 @@ describe('SavedObjectsRepository', () => { describe('client calls', () => { it(`should use the ES update action when type is not multi-namespace`, async () => { - await updateSuccess(type, id, attributes); + await updateSuccess(client, repository, registry, type, id, attributes); expect(client.get).not.toHaveBeenCalled(); expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); expect(client.update).toHaveBeenCalledTimes(1); }); it(`should use the ES get action then update action when type is multi-namespace`, async () => { - await updateSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes); + await updateSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_ISOLATED_TYPE, + id, + attributes + ); expect(client.get).toHaveBeenCalledTimes(1); expect(mockPreflightCheckForCreate).not.toHaveBeenCalled(); expect(client.update).toHaveBeenCalledTimes(1); @@ -5100,6 +4548,9 @@ describe('SavedObjectsRepository', () => { it(`should check for alias conflicts if a new multi-namespace object would be created`, async () => { await updateSuccess( + client, + repository, + registry, MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes, @@ -5112,7 +4563,7 @@ describe('SavedObjectsRepository', () => { }); it(`defaults to no references array`, async () => { - await updateSuccess(type, id, attributes); + await updateSuccess(client, repository, registry, type, id, attributes); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ body: { doc: expect.not.objectContaining({ references: expect.anything() }) }, @@ -5123,7 +4574,7 @@ describe('SavedObjectsRepository', () => { it(`accepts custom references array`, async () => { const test = async (references: SavedObjectReference[]) => { - await updateSuccess(type, id, attributes, { references }); + await updateSuccess(client, repository, registry, type, id, attributes, { references }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ body: { doc: expect.objectContaining({ references }) }, @@ -5138,7 +4589,7 @@ describe('SavedObjectsRepository', () => { }); it(`uses the 'upsertAttributes' option when specified for a single-namespace type`, async () => { - await updateSuccess(type, id, attributes, { + await updateSuccess(client, repository, registry, type, id, attributes, { upsert: { title: 'foo', description: 'bar', @@ -5163,8 +4614,8 @@ describe('SavedObjectsRepository', () => { it(`uses the 'upsertAttributes' option when specified for a multi-namespace type that does not exist`, async () => { const options = { upsert: { title: 'foo', description: 'bar' } }; - mockUpdateResponse(MULTI_NAMESPACE_ISOLATED_TYPE, id, options); - await savedObjectsRepository.update(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes, options); + mockUpdateResponse(client, MULTI_NAMESPACE_ISOLATED_TYPE, id, options); + await repository.update(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes, options); expect(client.get).toHaveBeenCalledTimes(1); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ @@ -5185,7 +4636,15 @@ describe('SavedObjectsRepository', () => { it(`ignores use the 'upsertAttributes' option when specified for a multi-namespace type that already exists`, async () => { const options = { upsert: { title: 'foo', description: 'bar' } }; - await updateSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes, options); + await updateSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_ISOLATED_TYPE, + id, + attributes, + options + ); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:logstash-*`, @@ -5200,7 +4659,7 @@ describe('SavedObjectsRepository', () => { it(`doesn't accept custom references if not an array`, async () => { const test = async (references: unknown) => { // @ts-expect-error references is unknown - await updateSuccess(type, id, attributes, { references }); + await updateSuccess(client, repository, registry, type, id, attributes, { references }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ body: { doc: expect.not.objectContaining({ references: expect.anything() }) }, @@ -5216,7 +4675,7 @@ describe('SavedObjectsRepository', () => { }); it(`defaults to a refresh setting of wait_for`, async () => { - await updateSuccess(type, id, { foo: 'bar' }); + await updateSuccess(client, repository, registry, type, id, { foo: 'bar' }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ refresh: 'wait_for', @@ -5226,7 +4685,15 @@ describe('SavedObjectsRepository', () => { }); it(`does not default to the version of the existing document when type is multi-namespace`, async () => { - await updateSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes, { references }); + await updateSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_ISOLATED_TYPE, + id, + attributes, + { references } + ); const versionProperties = { if_seq_no: mockVersionProps._seq_no, if_primary_term: mockVersionProps._primary_term, @@ -5238,7 +4705,7 @@ describe('SavedObjectsRepository', () => { }); it(`accepts version`, async () => { - await updateSuccess(type, id, attributes, { + await updateSuccess(client, repository, registry, type, id, attributes, { version: encodeHitVersion({ _seq_no: 100, _primary_term: 200 }), }); expect(client.update).toHaveBeenCalledWith( @@ -5248,7 +4715,7 @@ describe('SavedObjectsRepository', () => { }); it('default to a `retry_on_conflict` setting of `3` when `version` is not provided', async () => { - await updateSuccess(type, id, attributes, {}); + await updateSuccess(client, repository, registry, type, id, attributes, {}); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ retry_on_conflict: 3 }), expect.anything() @@ -5256,7 +4723,7 @@ describe('SavedObjectsRepository', () => { }); it('default to a `retry_on_conflict` setting of `0` when `version` is provided', async () => { - await updateSuccess(type, id, attributes, { + await updateSuccess(client, repository, registry, type, id, attributes, { version: encodeHitVersion({ _seq_no: 100, _primary_term: 200 }), }); expect(client.update).toHaveBeenCalledWith( @@ -5266,7 +4733,7 @@ describe('SavedObjectsRepository', () => { }); it('accepts a `retryOnConflict` option', async () => { - await updateSuccess(type, id, attributes, { + await updateSuccess(client, repository, registry, type, id, attributes, { version: encodeHitVersion({ _seq_no: 100, _primary_term: 200 }), retryOnConflict: 42, }); @@ -5277,7 +4744,7 @@ describe('SavedObjectsRepository', () => { }); it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - await updateSuccess(type, id, attributes, { namespace }); + await updateSuccess(client, repository, registry, type, id, attributes, { namespace }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: expect.stringMatching(`${namespace}:${type}:${id}`) }), expect.anything() @@ -5285,7 +4752,7 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - await updateSuccess(type, id, attributes, { references }); + await updateSuccess(client, repository, registry, type, id, attributes, { references }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: expect.stringMatching(`${type}:${id}`) }), expect.anything() @@ -5293,7 +4760,10 @@ describe('SavedObjectsRepository', () => { }); it(`normalizes options.namespace from 'default' to undefined`, async () => { - await updateSuccess(type, id, attributes, { references, namespace: 'default' }); + await updateSuccess(client, repository, registry, type, id, attributes, { + references, + namespace: 'default', + }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: expect.stringMatching(`${type}:${id}`) }), expect.anything() @@ -5301,7 +4771,9 @@ describe('SavedObjectsRepository', () => { }); it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - await updateSuccess(NAMESPACE_AGNOSTIC_TYPE, id, attributes, { namespace }); + await updateSuccess(client, repository, registry, NAMESPACE_AGNOSTIC_TYPE, id, attributes, { + namespace, + }); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: expect.stringMatching(`${NAMESPACE_AGNOSTIC_TYPE}:${id}`), @@ -5310,7 +4782,15 @@ describe('SavedObjectsRepository', () => { ); client.update.mockClear(); - await updateSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes, { namespace }); + await updateSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_ISOLATED_TYPE, + id, + attributes, + { namespace } + ); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ id: expect.stringMatching(`${MULTI_NAMESPACE_ISOLATED_TYPE}:${id}`), @@ -5320,7 +4800,14 @@ describe('SavedObjectsRepository', () => { }); it(`includes _source_includes when type is multi-namespace`, async () => { - await updateSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes); + await updateSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_ISOLATED_TYPE, + id, + attributes + ); expect(client.update).toHaveBeenCalledWith( expect.objectContaining({ _source_includes: ['namespace', 'namespaces', 'originId'] }), expect.anything() @@ -5328,7 +4815,7 @@ describe('SavedObjectsRepository', () => { }); it(`includes _source_includes when type is not multi-namespace`, async () => { - await updateSuccess(type, id, attributes); + await updateSuccess(client, repository, registry, type, id, attributes); expect(client.update).toHaveBeenLastCalledWith( expect.objectContaining({ _source_includes: ['namespace', 'namespaces', 'originId'], @@ -5340,15 +4827,15 @@ describe('SavedObjectsRepository', () => { describe('errors', () => { const expectNotFoundError = async (type: string, id: string) => { - await expect(savedObjectsRepository.update(type, id, {})).rejects.toThrowError( - createGenericNotFoundError(type, id) + await expect(repository.update(type, id, {})).rejects.toThrowError( + createGenericNotFoundErrorPayload(type, id) ); }; it(`throws when options.namespace is '*'`, async () => { await expect( - savedObjectsRepository.update(type, id, attributes, { namespace: ALL_NAMESPACES_STRING }) - ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); + repository.update(type, id, attributes, { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError(createBadRequestErrorPayload('"options.namespace" cannot be "*"')); }); it(`throws when type is invalid`, async () => { @@ -5362,8 +4849,8 @@ describe('SavedObjectsRepository', () => { }); it(`throws when id is empty`, async () => { - await expect(savedObjectsRepository.update(type, '', attributes)).rejects.toThrowError( - createBadRequestError('id cannot be empty') + await expect(repository.update(type, '', attributes)).rejects.toThrowError( + createBadRequestErrorPayload('id cannot be empty') ); expect(client.update).not.toHaveBeenCalled(); }); @@ -5390,7 +4877,11 @@ describe('SavedObjectsRepository', () => { }); it(`throws when type is multi-namespace and the document exists, but not in this namespace`, async () => { - const response = getMockGetResponse({ type: MULTI_NAMESPACE_ISOLATED_TYPE, id }, namespace); + const response = getMockGetResponse( + registry, + { type: MULTI_NAMESPACE_ISOLATED_TYPE, id }, + namespace + ); client.get.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(response) ); @@ -5408,7 +4899,7 @@ describe('SavedObjectsRepository', () => { { type: 'type', id: 'id', error: { type: 'aliasConflict' } }, ]); await expect( - savedObjectsRepository.update( + repository.update( MULTI_NAMESPACE_ISOLATED_TYPE, id, { attr: 'value' }, @@ -5419,7 +4910,7 @@ describe('SavedObjectsRepository', () => { }, } ) - ).rejects.toThrowError(createConflictError(MULTI_NAMESPACE_ISOLATED_TYPE, id)); + ).rejects.toThrowError(createConflictErrorPayload(MULTI_NAMESPACE_ISOLATED_TYPE, id)); expect(client.get).toHaveBeenCalledTimes(1); expect(mockPreflightCheckForCreate).toHaveBeenCalledTimes(1); expect(client.update).not.toHaveBeenCalled(); @@ -5430,6 +4921,9 @@ describe('SavedObjectsRepository', () => { { type: 'type', id: 'id', error: { type: 'conflict' } }, ]); await updateSuccess( + client, + repository, + registry, MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes, @@ -5458,7 +4952,7 @@ describe('SavedObjectsRepository', () => { describe('returns', () => { it(`returns _seq_no and _primary_term encoded as version`, async () => { - const result = await updateSuccess(type, id, attributes, { + const result = await updateSuccess(client, repository, registry, type, id, attributes, { namespace, references, }); @@ -5474,21 +4968,37 @@ describe('SavedObjectsRepository', () => { }); it(`includes namespaces if type is multi-namespace`, async () => { - const result = await updateSuccess(MULTI_NAMESPACE_ISOLATED_TYPE, id, attributes); + const result = await updateSuccess( + client, + repository, + registry, + MULTI_NAMESPACE_ISOLATED_TYPE, + id, + attributes + ); expect(result).toMatchObject({ namespaces: expect.any(Array), }); }); it(`includes namespaces if type is not multi-namespace`, async () => { - const result = await updateSuccess(type, id, attributes); + const result = await updateSuccess(client, repository, registry, type, id, attributes); expect(result).toMatchObject({ namespaces: ['default'], }); }); it(`includes originId property if present in cluster call response`, async () => { - const result = await updateSuccess(type, id, attributes, {}, { includeOriginId: true }); + const result = await updateSuccess( + client, + repository, + registry, + type, + id, + attributes, + {}, + { originId } + ); expect(result).toMatchObject({ originId }); }); }); @@ -5500,7 +5010,7 @@ describe('SavedObjectsRepository', () => { const generateResults = (id?: string) => ({ id: id || 'id' }); const successResponse = async (type: string, options?: SavedObjectsOpenPointInTimeOptions) => { client.openPointInTime.mockResponseOnce(generateResults()); - const result = await savedObjectsRepository.openPointInTimeForType(type, options); + const result = await repository.openPointInTimeForType(type, options); expect(client.openPointInTime).toHaveBeenCalledTimes(1); return result; }; @@ -5544,8 +5054,8 @@ describe('SavedObjectsRepository', () => { describe('errors', () => { const expectNotFoundError = async (types: string | string[]) => { - await expect(savedObjectsRepository.openPointInTimeForType(types)).rejects.toThrowError( - createGenericNotFoundError() + await expect(repository.openPointInTimeForType(types)).rejects.toThrowError( + createGenericNotFoundErrorPayload() ); }; @@ -5579,7 +5089,7 @@ describe('SavedObjectsRepository', () => { client.openPointInTime.mockResolvedValueOnce( elasticsearchClientMock.createSuccessTransportRequestPromise(results) ); - const response = await savedObjectsRepository.openPointInTimeForType(type); + const response = await repository.openPointInTimeForType(type); expect(response).toEqual({ id }); }); }); @@ -5589,7 +5099,7 @@ describe('SavedObjectsRepository', () => { const generateResults = () => ({ succeeded: true, num_freed: 3 }); const successResponse = async (id: string) => { client.closePointInTime.mockResponseOnce(generateResults()); - const result = await savedObjectsRepository.closePointInTime(id); + const result = await repository.closePointInTime(id); expect(client.closePointInTime).toHaveBeenCalledTimes(1); return result; }; @@ -5617,7 +5127,7 @@ describe('SavedObjectsRepository', () => { it(`returns response body from ES`, async () => { const results = generateResults(); client.closePointInTime.mockResponseOnce(results); - const response = await savedObjectsRepository.closePointInTime('abc123'); + const response = await repository.closePointInTime('abc123'); expect(response).toEqual(results); }); }); @@ -5625,7 +5135,7 @@ describe('SavedObjectsRepository', () => { describe('#createPointInTimeFinder', () => { it('returns a new PointInTimeFinder instance', async () => { - const result = await savedObjectsRepository.createPointInTimeFinder({ type: 'PIT' }); + const result = await repository.createPointInTimeFinder({ type: 'PIT' }); expect(result).toBeInstanceOf(PointInTimeFinder); }); @@ -5641,7 +5151,7 @@ describe('SavedObjectsRepository', () => { }, }; - await savedObjectsRepository.createPointInTimeFinder(options, dependencies); + await repository.createPointInTimeFinder(options, dependencies); expect(pointInTimeFinderMock).toHaveBeenCalledWith( options, expect.objectContaining({ @@ -5666,9 +5176,9 @@ describe('SavedObjectsRepository', () => { }; mockCollectMultiNamespaceReferences.mockResolvedValue(expectedResult); - await expect( - savedObjectsRepository.collectMultiNamespaceReferences(objects) - ).resolves.toEqual(expectedResult); + await expect(repository.collectMultiNamespaceReferences(objects)).resolves.toEqual( + expectedResult + ); expect(mockCollectMultiNamespaceReferences).toHaveBeenCalledTimes(1); expect(mockCollectMultiNamespaceReferences).toHaveBeenCalledWith( expect.objectContaining({ objects }) @@ -5679,9 +5189,7 @@ describe('SavedObjectsRepository', () => { const expectedResult = new Error('Oh no!'); mockCollectMultiNamespaceReferences.mockRejectedValue(expectedResult); - await expect(savedObjectsRepository.collectMultiNamespaceReferences([])).rejects.toEqual( - expectedResult - ); + await expect(repository.collectMultiNamespaceReferences([])).rejects.toEqual(expectedResult); }); }); @@ -5707,7 +5215,7 @@ describe('SavedObjectsRepository', () => { mockUpdateObjectsSpaces.mockResolvedValue(expectedResult); await expect( - savedObjectsRepository.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options) + repository.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options) ).resolves.toEqual(expectedResult); expect(mockUpdateObjectsSpaces).toHaveBeenCalledTimes(1); expect(mockUpdateObjectsSpaces).toHaveBeenCalledWith( @@ -5719,9 +5227,7 @@ describe('SavedObjectsRepository', () => { const expectedResult = new Error('Oh no!'); mockUpdateObjectsSpaces.mockRejectedValue(expectedResult); - await expect(savedObjectsRepository.updateObjectsSpaces([], [], [])).rejects.toEqual( - expectedResult - ); + await expect(repository.updateObjectsSpaces([], [], [])).rejects.toEqual(expectedResult); }); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index f48e031bd23c48..c2fc7e69bda7cc 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -7,6 +7,7 @@ */ import { omit, isObject } from 'lodash'; +import Boom from '@hapi/boom'; import type { Payload } from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import * as esKuery from '@kbn/es-query'; @@ -18,7 +19,6 @@ import { } from '@kbn/core-elasticsearch-server-internal'; import type { SavedObject } from '@kbn/core-saved-objects-common'; import type { - ISavedObjectsRepository, SavedObjectsBaseOptions, SavedObjectsIncrementCounterOptions, SavedObjectsDeleteByNamespaceOptions, @@ -57,14 +57,23 @@ import type { SavedObjectsBulkDeleteObject, SavedObjectsBulkDeleteOptions, SavedObjectsBulkDeleteResponse, + SavedObjectsFindInternalOptions, } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectSanitizedDoc, SavedObjectsRawDoc, SavedObjectsRawDocSource, ISavedObjectTypeRegistry, + SavedObjectsExtensions, + ISavedObjectsEncryptionExtension, + ISavedObjectsSecurityExtension, + ISavedObjectsSpacesExtension, + AuditAction, + CheckAuthorizationResult, + AuthorizationTypeMap, } from '@kbn/core-saved-objects-server'; import { + DEFAULT_NAMESPACE_STRING, SavedObjectsErrorHelpers, type DecoratedError, } from '@kbn/core-saved-objects-utils-server'; @@ -91,7 +100,11 @@ import { PointInTimeFinder } from './point_in_time_finder'; import { createRepositoryEsClient, RepositoryEsClient } from './repository_es_client'; import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; -import { internalBulkResolve, InternalBulkResolveError } from './internal_bulk_resolve'; +import { + internalBulkResolve, + InternalBulkResolveError, + isBulkResolveError, +} from './internal_bulk_resolve'; import { validateConvertFilterToKueryNode } from './filter_utils'; import { validateAndConvertAggregations } from './aggregations'; import { @@ -111,6 +124,7 @@ import { updateObjectsSpaces } from './update_objects_spaces'; import { preflightCheckForCreate, PreflightCheckForCreateObject, + PreflightCheckForCreateResult, } from './preflight_check_for_create'; import { deleteLegacyUrlAliases } from './legacy_url_aliases'; import type { @@ -136,12 +150,21 @@ export interface SavedObjectsRepositoryOptions { migrator: IKibanaMigrator; allowedTypes: string[]; logger: Logger; + extensions: SavedObjectsExtensions | undefined; } export const DEFAULT_REFRESH_SETTING = 'wait_for'; export const DEFAULT_RETRY_COUNT = 3; const MAX_CONCURRENT_ALIAS_DELETIONS = 10; + +/** + * See {@link SavedObjectsRepository} + * + * @public + */ +export type ISavedObjectsRepository = Pick; + /** * @internal */ @@ -185,6 +208,9 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { private _registry: ISavedObjectTypeRegistry; private _allowedTypes: string[]; private readonly client: RepositoryEsClient; + private readonly _encryptionExtension: ISavedObjectsEncryptionExtension | undefined; + private readonly _securityExtension: ISavedObjectsSecurityExtension | undefined; + private readonly _spacesExtension: ISavedObjectsSpacesExtension | undefined; private _serializer: SavedObjectsSerializer; private _logger: Logger; @@ -203,6 +229,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { client: ElasticsearchClient, logger: Logger, includedHiddenTypes: string[] = [], + extensions?: SavedObjectsExtensions, + /** The injectedConstructor is only used for unit testing */ injectedConstructor: any = SavedObjectsRepository ): ISavedObjectsRepository { const mappings = migrator.getActiveMappings(); @@ -228,6 +256,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { allowedTypes, client, logger, + extensions, }); } @@ -241,6 +270,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { migrator, allowedTypes = [], logger, + extensions, } = options; // It's important that we migrate documents / mark them as up-to-date @@ -261,6 +291,9 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { this._allowedTypes = allowedTypes; this._serializer = serializer; this._logger = logger; + this._encryptionExtension = extensions?.encryptionExtension; + this._securityExtension = extensions?.securityExtension; + this._spacesExtension = extensions?.spacesExtension; } /** @@ -271,6 +304,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { attributes: T, options: SavedObjectsCreateOptions = {} ): Promise> { + const namespace = this.getCurrentNamespace(options.namespace); const { migrationVersion, coreMigrationVersion, @@ -280,21 +314,20 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { initialNamespaces, version, } = options; - const id = options.id || SavedObjectsUtils.generateId(); - const namespace = normalizeNamespace(options.namespace); - - this.validateInitialNamespaces(type, initialNamespaces); - this.validateOriginId(type, options); - if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } + const id = this.getValidId(type, options.id, options.version, options.overwrite); + this.validateInitialNamespaces(type, initialNamespaces); + this.validateOriginId(type, options); const time = getCurrentTime(); let savedObjectNamespace: string | undefined; let savedObjectNamespaces: string[] | undefined; let existingOriginId: string | undefined; + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + let preflightResult: PreflightCheckForCreateResult | undefined; if (this._registry.isSingleNamespace(type)) { savedObjectNamespace = initialNamespaces ? normalizeNamespace(initialNamespaces[0]) @@ -303,24 +336,55 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { if (options.id) { // we will overwrite a multi-namespace saved object if it exists; if that happens, ensure we preserve its included namespaces // note: this check throws an error if the object is found but does not exist in this namespace - const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); - const [{ error, existingDocument }] = await preflightCheckForCreate({ - registry: this._registry, - client: this.client, - serializer: this._serializer, - getIndexForType: this.getIndexForType.bind(this), - createPointInTimeFinder: this.createPointInTimeFinder.bind(this), - objects: [{ type, id, overwrite, namespaces: initialNamespaces ?? [namespaceString] }], - }); - if (error) { - throw SavedObjectsErrorHelpers.createConflictError(type, id); - } - savedObjectNamespaces = - initialNamespaces || getSavedObjectNamespaces(namespace, existingDocument); - existingOriginId = existingDocument?._source?.originId; - } else { - savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace); + preflightResult = ( + await preflightCheckForCreate({ + registry: this._registry, + client: this.client, + serializer: this._serializer, + getIndexForType: this.getIndexForType.bind(this), + createPointInTimeFinder: this.createPointInTimeFinder.bind(this), + objects: [{ type, id, overwrite, namespaces: initialNamespaces ?? [namespaceString] }], + }) + )[0]; } + savedObjectNamespaces = + initialNamespaces || getSavedObjectNamespaces(namespace, preflightResult?.existingDocument); + existingOriginId = preflightResult?.existingDocument?._source?.originId; + } + + const spacesToEnforce = new Set(initialNamespaces).add(namespaceString); // Always check/enforce authZ for the active space + const existingNamespaces = preflightResult?.existingDocument?._source?.namespaces || []; + const spacesToAuthorize = new Set(existingNamespaces); + spacesToAuthorize.delete(ALL_NAMESPACES_STRING); // Don't accidentally check for global privileges when the object exists in '*' + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set([type]), + spaces: new Set([...spacesToEnforce, ...spacesToAuthorize]), // existing namespaces are included so we can later redact if necessary + actions: ['create'], + // If a user tries to create an object with `initialNamespaces: ['*']`, they need to have 'create' privileges for the Global Resource + // (e.g., All privileges for All Spaces). + // Inversely, if a user tries to overwrite an object that already exists in '*', they don't need to 'create' privileges for the Global + // Resource, so in that case we have to filter out that string from spacesToAuthorize (because `allowGlobalResource: true` is used + // below.) + options: { allowGlobalResource: true }, + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces: new Map([[type, spacesToEnforce]]), + action: 'create', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => + this._securityExtension!.addAuditEvent({ + action: AuditAction.CREATE, + savedObject: { type, id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the create operation has not occurred yet + }), + }); + } + + if (preflightResult?.error) { + // This intentionally occurs _after_ the authZ enforcement (which may throw a 403 error earlier) + throw SavedObjectsErrorHelpers.createConflictError(type, id); } // 1. If the originId has been *explicitly set* in the options (defined or undefined), respect that. @@ -334,7 +398,12 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ...(savedObjectNamespace && { namespace: savedObjectNamespace }), ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), originId, - attributes, + attributes: await this.optionallyEncryptAttributes( + type, + id, + savedObjectNamespace, // if single namespace type, this is the first in initialNamespaces. If multi-namespace type this is options.namespace/current namespace. + attributes + ), migrationVersion, coreMigrationVersion, updated_at: time, @@ -370,10 +439,11 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(id, type); } - return this._rawToSavedObject({ - ...raw, - ...body, - }); + return this.optionallyDecryptAndRedactSingleResult( + this._rawToSavedObject({ ...raw, ...body }), + authorizationResult?.typeMap, + attributes + ); } /** @@ -383,27 +453,28 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { objects: Array>, options: SavedObjectsCreateOptions = {} ): Promise> { + const namespace = this.getCurrentNamespace(options.namespace); const { overwrite = false, refresh = DEFAULT_REFRESH_SETTING } = options; - const namespace = normalizeNamespace(options.namespace); const time = getCurrentTime(); let preflightCheckIndexCounter = 0; - const expectedResults = objects.map< - Either< - { type: string; id?: string; error: Payload }, - { - method: 'index' | 'create'; - object: SavedObjectsBulkCreateObject & { id: string }; - preflightCheckIndex?: number; - } - > - >((object) => { - const { type, id, initialNamespaces } = object; + type ExpectedResult = Either< + { type: string; id?: string; error: Payload }, + { + method: 'index' | 'create'; + object: SavedObjectsBulkCreateObject & { id: string }; + preflightCheckIndex?: number; + } + >; + const expectedResults = objects.map((object) => { + const { type, id: requestId, initialNamespaces, version } = object; let error: DecoratedError | undefined; + let id: string = ''; // Assign to make TS happy, the ID will be validated (or randomly generated if needed) during getValidId below if (!this._allowedTypes.includes(type)) { error = SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } else { try { + id = this.getValidId(type, requestId, version, overwrite); this.validateInitialNamespaces(type, initialNamespaces); this.validateOriginId(type, object); } catch (e) { @@ -414,26 +485,36 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { if (error) { return { tag: 'Left', - value: { id, type, error: errorContent(error) }, + value: { id: requestId, type, error: errorContent(error) }, }; } - const method = id && overwrite ? 'index' : 'create'; - const requiresNamespacesCheck = id && this._registry.isMultiNamespace(type); + const method = requestId && overwrite ? 'index' : 'create'; + const requiresNamespacesCheck = requestId && this._registry.isMultiNamespace(type); return { tag: 'Right', value: { method, - object: { ...object, id: object.id || SavedObjectsUtils.generateId() }, + object: { ...object, id }, ...(requiresNamespacesCheck && { preflightCheckIndex: preflightCheckIndexCounter++ }), }, }; }); + const validObjects = expectedResults.filter(isRight); + if (validObjects.length === 0) { + // We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. + return { + // Technically the returned array should only contain SavedObject results, but for errors this is not true (we cast to 'unknown' below) + saved_objects: expectedResults.map>( + ({ value }) => value as unknown as SavedObject + ), + }; + } + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); - const preflightCheckObjects = expectedResults - .filter(isRight) + const preflightCheckObjects = validObjects .filter(({ value }) => value.preflightCheckIndex !== undefined) .map(({ value }) => { const { type, id, initialNamespaces } = value.object; @@ -449,115 +530,168 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { objects: preflightCheckObjects, }); + const typesAndSpaces = new Map>(); + const spacesToAuthorize = new Set([namespaceString]); // Always check authZ for the active space + for (const { value } of validObjects) { + const { object, preflightCheckIndex: index } = value; + const preflightResult = index !== undefined ? preflightCheckResponse[index] : undefined; + + const spacesToEnforce = typesAndSpaces.get(object.type) ?? new Set([namespaceString]); // Always enforce authZ for the active space + for (const space of object.initialNamespaces ?? []) { + spacesToEnforce.add(space); + spacesToAuthorize.add(space); + } + typesAndSpaces.set(object.type, spacesToEnforce); + for (const space of preflightResult?.existingDocument?._source.namespaces ?? []) { + if (space === ALL_NAMESPACES_STRING) continue; // Don't accidentally check for global privileges when the object exists in '*' + spacesToAuthorize.add(space); // existing namespaces are included so we can later redact if necessary + } + } + + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: spacesToAuthorize, + actions: ['bulk_create'], + // If a user tries to create an object with `initialNamespaces: ['*']`, they need to have 'bulk_create' privileges for the Global + // Resource (e.g., All privileges for All Spaces). + // Inversely, if a user tries to overwrite an object that already exists in '*', they don't need to have 'bulk_create' privileges for the Global + // Resource, so in that case we have to filter out that string from spacesToAuthorize (because `allowGlobalResource: true` is used + // below.) + options: { allowGlobalResource: true }, + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces, + action: 'bulk_create', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + for (const { value } of validObjects) { + this._securityExtension!.addAuditEvent({ + action: AuditAction.CREATE, + savedObject: { type: value.object.type, id: value.object.id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the create operation has not occurred yet + }); + } + }, + }); + } + let bulkRequestIndexCounter = 0; const bulkCreateParams: object[] = []; - const expectedBulkResults = expectedResults.map< - Either< - { type: string; id?: string; error: Payload }, - { esRequestIndex: number; requestedId: string; rawMigratedDoc: SavedObjectsRawDoc } - > - >((expectedBulkGetResult) => { - if (isLeft(expectedBulkGetResult)) { - return expectedBulkGetResult; - } + type ExpectedBulkResult = Either< + { type: string; id?: string; error: Payload }, + { esRequestIndex: number; requestedId: string; rawMigratedDoc: SavedObjectsRawDoc } + >; + const expectedBulkResults = await Promise.all( + expectedResults.map>(async (expectedBulkGetResult) => { + if (isLeft(expectedBulkGetResult)) { + return expectedBulkGetResult; + } - let savedObjectNamespace: string | undefined; - let savedObjectNamespaces: string[] | undefined; - let existingOriginId: string | undefined; - let versionProperties; - const { - preflightCheckIndex, - object: { initialNamespaces, version, ...object }, - method, - } = expectedBulkGetResult.value; - if (preflightCheckIndex !== undefined) { - const preflightResult = preflightCheckResponse[preflightCheckIndex]; - const { type, id, existingDocument, error } = preflightResult; - if (error) { - const { metadata } = error; + let savedObjectNamespace: string | undefined; + let savedObjectNamespaces: string[] | undefined; + let existingOriginId: string | undefined; + let versionProperties; + const { + preflightCheckIndex, + object: { initialNamespaces, version, ...object }, + method, + } = expectedBulkGetResult.value; + if (preflightCheckIndex !== undefined) { + const preflightResult = preflightCheckResponse[preflightCheckIndex]; + const { type, id, existingDocument, error } = preflightResult; + if (error) { + const { metadata } = error; + return { + tag: 'Left', + value: { + id, + type, + error: { + ...errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)), + ...(metadata && { metadata }), + }, + }, + }; + } + savedObjectNamespaces = + initialNamespaces || getSavedObjectNamespaces(namespace, existingDocument); + versionProperties = getExpectedVersionProperties(version); + existingOriginId = existingDocument?._source?.originId; + } else { + if (this._registry.isSingleNamespace(object.type)) { + savedObjectNamespace = initialNamespaces + ? normalizeNamespace(initialNamespaces[0]) + : namespace; + } else if (this._registry.isMultiNamespace(object.type)) { + savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace); + } + versionProperties = getExpectedVersionProperties(version); + } + + // 1. If the originId has been *explicitly set* in the options (defined or undefined), respect that. + // 2. Otherwise, preserve the originId of the existing object that is being overwritten, if any. + const originId = Object.keys(object).includes('originId') + ? object.originId + : existingOriginId; + const migrated = this._migrator.migrateDocument({ + id: object.id, + type: object.type, + attributes: await this.optionallyEncryptAttributes( + object.type, + object.id, + savedObjectNamespace, // only used for multi-namespace object types + object.attributes + ), + migrationVersion: object.migrationVersion, + coreMigrationVersion: object.coreMigrationVersion, + ...(savedObjectNamespace && { namespace: savedObjectNamespace }), + ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), + updated_at: time, + references: object.references || [], + originId, + }) as SavedObjectSanitizedDoc; + + /** + * If a validation has been registered for this type, we run it against the migrated attributes. + * This is an imperfect solution because malformed attributes could have already caused the + * migration to fail, but it's the best we can do without devising a way to run validations + * inside the migration algorithm itself. + */ + try { + this.validateObjectAttributes(object.type, migrated); + } catch (error) { return { tag: 'Left', value: { - id, - type, - error: { - ...errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)), - ...(metadata && { metadata }), - }, + id: object.id, + type: object.type, + error, }, }; } - savedObjectNamespaces = - initialNamespaces || getSavedObjectNamespaces(namespace, existingDocument); - versionProperties = getExpectedVersionProperties(version); - existingOriginId = existingDocument?._source?.originId; - } else { - if (this._registry.isSingleNamespace(object.type)) { - savedObjectNamespace = initialNamespaces - ? normalizeNamespace(initialNamespaces[0]) - : namespace; - } else if (this._registry.isMultiNamespace(object.type)) { - savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces(namespace); - } - versionProperties = getExpectedVersionProperties(version); - } - // 1. If the originId has been *explicitly set* for the object (defined or undefined), respect that. - // 2. Otherwise, preserve the originId of the existing object that is being overwritten, if any. - const originId = Object.keys(object).includes('originId') - ? object.originId - : existingOriginId; - const migrated = this._migrator.migrateDocument({ - id: object.id, - type: object.type, - attributes: object.attributes, - migrationVersion: object.migrationVersion, - coreMigrationVersion: object.coreMigrationVersion, - ...(savedObjectNamespace && { namespace: savedObjectNamespace }), - ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), - updated_at: time, - references: object.references || [], - originId, - }) as SavedObjectSanitizedDoc; - - /** - * If a validation has been registered for this type, we run it against the migrated attributes. - * This is an imperfect solution because malformed attributes could have already caused the - * migration to fail, but it's the best we can do without devising a way to run validations - * inside the migration algorithm itself. - */ - try { - this.validateObjectAttributes(object.type, migrated); - } catch (error) { - return { - tag: 'Left', - value: { - id: object.id, - type: object.type, - error, - }, + const expectedResult = { + esRequestIndex: bulkRequestIndexCounter++, + requestedId: object.id, + rawMigratedDoc: this._serializer.savedObjectToRaw(migrated), }; - } - - const expectedResult = { - esRequestIndex: bulkRequestIndexCounter++, - requestedId: object.id, - rawMigratedDoc: this._serializer.savedObjectToRaw(migrated), - }; - bulkCreateParams.push( - { - [method]: { - _id: expectedResult.rawMigratedDoc._id, - _index: this.getIndexForType(object.type), - ...(overwrite && versionProperties), + bulkCreateParams.push( + { + [method]: { + _id: expectedResult.rawMigratedDoc._id, + _index: this.getIndexForType(object.type), + ...(overwrite && versionProperties), + }, }, - }, - expectedResult.rawMigratedDoc._source - ); + expectedResult.rawMigratedDoc._source + ); - return { tag: 'Right', value: expectedResult }; - }); + return { tag: 'Right', value: expectedResult }; + }) + ); const bulkResponse = bulkCreateParams.length ? await this.client.bulk({ @@ -567,7 +701,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }) : undefined; - return { + const result = { saved_objects: expectedBulkResults.map((expectedResult) => { if (isLeft(expectedResult)) { return expectedResult.value as any; @@ -590,6 +724,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }); }), }; + + return this.optionallyDecryptAndRedactBulkResult(result, authorizationResult?.typeMap, objects); } /** @@ -599,39 +735,64 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { objects: SavedObjectsCheckConflictsObject[] = [], options: SavedObjectsBaseOptions = {} ): Promise { + const namespace = this.getCurrentNamespace(options.namespace); + if (objects.length === 0) { return { errors: [] }; } - const namespace = normalizeNamespace(options.namespace); - let bulkGetRequestIndexCounter = 0; - const expectedBulkGetResults: Array, Record>> = - objects.map((object) => { - const { type, id } = object; - - if (!this._allowedTypes.includes(type)) { - return { - tag: 'Left', - value: { - id, - type, - error: errorContent(SavedObjectsErrorHelpers.createUnsupportedTypeError(type)), - }, - }; - } + type ExpectedBulkGetResult = Either< + { type: string; id: string; error: Payload }, + { type: string; id: string; esRequestIndex: number } + >; + const expectedBulkGetResults = objects.map((object) => { + const { type, id } = object; + if (!this._allowedTypes.includes(type)) { return { - tag: 'Right', + tag: 'Left', value: { - type, id, - esRequestIndex: bulkGetRequestIndexCounter++, + type, + error: errorContent(SavedObjectsErrorHelpers.createUnsupportedTypeError(type)), }, }; + } + + return { + tag: 'Right', + value: { + type, + id, + esRequestIndex: bulkGetRequestIndexCounter++, + }, + }; + }); + + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + const validObjects = expectedBulkGetResults.filter(isRight); + const typesAndSpaces = new Map>(); + for (const { value } of validObjects) { + typesAndSpaces.set(value.type, new Set([namespaceString])); // Always enforce authZ for the active space + } + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: new Set([namespaceString]), // Always check authZ for the active space + actions: ['bulk_create'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces, + action: 'bulk_create', + typeMap: authorizationResult.typeMap, + // auditCallback is intentionally omitted, this function in the previous Security SOC wrapper implementation + // did not have audit logging. This is primarily because it is only used internally during imports, it is not + // exposed in a public HTTP API }); + } - const bulkGetDocs = expectedBulkGetResults.filter(isRight).map(({ value: { type, id } }) => ({ + const bulkGetDocs = validObjects.map(({ value: { type, id } }) => ({ _id: this._serializer.generateRawId(namespace, type, id), _index: this.getIndexForType(type), _source: { includes: ['type', 'namespaces'] }, @@ -688,11 +849,36 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { * {@inheritDoc ISavedObjectsRepository.delete} */ async delete(type: string, id: string, options: SavedObjectsDeleteOptions = {}): Promise<{}> { + const namespace = this.getCurrentNamespace(options.namespace); + if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } + const { refresh = DEFAULT_REFRESH_SETTING, force } = options; - const namespace = normalizeNamespace(options.namespace); + + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + const typesAndSpaces = new Map>([[type, new Set([namespaceString])]]); // Always enforce authZ for the active space + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set([type]), + spaces: new Set([namespaceString]), // Always check authZ for the active space + actions: ['delete'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces, + action: 'delete', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + this._securityExtension!.addAuditEvent({ + action: AuditAction.DELETE, + savedObject: { type, id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the delete operation has not occurred yet + }); + }, + }); + } const rawId = this._serializer.generateRawId(namespace, type, id); let preflightResult: PreflightCheckNamespacesResult | undefined; @@ -1063,6 +1249,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { namespace: string, options: SavedObjectsDeleteByNamespaceOptions = {} ): Promise { + // This is not exposed on the SOC; authorization and audit logging is handled by the Spaces plugin if (!namespace || typeof namespace !== 'string' || namespace === '*') { throw new TypeError(`namespace is required, and must be a string that is not equal to '*'`); } @@ -1120,8 +1307,20 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { * {@inheritDoc ISavedObjectsRepository.find} */ async find( - options: SavedObjectsFindOptions + options: SavedObjectsFindOptions, + internalOptions: SavedObjectsFindInternalOptions = {} ): Promise> { + let namespaces!: string[]; + const { disableExtensions } = internalOptions; + if (disableExtensions || !this._spacesExtension) { + namespaces = options.namespaces ?? [DEFAULT_NAMESPACE_STRING]; + // If the consumer specified `namespaces: []`, throw a Bad Request error + if (namespaces.length === 0) + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.namespaces cannot be an empty array' + ); + } + const { search, defaultSearchOperator = 'OR', @@ -1138,41 +1337,23 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { sortField, sortOrder, fields, - namespaces, type, - typeToNamespacesMap, filter, preference, aggs, } = options; - if (!type && !typeToNamespacesMap) { + if (!type) { throw SavedObjectsErrorHelpers.createBadRequestError( 'options.type must be a string or an array of strings' ); - } else if (namespaces?.length === 0 && !typeToNamespacesMap) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'options.namespaces cannot be an empty array' - ); - } else if (type && typeToNamespacesMap) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'options.type must be an empty string when options.typeToNamespacesMap is used' - ); - } else if ((!namespaces || namespaces?.length) && typeToNamespacesMap) { - throw SavedObjectsErrorHelpers.createBadRequestError( - 'options.namespaces must be an empty array when options.typeToNamespacesMap is used' - ); } else if (preference?.length && pit) { throw SavedObjectsErrorHelpers.createBadRequestError( 'options.preference must be excluded when options.pit is used' ); } - const types = type - ? Array.isArray(type) - ? type - : [type] - : Array.from(typeToNamespacesMap!.keys()); + const types = Array.isArray(type) ? type : [type]; const allowedTypes = types.filter((t) => this._allowedTypes.includes(t)); if (allowedTypes.length === 0) { return SavedObjectsUtils.createEmptyFindResponse(options); @@ -1208,6 +1389,54 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { } } + if (!disableExtensions && this._spacesExtension) { + try { + namespaces = await this._spacesExtension.getSearchableNamespaces(options.namespaces); + } catch (err) { + if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { + // The user is not authorized to access any space, return an empty response. + return SavedObjectsUtils.createEmptyFindResponse(options); + } + throw err; + } + if (namespaces.length === 0) { + // The user is authorized to access *at least one space*, but not any of the spaces they requested; return an empty response. + return SavedObjectsUtils.createEmptyFindResponse(options); + } + } + + // We have to first do a "pre-authorization" check so that we can construct the search DSL accordingly + const spacesToPreauthorize = new Set(namespaces); + let typeToNamespacesMap: Map | undefined; + let preAuthorizationResult: CheckAuthorizationResult<'find'> | undefined; + if (!disableExtensions && this._securityExtension) { + preAuthorizationResult = await this._securityExtension.checkAuthorization({ + types: new Set(types), + spaces: spacesToPreauthorize, + actions: ['find'], + }); + if (preAuthorizationResult.status === 'unauthorized') { + // If the user is unauthorized to find *anything* they requested, return an empty response + this._securityExtension.addAuditEvent({ + action: AuditAction.FIND, + error: new Error(`User is unauthorized for any requested types/spaces.`), + // TODO: include object type(s) that were requested? + // requestedTypes: types, + // requestedSpaces: namespaces, + }); + return SavedObjectsUtils.createEmptyFindResponse(options); + } + if (preAuthorizationResult.status === 'partially_authorized') { + typeToNamespacesMap = new Map(); + for (const [objType, entry] of preAuthorizationResult.typeMap) { + if (!entry.find) continue; + // This ensures that the query DSL can filter only for object types that the user is authorized to access for a given space + const { authorizedSpaces, isGloballyAuthorized } = entry.find; + typeToNamespacesMap.set(objType, isGloballyAuthorized ? namespaces : authorizedSpaces); + } + } + } + const esOptions = { // If `pit` is provided, we drop the `index`, otherwise ES returns 400. index: pit ? undefined : this.getIndicesForTypes(allowedTypes), @@ -1234,7 +1463,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { sortField, sortOrder, namespaces, - typeToNamespacesMap, + typeToNamespacesMap, // If defined, this takes precedence over the `type` and `namespaces` fields hasReference, hasReferenceOperator, hasNoReference, @@ -1257,15 +1486,10 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { } // 404 is only possible here if the index is missing, which // we don't want to leak, see "404s from missing index" above - return { - page, - per_page: perPage, - total: 0, - saved_objects: [], - }; + return SavedObjectsUtils.createEmptyFindResponse(options); } - return { + const result = { ...(body.aggregations ? { aggregations: body.aggregations as unknown as A } : {}), page, per_page: perPage, @@ -1281,6 +1505,36 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ), pit_id: body.pit_id, } as SavedObjectsFindResponse; + + if (disableExtensions) { + return result; + } + + const spacesToAuthorize = new Set(spacesToPreauthorize); // only for namespace redaction + for (const { type: objType, id, namespaces: objectNamespaces = [] } of result.saved_objects) { + for (const space of objectNamespaces) { + spacesToAuthorize.add(space); + } + this._securityExtension?.addAuditEvent({ + action: AuditAction.FIND, + savedObject: { type: objType, id }, + }); + } + const authorizationResult = + spacesToAuthorize.size > spacesToPreauthorize.size + ? // If there are any namespaces in the object results that were not already checked during pre-authorization, we need *another* + // authorization check so we can correctly redact the object namespaces below. + await this._securityExtension?.checkAuthorization({ + types: new Set(types), + spaces: spacesToAuthorize, + actions: ['find'], + }) + : undefined; + + return this.optionallyDecryptAndRedactBulkResult( + result, + authorizationResult?.typeMap ?? preAuthorizationResult?.typeMap // If we made a second authorization check, use that one; otherwise, fall back to the pre-authorization check + ); } /** @@ -1290,23 +1544,44 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { objects: SavedObjectsBulkGetObject[] = [], options: SavedObjectsBaseOptions = {} ): Promise> { - const namespace = normalizeNamespace(options.namespace); + const namespace = this.getCurrentNamespace(options.namespace); if (objects.length === 0) { return { saved_objects: [] }; } + let availableSpacesPromise: Promise | undefined; + const getAvailableSpaces = async () => { + if (!availableSpacesPromise) { + // This function is only called below if the spaces extension is enabled, so we can safely use the non-null assertion operator here. + availableSpacesPromise = this._spacesExtension!.getSearchableNamespaces([ + ALL_NAMESPACES_STRING, + ]).catch((err) => { + if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { + // the user doesn't have access to any spaces; return the current space ID and allow the SOR authZ check to fail + return [SavedObjectsUtils.namespaceIdToString(namespace)]; + } else { + throw err; + } + }); + } + return availableSpacesPromise; + }; let bulkGetRequestIndexCounter = 0; - const expectedBulkGetResults: Array, Record>> = - objects.map((object) => { - const { type, id, fields, namespaces } = object; + type ExpectedBulkGetResult = Either< + { type: string; id: string; error: Payload }, + { type: string; id: string; fields?: string[]; namespaces?: string[]; esRequestIndex: number } + >; + const expectedBulkGetResults = await Promise.all( + objects.map>(async (object) => { + const { type, id, fields } = object; let error: DecoratedError | undefined; if (!this._allowedTypes.includes(type)) { error = SavedObjectsErrorHelpers.createUnsupportedTypeError(type); } else { try { - this.validateObjectNamespaces(type, id, namespaces); + this.validateObjectNamespaces(type, id, object.namespaces); } catch (e) { error = e; } @@ -1319,6 +1594,10 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }; } + let namespaces = object.namespaces; + if (this._spacesExtension && namespaces?.includes(ALL_NAMESPACES_STRING)) { + namespaces = await getAvailableSpaces(); + } return { tag: 'Right', value: { @@ -1329,17 +1608,27 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { esRequestIndex: bulkGetRequestIndexCounter++, }, }; - }); + }) + ); + + const validObjects = expectedBulkGetResults.filter(isRight); + if (validObjects.length === 0) { + // We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. + return { + // Technically the returned array should only contain SavedObject results, but for errors this is not true (we cast to 'any' below) + saved_objects: expectedBulkGetResults.map>( + ({ value }) => value as unknown as SavedObject + ), + }; + } const getNamespaceId = (namespaces?: string[]) => namespaces !== undefined ? SavedObjectsUtils.namespaceStringToId(namespaces[0]) : namespace; - const bulkGetDocs = expectedBulkGetResults - .filter(isRight) - .map(({ value: { type, id, fields, namespaces } }) => ({ - _id: this._serializer.generateRawId(getNamespaceId(namespaces), type, id), // the namespace prefix is only used for single-namespace object types - _index: this.getIndexForType(type), - _source: { includes: includedFields(type, fields) }, - })); + const bulkGetDocs = validObjects.map(({ value: { type, id, fields, namespaces } }) => ({ + _id: this._serializer.generateRawId(getNamespaceId(namespaces), type, id), // the namespace prefix is only used for single-namespace object types + _index: this.getIndexForType(type), + _source: { includes: includedFields(type, fields) }, + })); const bulkGetResponse = bulkGetDocs.length ? await this.client.mget( { @@ -1361,7 +1650,9 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(); } - return { + const typesAndSpaces = new Map>(); + const spacesToAuthorize = new Set([SavedObjectsUtils.namespaceIdToString(namespace)]); // Always check authZ for the active space + const result = { saved_objects: expectedBulkGetResults.map((expectedResult) => { if (isLeft(expectedResult)) { return expectedResult.value as any; @@ -1370,11 +1661,24 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const { type, id, - namespaces = [SavedObjectsUtils.namespaceIdToString(namespace)], + namespaces = [SavedObjectsUtils.namespaceIdToString(namespace)], // set to default value for `rawDocExistsInNamespaces` check below esRequestIndex, } = expectedResult.value; const doc = bulkGetResponse?.body.docs[esRequestIndex]; + const spacesToEnforce = + typesAndSpaces.get(type) ?? new Set([SavedObjectsUtils.namespaceIdToString(namespace)]); // Always enforce authZ for the active space + for (const space of namespaces) { + spacesToEnforce.add(space); + typesAndSpaces.set(type, spacesToEnforce); + spacesToAuthorize.add(space); + } + + // @ts-expect-error MultiGetHit._source is optional + for (const space of doc?._source?.namespaces ?? []) { + spacesToAuthorize.add(space); // existing namespaces are included so we can later redact if necessary + } + // @ts-expect-error MultiGetHit._source is optional if (!doc?.found || !this.rawDocExistsInNamespaces(doc, namespaces)) { return { @@ -1388,6 +1692,31 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { return getSavedObjectFromSource(this._registry, type, id, doc); }), }; + + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: spacesToAuthorize, + actions: ['bulk_get'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces, + action: 'bulk_get', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + for (const { type, id, error: bulkError } of result.saved_objects) { + if (!error && !!bulkError) continue; // Only log success events for objects that were actually found (and are being returned to the user) + this._securityExtension!.addAuditEvent({ + action: AuditAction.GET, + savedObject: { type, id }, + error, + }); + } + }, + }); + } + + return this.optionallyDecryptAndRedactBulkResult(result, authorizationResult?.typeMap); } /** @@ -1397,6 +1726,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { objects: SavedObjectsBulkResolveObject[], options: SavedObjectsBaseOptions = {} ): Promise> { + const namespace = this.getCurrentNamespace(options.namespace); const { resolved_objects: bulkResults } = await internalBulkResolve({ registry: this._registry, allowedTypes: this._allowedTypes, @@ -1404,12 +1734,14 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { serializer: this._serializer, getIndexForType: this.getIndexForType.bind(this), incrementCounterInternal: this.incrementCounterInternal.bind(this), + encryptionExtension: this._encryptionExtension, + securityExtension: this._securityExtension, objects, - options, + options: { ...options, namespace }, }); const resolvedObjects = bulkResults.map>((result) => { // extract payloads from saved object errors - if ((result as InternalBulkResolveError).error) { + if (isBulkResolveError(result)) { const errorResult = result as InternalBulkResolveError; const { type, id, error } = errorResult; return { @@ -1417,7 +1749,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { outcome: 'exactMatch', }; } - return result as SavedObjectsResolveResponse; + return result; }); return { resolved_objects: resolvedObjects }; } @@ -1430,10 +1762,11 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { id: string, options: SavedObjectsBaseOptions = {} ): Promise> { + const namespace = this.getCurrentNamespace(options.namespace); + if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - const namespace = normalizeNamespace(options.namespace); const { body, statusCode, headers } = await this.client.get( { id: this._serializer.generateRawId(namespace, type, id), @@ -1447,6 +1780,31 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(type, id); } + const spacesToEnforce = new Set([SavedObjectsUtils.namespaceIdToString(namespace)]); // Always check/enforce authZ for the active space + const existingNamespaces = body?._source?.namespaces || []; + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set([type]), + spaces: new Set([...spacesToEnforce, ...existingNamespaces]), // existing namespaces are included so we can later redact if necessary + actions: ['get'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces: new Map([[type, spacesToEnforce]]), + action: 'get', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + if (error) { + this._securityExtension!.addAuditEvent({ + action: AuditAction.GET, + savedObject: { type, id }, + error, + }); + } + // Audit event for success case is added separately below + }, + }); + } + if ( !isFoundGetResponse(body) || indexNotFound || @@ -1455,7 +1813,15 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { // see "404s from missing index" above throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } - return getSavedObjectFromSource(this._registry, type, id, body); + // Only log a success event if the object was actually found (and is being returned to the user) + this._securityExtension?.addAuditEvent({ + action: AuditAction.GET, + savedObject: { type, id }, + }); + + const result = getSavedObjectFromSource(this._registry, type, id, body); + + return this.optionallyDecryptAndRedactSingleResult(result, authorizationResult?.typeMap); } /** @@ -1466,6 +1832,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { id: string, options: SavedObjectsBaseOptions = {} ): Promise> { + const namespace = this.getCurrentNamespace(options.namespace); const { resolved_objects: bulkResults } = await internalBulkResolve({ registry: this._registry, allowedTypes: this._allowedTypes, @@ -1473,14 +1840,16 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { serializer: this._serializer, getIndexForType: this.getIndexForType.bind(this), incrementCounterInternal: this.incrementCounterInternal.bind(this), + encryptionExtension: this._encryptionExtension, + securityExtension: this._securityExtension, objects: [{ type, id }], - options, + options: { ...options, namespace }, }); const [result] = bulkResults; - if ((result as InternalBulkResolveError).error) { - throw (result as InternalBulkResolveError).error; + if (isBulkResolveError(result)) { + throw result.error; } - return result as SavedObjectsResolveResponse; + return result; } /** @@ -1492,6 +1861,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { attributes: Partial, options: SavedObjectsUpdateOptions = {} ): Promise> { + const namespace = this.getCurrentNamespace(options.namespace); + if (!this._allowedTypes.includes(type)) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); } @@ -1506,7 +1877,6 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { refresh = DEFAULT_REFRESH_SETTING, retryOnConflict = version ? 0 : DEFAULT_RETRY_COUNT, } = options; - const namespace = normalizeNamespace(options.namespace); let preflightResult: PreflightCheckNamespacesResult | undefined; if (this._registry.isMultiNamespace(type)) { @@ -1515,20 +1885,42 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { id, namespace, }); - if ( - preflightResult.checkResult === 'found_outside_namespace' || - (!upsert && preflightResult.checkResult === 'not_found') - ) { - throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); - } - if (upsert && preflightResult.checkResult === 'not_found') { - // If an upsert would result in the creation of a new object, we need to check for alias conflicts too. - // This takes an extra round trip to Elasticsearch, but this won't happen often. - // TODO: improve performance by combining these into a single preflight check - await this.preflightCheckForUpsertAliasConflict(type, id, namespace); - } } + const spacesToEnforce = new Set([SavedObjectsUtils.namespaceIdToString(namespace)]); // Always check/enforce authZ for the active space + const existingNamespaces = preflightResult?.savedObjectNamespaces || []; + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set([type]), + spaces: new Set([...spacesToEnforce, ...existingNamespaces]), // existing namespaces are included so we can later redact if necessary + actions: ['update'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces: new Map([[type, spacesToEnforce]]), + action: 'update', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => + this._securityExtension!.addAuditEvent({ + action: AuditAction.UPDATE, + savedObject: { type, id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the update/upsert operation has not occurred yet + }), + }); + } + + if ( + preflightResult?.checkResult === 'found_outside_namespace' || + (!upsert && preflightResult?.checkResult === 'not_found') + ) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + if (upsert && preflightResult?.checkResult === 'not_found') { + // If an upsert would result in the creation of a new object, we need to check for alias conflicts too. + // This takes an extra round trip to Elasticsearch, but this won't happen often. + // TODO: improve performance by combining these into a single preflight check + await this.preflightCheckForUpsertAliasConflict(type, id, namespace); + } const time = getCurrentTime(); let rawUpsert: SavedObjectsRawDoc | undefined; @@ -1549,7 +1941,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ...(savedObjectNamespace && { namespace: savedObjectNamespace }), ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), attributes: { - ...upsert, + ...(await this.optionallyEncryptAttributes(type, id, options.namespace, upsert)), }, updated_at: time, }); @@ -1557,7 +1949,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { } const doc = { - [type]: attributes, + [type]: await this.optionallyEncryptAttributes(type, id, options.namespace, attributes), updated_at: time, ...(Array.isArray(references) && { references }), }; @@ -1595,7 +1987,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ]; } - return { + const result = { id, type, updated_at: time, @@ -1604,7 +1996,13 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ...(originId && { originId }), references, attributes, - }; + } as SavedObject; + + return this.optionallyDecryptAndRedactSingleResult( + result, + authorizationResult?.typeMap, + attributes + ); } /** @@ -1612,8 +2010,9 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { */ async collectMultiNamespaceReferences( objects: SavedObjectsCollectMultiNamespaceReferencesObject[], - options?: SavedObjectsCollectMultiNamespaceReferencesOptions + options: SavedObjectsCollectMultiNamespaceReferencesOptions = {} ) { + const namespace = this.getCurrentNamespace(options.namespace); return collectMultiNamespaceReferences({ registry: this._registry, allowedTypes: this._allowedTypes, @@ -1621,8 +2020,9 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { serializer: this._serializer, getIndexForType: this.getIndexForType.bind(this), createPointInTimeFinder: this.createPointInTimeFinder.bind(this), + securityExtension: this._securityExtension, objects, - options, + options: { ...options, namespace }, }); } @@ -1633,8 +2033,9 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { objects: SavedObjectsUpdateObjectsSpacesObject[], spacesToAdd: string[], spacesToRemove: string[], - options?: SavedObjectsUpdateObjectsSpacesOptions + options: SavedObjectsUpdateObjectsSpacesOptions = {} ) { + const namespace = this.getCurrentNamespace(options.namespace); return updateObjectsSpaces({ mappings: this._mappings, registry: this._registry, @@ -1643,10 +2044,11 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { serializer: this._serializer, logger: this._logger, getIndexForType: this.getIndexForType.bind(this), + securityExtension: this._securityExtension, objects, spacesToAdd, spacesToRemove, - options, + options: { ...options, namespace }, }); } @@ -1657,72 +2059,86 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { objects: Array>, options: SavedObjectsBulkUpdateOptions = {} ): Promise> { + const namespace = this.getCurrentNamespace(options.namespace); const time = getCurrentTime(); - const namespace = normalizeNamespace(options.namespace); let bulkGetRequestIndexCounter = 0; - const expectedBulkGetResults: Array, Record>> = - objects.map((object) => { - const { type, id } = object; - - if (!this._allowedTypes.includes(type)) { - return { - tag: 'Left', - value: { - id, - type, - error: errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)), - }, - }; + type DocumentToSave = Record; + type ExpectedBulkGetResult = Either< + { type: string; id: string; error: Payload }, + { + type: string; + id: string; + version?: string; + documentToSave: DocumentToSave; + objectNamespace?: string; + esRequestIndex?: number; + } + >; + const expectedBulkGetResults = objects.map((object) => { + const { type, id, attributes, references, version, namespace: objectNamespace } = object; + let error: DecoratedError | undefined; + if (!this._allowedTypes.includes(type)) { + error = SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } else { + try { + if (objectNamespace === ALL_NAMESPACES_STRING) { + error = SavedObjectsErrorHelpers.createBadRequestError('"namespace" cannot be "*"'); + } + } catch (e) { + error = e; } + } - const { attributes, references, version, namespace: objectNamespace } = object; + // `objectNamespace` is a namespace string, while `namespace` is a namespace ID. + // The object namespace string, if defined, will supersede the operation's namespace ID. - if (objectNamespace === ALL_NAMESPACES_STRING) { - return { - tag: 'Left', - value: { - id, - type, - error: errorContent( - SavedObjectsErrorHelpers.createBadRequestError('"namespace" cannot be "*"') - ), - }, - }; - } - // `objectNamespace` is a namespace string, while `namespace` is a namespace ID. - // The object namespace string, if defined, will supersede the operation's namespace ID. - - const documentToSave = { - [type]: attributes, - updated_at: time, - ...(Array.isArray(references) && { references }), + if (error) { + return { + tag: 'Left', + value: { id, type, error: errorContent(error) }, }; + } - const requiresNamespacesCheck = this._registry.isMultiNamespace(object.type); + const documentToSave = { + [type]: attributes, + updated_at: time, + ...(Array.isArray(references) && { references }), + }; - return { - tag: 'Right', - value: { - type, - id, - version, - documentToSave, - objectNamespace, - ...(requiresNamespacesCheck && { esRequestIndex: bulkGetRequestIndexCounter++ }), - }, - }; - }); + const requiresNamespacesCheck = this._registry.isMultiNamespace(object.type); + + return { + tag: 'Right', + value: { + type, + id, + version, + documentToSave, + objectNamespace, + ...(requiresNamespacesCheck && { esRequestIndex: bulkGetRequestIndexCounter++ }), + }, + }; + }); + const validObjects = expectedBulkGetResults.filter(isRight); + if (validObjects.length === 0) { + // We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. + return { + // Technically the returned array should only contain SavedObject results, but for errors this is not true (we cast to 'any' below) + saved_objects: expectedBulkGetResults.map>( + ({ value }) => value as unknown as SavedObject + ), + }; + } + + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); const getNamespaceId = (objectNamespace?: string) => objectNamespace !== undefined ? SavedObjectsUtils.namespaceStringToId(objectNamespace) : namespace; - const getNamespaceString = (objectNamespace?: string) => - objectNamespace ?? SavedObjectsUtils.namespaceIdToString(namespace); - - const bulkGetDocs = expectedBulkGetResults - .filter(isRight) + const getNamespaceString = (objectNamespace?: string) => objectNamespace ?? namespaceString; + const bulkGetDocs = validObjects .filter(({ value }) => value.esRequestIndex !== undefined) .map(({ value: { type, id, objectNamespace } }) => ({ _id: this._serializer.generateRawId(getNamespaceId(objectNamespace), type, id), @@ -1730,17 +2146,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { _source: ['type', 'namespaces'], })); const bulkGetResponse = bulkGetDocs.length - ? await this.client.mget( - { - body: { - docs: bulkGetDocs, - }, - }, - { - ignore: [404], - meta: true, - } - ) + ? await this.client.mget({ body: { docs: bulkGetDocs } }, { ignore: [404], meta: true }) : undefined; // fail fast if we can't verify a 404 response is from Elasticsearch if ( @@ -1753,72 +2159,139 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { throw SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(); } - let bulkUpdateRequestIndexCounter = 0; - const bulkUpdateParams: object[] = []; - const expectedBulkUpdateResults: Array, Record>> = - expectedBulkGetResults.map((expectedBulkGetResult) => { - if (isLeft(expectedBulkGetResult)) { - return expectedBulkGetResult; - } + const typesAndSpaces = new Map>(); + const spacesToAuthorize = new Set([namespaceString]); // Always check authZ for the active space + for (const { value } of validObjects) { + const { type, objectNamespace, esRequestIndex: index } = value; + const objectNamespaceString = getNamespaceString(objectNamespace); + const preflightResult = index !== undefined ? bulkGetResponse?.body.docs[index] : undefined; + + const spacesToEnforce = typesAndSpaces.get(type) ?? new Set([namespaceString]); // Always enforce authZ for the active space + spacesToEnforce.add(objectNamespaceString); + typesAndSpaces.set(type, spacesToEnforce); + spacesToAuthorize.add(objectNamespaceString); + // @ts-expect-error MultiGetHit._source is optional + for (const space of preflightResult?._source?.namespaces ?? []) { + spacesToAuthorize.add(space); // existing namespaces are included so we can later redact if necessary + } + } - const { esRequestIndex, id, type, version, documentToSave, objectNamespace } = - expectedBulkGetResult.value; + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: spacesToAuthorize, + actions: ['bulk_update'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces, + action: 'bulk_update', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + for (const { value } of validObjects) { + this._securityExtension!.addAuditEvent({ + action: AuditAction.UPDATE, + savedObject: { type: value.type, id: value.id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the update operation has not occurred yet + }); + } + }, + }); + } - let namespaces; - let versionProperties; - if (esRequestIndex !== undefined) { - const indexFound = bulkGetResponse?.statusCode !== 404; - const actualResult = indexFound ? bulkGetResponse?.body.docs[esRequestIndex] : undefined; - const docFound = indexFound && isMgetDoc(actualResult) && actualResult.found; - if ( - !docFound || - // @ts-expect-error MultiGetHit is incorrectly missing _id, _source - !this.rawDocExistsInNamespace(actualResult, getNamespaceId(objectNamespace)) - ) { - return { - tag: 'Left', - value: { - id, - type, - error: errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)), - }, - }; + let bulkUpdateRequestIndexCounter = 0; + const bulkUpdateParams: object[] = []; + type ExpectedBulkUpdateResult = Either< + { type: string; id: string; error: Payload }, + { + type: string; + id: string; + namespaces: string[]; + documentToSave: DocumentToSave; + esRequestIndex: number; + } + >; + const expectedBulkUpdateResults = await Promise.all( + expectedBulkGetResults.map>( + async (expectedBulkGetResult) => { + if (isLeft(expectedBulkGetResult)) { + return expectedBulkGetResult; } - // @ts-expect-error MultiGetHit is incorrectly missing _id, _source - namespaces = actualResult!._source.namespaces ?? [ + + const { esRequestIndex, id, type, version, documentToSave, objectNamespace } = + expectedBulkGetResult.value; + + let namespaces; + let versionProperties; + if (esRequestIndex !== undefined) { + const indexFound = bulkGetResponse?.statusCode !== 404; + const actualResult = indexFound + ? bulkGetResponse?.body.docs[esRequestIndex] + : undefined; + const docFound = indexFound && isMgetDoc(actualResult) && actualResult.found; + if ( + !docFound || + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + !this.rawDocExistsInNamespace(actualResult, getNamespaceId(objectNamespace)) + ) { + return { + tag: 'Left', + value: { + id, + type, + error: errorContent( + SavedObjectsErrorHelpers.createGenericNotFoundError(type, id) + ), + }, + }; + } // @ts-expect-error MultiGetHit is incorrectly missing _id, _source - SavedObjectsUtils.namespaceIdToString(actualResult!._source.namespace), - ]; - versionProperties = getExpectedVersionProperties(version); - } else { - if (this._registry.isSingleNamespace(type)) { - // if `objectNamespace` is undefined, fall back to `options.namespace` - namespaces = [getNamespaceString(objectNamespace)]; + namespaces = actualResult!._source.namespaces ?? [ + // @ts-expect-error MultiGetHit is incorrectly missing _id, _source + SavedObjectsUtils.namespaceIdToString(actualResult!._source.namespace), + ]; + versionProperties = getExpectedVersionProperties(version); + } else { + if (this._registry.isSingleNamespace(type)) { + // if `objectNamespace` is undefined, fall back to `options.namespace` + namespaces = [getNamespaceString(objectNamespace)]; + } + versionProperties = getExpectedVersionProperties(version); } - versionProperties = getExpectedVersionProperties(version); - } - const expectedResult = { - type, - id, - namespaces, - esRequestIndex: bulkUpdateRequestIndexCounter++, - documentToSave: expectedBulkGetResult.value.documentToSave, - }; + const expectedResult = { + type, + id, + namespaces, + esRequestIndex: bulkUpdateRequestIndexCounter++, + documentToSave: expectedBulkGetResult.value.documentToSave, + }; - bulkUpdateParams.push( - { - update: { - _id: this._serializer.generateRawId(getNamespaceId(objectNamespace), type, id), - _index: this.getIndexForType(type), - ...versionProperties, + bulkUpdateParams.push( + { + update: { + _id: this._serializer.generateRawId(getNamespaceId(objectNamespace), type, id), + _index: this.getIndexForType(type), + ...versionProperties, + }, }, - }, - { doc: documentToSave } - ); + { + doc: { + ...documentToSave, + [type]: await this.optionallyEncryptAttributes( + type, + id, + objectNamespace || namespace, + documentToSave[type] + ), + }, + } + ); - return { tag: 'Right', value: expectedResult }; - }); + return { tag: 'Right', value: expectedResult }; + } + ) + ); const { refresh = DEFAULT_REFRESH_SETTING } = options; const bulkUpdateResponse = bulkUpdateParams.length @@ -1830,7 +2303,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }) : undefined; - return { + const result = { saved_objects: expectedBulkUpdateResults.map((expectedResult) => { if (isLeft(expectedResult)) { return expectedResult.value as any; @@ -1865,6 +2338,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }; }), }; + + return this.optionallyDecryptAndRedactBulkResult(result, authorizationResult?.typeMap, objects); } /** @@ -1875,7 +2350,32 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { id: string, options: SavedObjectsRemoveReferencesToOptions = {} ): Promise { - const { namespace, refresh = true } = options; + const namespace = this.getCurrentNamespace(options.namespace); + const { refresh = true } = options; + + // TODO: Improve authorization and auditing (https://github.com/elastic/kibana/issues/135259) + + const spaces = new Set([SavedObjectsUtils.namespaceIdToString(namespace)]); // Always check/enforce authZ for the active space + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set([type]), + spaces, + actions: ['delete'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces: new Map([[type, spaces]]), + action: 'delete', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => + this._securityExtension!.addAuditEvent({ + action: AuditAction.REMOVE_REFERENCES, + savedObject: { type, id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the updateByQuery operation has not occurred yet + }), + }); + } + const allTypes = this._registry.getAllTypes().map((t) => t.name); // we need to target all SO indices as all types of objects may have references to the given SO. @@ -1941,6 +2441,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { counterFields: Array, options?: SavedObjectsIncrementCounterOptions ) { + // This is not exposed on the SOC, there are no authorization or audit logging checks if (typeof type !== 'string') { throw new Error('"type" argument must be a string'); } @@ -2107,14 +2608,70 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { */ async openPointInTimeForType( type: string | string[], - { keepAlive = '5m', preference }: SavedObjectsOpenPointInTimeOptions = {} + options: SavedObjectsOpenPointInTimeOptions = {}, + internalOptions: SavedObjectsFindInternalOptions = {} ): Promise { + const { disableExtensions } = internalOptions; + let namespaces!: string[]; + if (disableExtensions || !this._spacesExtension) { + namespaces = options.namespaces ?? [DEFAULT_NAMESPACE_STRING]; + // If the consumer specified `namespaces: []`, throw a Bad Request error + if (namespaces.length === 0) + throw SavedObjectsErrorHelpers.createBadRequestError( + 'options.namespaces cannot be an empty array' + ); + } + + const { keepAlive = '5m', preference } = options; const types = Array.isArray(type) ? type : [type]; const allowedTypes = types.filter((t) => this._allowedTypes.includes(t)); if (allowedTypes.length === 0) { throw SavedObjectsErrorHelpers.createGenericNotFoundError(); } + if (!disableExtensions && this._spacesExtension) { + try { + namespaces = await this._spacesExtension.getSearchableNamespaces(options.namespaces); + } catch (err) { + if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { + // The user is not authorized to access any space, throw a bad request error. + throw SavedObjectsErrorHelpers.createBadRequestError(); + } + throw err; + } + if (namespaces.length === 0) { + // The user is authorized to access *at least one space*, but not any of the spaces they requested; throw a bad request error. + throw SavedObjectsErrorHelpers.createBadRequestError(); + } + } + + if (!disableExtensions && this._securityExtension) { + const spaces = new Set(namespaces); + const preAuthorizationResult = await this._securityExtension.checkAuthorization({ + types: new Set(types), + spaces, + actions: ['open_point_in_time'], + }); + if (preAuthorizationResult.status === 'unauthorized') { + // If the user is unauthorized to find *anything* they requested, return an empty response + this._securityExtension.addAuditEvent({ + action: AuditAction.OPEN_POINT_IN_TIME, + error: new Error('User is unauthorized for any requested types/spaces.'), + // TODO: include object type(s) that were requested? + // requestedTypes: types, + // requestedSpaces: namespaces, + }); + throw SavedObjectsErrorHelpers.decorateForbiddenError(new Error('unauthorized')); + } + this._securityExtension.addAuditEvent({ + action: AuditAction.OPEN_POINT_IN_TIME, + outcome: 'unknown', + // TODO: include object type(s) that were requested? + // requestedTypes: types, + // requestedSpaces: namespaces, + }); + } + const esOptions = { index: this.getIndicesForTypes(allowedTypes), keep_alive: keepAlive, @@ -2144,8 +2701,18 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { */ async closePointInTime( id: string, - options?: SavedObjectsClosePointInTimeOptions + options?: SavedObjectsClosePointInTimeOptions, + internalOptions: SavedObjectsFindInternalOptions = {} ): Promise { + const { disableExtensions } = internalOptions; + + if (!disableExtensions && this._securityExtension) { + this._securityExtension.addAuditEvent({ + action: AuditAction.CLOSE_POINT_IN_TIME, + outcome: 'unknown', + }); + } + return await this.client.closePointInTime({ body: { id }, }); @@ -2156,12 +2723,14 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { */ createPointInTimeFinder( findOptions: SavedObjectsCreatePointInTimeFinderOptions, - dependencies?: SavedObjectsCreatePointInTimeFinderDependencies + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies, + internalOptions?: SavedObjectsFindInternalOptions ): ISavedObjectsPointInTimeFinder { return new PointInTimeFinder(findOptions, { logger: this._logger, client: this, ...dependencies, + /* internalOptions,*/ }); } @@ -2276,6 +2845,20 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { // any other error from this check does not matter } + /** + * If the spaces extension is enabled, we should use that to get the current namespace (and optionally throw an error if a consumer + * attempted to specify the namespace option). + * + * If the spaces extension is *not* enabled, we should simply normalize the namespace option so that `'default'` can be used + * interchangeably with `undefined`. + */ + private getCurrentNamespace(namespace?: string) { + if (this._spacesExtension) { + return this._spacesExtension.getCurrentNamespace(namespace); + } + return normalizeNamespace(namespace); + } + /** The `initialNamespaces` field (create, bulkCreate) is used to create an object in an initial set of spaces. */ private validateInitialNamespaces(type: string, initialNamespaces: string[] | undefined) { if (!initialNamespaces) { @@ -2353,6 +2936,95 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ); } } + + /** + * Saved objects with encrypted attributes should have IDs that are hard to guess, especially since IDs are part of the AAD used during + * encryption, that's why we control them within this function and don't allow consumers to specify their own IDs directly for encryptable + * types unless overwriting the original document. + */ + private getValidId( + type: string, + id: string | undefined, + version: string | undefined, + overwrite: boolean | undefined + ) { + if (!this._encryptionExtension?.isEncryptableType(type)) { + return id || SavedObjectsUtils.generateId(); + } + if (!id) { + return SavedObjectsUtils.generateId(); + } + // only allow a specified ID if we're overwriting an existing ESO with a Version + // this helps us ensure that the document really was previously created using ESO + // and not being used to get around the specified ID limitation + const canSpecifyID = (overwrite && version) || SavedObjectsUtils.isRandomId(id); + if (!canSpecifyID) { + throw SavedObjectsErrorHelpers.createBadRequestError( + 'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.' + ); + } + return id; + } + + private async optionallyEncryptAttributes( + type: string, + id: string, + namespaceOrNamespaces: string | string[] | undefined, + attributes: T + ): Promise { + if (!this._encryptionExtension?.isEncryptableType(type)) { + return attributes; + } + const namespace = Array.isArray(namespaceOrNamespaces) + ? namespaceOrNamespaces[0] + : namespaceOrNamespaces; + const descriptor = { type, id, namespace }; + return this._encryptionExtension.encryptAttributes( + descriptor, + attributes as Record + ) as unknown as T; + } + + private async optionallyDecryptAndRedactSingleResult( + object: SavedObject, + typeMap: AuthorizationTypeMap | undefined, + originalAttributes?: T + ) { + if (this._encryptionExtension?.isEncryptableType(object.type)) { + object = await this._encryptionExtension.decryptOrStripResponseAttributes( + object, + originalAttributes + ); + } + if (typeMap) { + return this._securityExtension!.redactNamespaces({ typeMap, savedObject: object }); + } + return object; + } + + private async optionallyDecryptAndRedactBulkResult< + T, + R extends { saved_objects: Array> }, + A extends string, + O extends Array<{ attributes: T }> + >(response: R, typeMap: AuthorizationTypeMap | undefined, originalObjects?: O) { + const modifiedObjects = await Promise.all( + response.saved_objects.map(async (object, index) => { + if (object.error) { + // If the bulk operation failed, the object will not have an attributes field at all, it will have an error field instead. + // In this case, don't attempt to decrypt, just return the object. + return object; + } + const originalAttributes = originalObjects?.[index].attributes; + return await this.optionallyDecryptAndRedactSingleResult( + object, + typeMap, + originalAttributes + ); + }) + ); + return { ...response, saved_objects: modifiedObjects }; + } } /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts index 564021482714db..16138f79a4bda9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts @@ -90,6 +90,7 @@ describe('SavedObjectsRepository#createRepository', () => { callAdminCluster, logger, [], + { encryptionExtension: undefined, securityExtension: undefined, spacesExtension: undefined }, SavedObjectsRepository ); expect(repository).toBeDefined(); @@ -109,6 +110,7 @@ describe('SavedObjectsRepository#createRepository', () => { callAdminCluster, logger, ['hiddenType', 'hiddenType', 'hiddenType'], + undefined, SavedObjectsRepository ); expect(repository).toBeDefined(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 541cc552dfbbac..e1cc0d0fbbf20a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -25,6 +25,20 @@ import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-inte import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import type { UpdateObjectsSpacesParams } from './update_objects_spaces'; import { updateObjectsSpaces } from './update_objects_spaces'; +import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; +import { extensionsMock } from './extensions.test.mock'; +import { + authMap, + checkAuthError, + enforceError, + mapsAreEqual, + setsAreEqual, + setupCheckAuthorized, + setupCheckUnauthorized, + setupEnforceFailure, + setupEnforceSuccess, + setupRedactPassthrough, +} from './repository.common.test'; type SetupParams = Partial< Pick @@ -67,7 +81,10 @@ describe('#updateObjectsSpaces', () => { let client: ReturnType; /** Sets up the type registry, saved objects client, etc. and return the full parameters object to be passed to `updateObjectsSpaces` */ - function setup({ objects = [], spacesToAdd = [], spacesToRemove = [], options }: SetupParams) { + function setup( + { objects = [], spacesToAdd = [], spacesToRemove = [], options }: SetupParams, + securityExtension?: ISavedObjectsSecurityExtension + ) { const registry = typeRegistryMock.create(); registry.isShareable.mockImplementation( (type) => [SHAREABLE_OBJ_TYPE, SHAREABLE_HIDDEN_OBJ_TYPE].includes(type) // NON_SHAREABLE_OBJ_TYPE is excluded @@ -82,6 +99,7 @@ describe('#updateObjectsSpaces', () => { serializer, logger: loggerMock.create(), getIndexForType: (type: string) => `index-for-${type}`, + securityExtension, objects, spacesToAdd, spacesToRemove, @@ -90,14 +108,16 @@ describe('#updateObjectsSpaces', () => { } /** Mocks the saved objects client so it returns the expected results */ - function mockMgetResults(...results: Array<{ found: boolean }>) { + function mockMgetResults( + ...results: Array<{ found: false } | { found: true; namespaces: string[] }> + ) { client.mget.mockResponseOnce({ docs: results.map((x) => x.found ? { _id: 'doesnt-matter', _index: 'doesnt-matter', - _source: { namespaces: [EXISTING_SPACE] }, + _source: { namespaces: x.namespaces }, ...VERSION_PROPS, found: true, } @@ -111,27 +131,8 @@ describe('#updateObjectsSpaces', () => { } /** Mocks the saved objects client so as to test unsupported server responding with 404 */ - function mockMgetResultsNotFound(...results: Array<{ found: boolean }>) { - client.mget.mockResponseOnce( - { - docs: results.map((x) => - x.found - ? { - _id: 'doesnt-matter', - _index: 'doesnt-matter', - _source: { namespaces: [EXISTING_SPACE] }, - ...VERSION_PROPS, - found: true, - } - : { - _id: 'doesnt-matter', - _index: 'doesnt-matter', - found: false, - } - ), - }, - { statusCode: 404, headers: {} } - ); + function mockMgetResultsNotFound() { + client.mget.mockResponseOnce({ docs: [] }, { statusCode: 404, headers: {} }); } /** Asserts that mget is called for the given objects */ @@ -220,7 +221,7 @@ describe('#updateObjectsSpaces', () => { const objects = [{ type: SHAREABLE_OBJ_TYPE, id: 'id-1' }]; const spacesToAdd = ['foo-space']; const params = setup({ objects, spacesToAdd }); - mockMgetResults({ found: true }); + mockMgetResults({ found: true, namespaces: [EXISTING_SPACE] }); client.bulk.mockReturnValueOnce( elasticsearchClientMock.createErrorTransportRequestPromise(new Error('bulk error')) ); @@ -231,39 +232,38 @@ describe('#updateObjectsSpaces', () => { it('returns mix of type errors, mget/bulk cluster errors, and successes', async () => { const obj1 = { type: SHAREABLE_HIDDEN_OBJ_TYPE, id: 'id-1' }; // invalid type (Not Found) const obj2 = { type: NON_SHAREABLE_OBJ_TYPE, id: 'id-2' }; // non-shareable type (Bad Request) - // obj3 below is mocking an example where the SOC attempted to retrieve it in a pre-flight request but it was not found. - // Since it has 'spaces: []', that indicates it should be skipped for cluster calls and just returned as a Not Found error. - // Realistically this would not be intermingled with other requested objects that do not have 'spaces' arrays, but it's fine for this - // specific test case. - const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3', spaces: [] }; // does not exist (Not Found) - const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4' }; // mget error (found but doesn't exist in the current space) - const obj5 = { type: SHAREABLE_OBJ_TYPE, id: 'id-5' }; // mget error (Not Found) - const obj6 = { type: SHAREABLE_OBJ_TYPE, id: 'id-6' }; // bulk error (mocked as BULK_ERROR) - const obj7 = { type: SHAREABLE_OBJ_TYPE, id: 'id-7' }; // success - - const objects = [obj1, obj2, obj3, obj4, obj5, obj6, obj7]; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; // mget error (found but doesn't exist in the current space) + const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4' }; // mget error (Not Found) + const obj5 = { type: SHAREABLE_OBJ_TYPE, id: 'id-5' }; // bulk error (mocked as BULK_ERROR) + const obj6 = { type: SHAREABLE_OBJ_TYPE, id: 'id-6' }; // success + + const objects = [obj1, obj2, obj3, obj4, obj5, obj6]; const spacesToAdd = ['foo-space']; const params = setup({ objects, spacesToAdd }); - mockMgetResults({ found: true }, { found: false }, { found: true }, { found: true }); // results for obj4, obj5, obj6, and obj7 - mockRawDocExistsInNamespace.mockReturnValueOnce(false); // for obj4 + mockMgetResults( + { found: true, namespaces: ['another-space'] }, // result for obj3 + { found: false }, // result for obj4 + { found: true, namespaces: [EXISTING_SPACE] }, // result for obj5 + { found: true, namespaces: [EXISTING_SPACE] } // result for obj6 + ); + mockRawDocExistsInNamespace.mockReturnValueOnce(false); // for obj3 + mockRawDocExistsInNamespace.mockReturnValueOnce(true); // for obj5 mockRawDocExistsInNamespace.mockReturnValueOnce(true); // for obj6 - mockRawDocExistsInNamespace.mockReturnValueOnce(true); // for obj7 - mockBulkResults({ error: true }, { error: false }); // results for obj6 and obj7 + mockBulkResults({ error: true }, { error: false }); // results for obj5 and obj6 const result = await updateObjectsSpaces(params); expect(client.mget).toHaveBeenCalledTimes(1); - expectMgetArgs(obj4, obj5, obj6, obj7); + expectMgetArgs(obj3, obj4, obj5, obj6); expect(mockRawDocExistsInNamespace).toHaveBeenCalledTimes(3); expect(client.bulk).toHaveBeenCalledTimes(1); - expectBulkArgs({ action: 'update', object: obj6 }, { action: 'update', object: obj7 }); + expectBulkArgs({ action: 'update', object: obj5 }, { action: 'update', object: obj6 }); expect(result.objects).toEqual([ { ...obj1, spaces: [], error: expect.objectContaining({ error: 'Not Found' }) }, { ...obj2, spaces: [], error: expect.objectContaining({ error: 'Bad Request' }) }, { ...obj3, spaces: [], error: expect.objectContaining({ error: 'Not Found' }) }, { ...obj4, spaces: [], error: expect.objectContaining({ error: 'Not Found' }) }, - { ...obj5, spaces: [], error: expect.objectContaining({ error: 'Not Found' }) }, - { ...obj6, spaces: [], error: BULK_ERROR }, - { ...obj7, spaces: [EXISTING_SPACE, 'foo-space'] }, + { ...obj5, spaces: [], error: BULK_ERROR }, + { ...obj6, spaces: [EXISTING_SPACE, 'foo-space'] }, ]); }); @@ -271,7 +271,7 @@ describe('#updateObjectsSpaces', () => { const objects = [{ type: SHAREABLE_OBJ_TYPE, id: 'id-1' }]; const spacesToAdd = ['foo-space']; const params = setup({ objects, spacesToAdd }); - mockMgetResultsNotFound({ found: true }); + mockMgetResultsNotFound(); await expect(() => updateObjectsSpaces(params)).rejects.toThrowError( SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError() @@ -281,71 +281,62 @@ describe('#updateObjectsSpaces', () => { // Note: these test cases do not include requested objects that will result in errors (those are covered above) describe('cluster and module calls', () => { - it('mget call skips objects that have "spaces" defined', async () => { - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [EXISTING_SPACE] }; // will not be retrieved - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; // will be passed to mget - - const objects = [obj1, obj2]; - const spacesToAdd = ['foo-space']; - const params = setup({ objects, spacesToAdd }); - mockMgetResults({ found: true }); // result for obj2 - mockBulkResults({ error: false }, { error: false }); // results for obj1 and obj2 - - await updateObjectsSpaces(params); - expect(client.mget).toHaveBeenCalledTimes(1); - expectMgetArgs(obj2); - }); - - it('does not call mget if all objects have "spaces" defined', async () => { - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [EXISTING_SPACE] }; // will not be retrieved - + it('makes mget call for objects', async () => { + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; const objects = [obj1]; const spacesToAdd = ['foo-space']; const params = setup({ objects, spacesToAdd }); + mockMgetResults({ found: true, namespaces: [EXISTING_SPACE] }); // result for obj1 mockBulkResults({ error: false }); // result for obj1 await updateObjectsSpaces(params); - expect(client.mget).not.toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalledTimes(1); + expectMgetArgs(obj1); }); describe('bulk call skips objects that will not be changed', () => { it('when adding spaces', async () => { - const space1 = 'space-to-add'; - const space2 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space2] }; // will be updated + const otherSpace = 'space-to-add'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; const objects = [obj1, obj2]; - const spacesToAdd = [space1]; + const spacesToAdd = [otherSpace]; const params = setup({ objects, spacesToAdd }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE, otherSpace] }, // result for obj1 -- will not be changed + { found: true, namespaces: [EXISTING_SPACE] } // result for obj2 -- will be updated to add otherSpace + ); mockBulkResults({ error: false }); // result for obj2 await updateObjectsSpaces(params); expect(client.bulk).toHaveBeenCalledTimes(1); expectBulkArgs({ action: 'update', - object: { ...obj2, namespaces: [space2, space1] }, + object: { ...obj2, namespaces: [EXISTING_SPACE, otherSpace] }, }); }); it('when removing spaces', async () => { - const space1 = 'space-to-remove'; - const space2 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space2] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space1, space2] }; // will be updated to remove space1 - const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3', spaces: [space1] }; // will be deleted (since it would have no spaces left) + const otherSpace = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; const objects = [obj1, obj2, obj3]; - const spacesToRemove = [space1]; + const spacesToRemove = [EXISTING_SPACE]; const params = setup({ objects, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [ALL_NAMESPACES_STRING] }, // result for obj1 -- will not be changed + { found: true, namespaces: [EXISTING_SPACE, otherSpace] }, // result for obj2 -- will be updated to remove EXISTING_SPACE + { found: true, namespaces: [EXISTING_SPACE] } // result for obj3 -- will be deleted (since it would have no spaces left) + ); mockBulkResults({ error: false }, { error: false }); // results for obj2 and obj3 await updateObjectsSpaces(params); expect(client.bulk).toHaveBeenCalledTimes(1); expectBulkArgs( - { action: 'update', object: { ...obj2, namespaces: [space2] } }, + { action: 'update', object: { ...obj2, namespaces: [otherSpace] } }, { action: 'delete', object: obj3 } ); }); @@ -353,52 +344,61 @@ describe('#updateObjectsSpaces', () => { it('when adding and removing spaces', async () => { const space1 = 'space-to-add'; const space2 = 'space-to-remove'; - const space3 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space3] }; // will be updated to add space1 - const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3', spaces: [space1, space2] }; // will be updated to remove space2 - const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4', spaces: [space2, space3] }; // will be updated to add space1 and remove space2 + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; + const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4' }; const objects = [obj1, obj2, obj3, obj4]; const spacesToAdd = [space1]; const spacesToRemove = [space2]; const params = setup({ objects, spacesToAdd, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE, space1] }, // result for obj1 -- will not be changed + { found: true, namespaces: [EXISTING_SPACE] }, // result for obj2 -- will be updated to add space1 + { found: true, namespaces: [EXISTING_SPACE, space1, space2] }, // result for obj3 -- will be updated to remove space2 + { found: true, namespaces: [EXISTING_SPACE, space2] } // result for obj3 -- will be updated to add space1 and remove space2 + ); mockBulkResults({ error: false }, { error: false }, { error: false }); // results for obj2, obj3, and obj4 await updateObjectsSpaces(params); expect(client.bulk).toHaveBeenCalledTimes(1); expectBulkArgs( - { action: 'update', object: { ...obj2, namespaces: [space3, space1] } }, - { action: 'update', object: { ...obj3, namespaces: [space1] } }, - { action: 'update', object: { ...obj4, namespaces: [space3, space1] } } + { action: 'update', object: { ...obj2, namespaces: [EXISTING_SPACE, space1] } }, + { action: 'update', object: { ...obj3, namespaces: [EXISTING_SPACE, space1] } }, + { action: 'update', object: { ...obj4, namespaces: [EXISTING_SPACE, space1] } } ); }); }); describe('does not call bulk if all objects do not need to be changed', () => { it('when adding spaces', async () => { - const space = 'space-to-add'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space] }; // will not be changed + const otherSpace = 'space-to-add'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; const objects = [obj1]; - const spacesToAdd = [space]; + const spacesToAdd = [otherSpace]; const params = setup({ objects, spacesToAdd }); - // this test case does not call mget or bulk + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE, otherSpace] } // result for obj1 -- will not be changed + ); + // this test case does not call bulk await updateObjectsSpaces(params); expect(client.bulk).not.toHaveBeenCalled(); }); it('when removing spaces', async () => { - const space1 = 'space-to-remove'; - const space2 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space2] }; // will not be changed + const otherSpace = 'space-to-remove'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; const objects = [obj1]; - const spacesToRemove = [space1]; + const spacesToRemove = [otherSpace]; const params = setup({ objects, spacesToRemove }); - // this test case does not call mget or bulk + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE] } // result for obj1 -- will not be changed + ); + // this test case does not call bulk await updateObjectsSpaces(params); expect(client.bulk).not.toHaveBeenCalled(); @@ -407,13 +407,16 @@ describe('#updateObjectsSpaces', () => { it('when adding and removing spaces', async () => { const space1 = 'space-to-add'; const space2 = 'space-to-remove'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will not be changed + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; const objects = [obj1]; const spacesToAdd = [space1]; const spacesToRemove = [space2]; const params = setup({ objects, spacesToAdd, spacesToRemove }); - // this test case does not call mget or bulk + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE, space1] } // result for obj1 -- will not be changed + ); + // this test case does not call bulk await updateObjectsSpaces(params); expect(client.bulk).not.toHaveBeenCalled(); @@ -424,33 +427,39 @@ describe('#updateObjectsSpaces', () => { it('does not delete aliases for objects that were not removed from any spaces', async () => { const space1 = 'space-to-add'; const space2 = 'space-to-remove'; - const space3 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space3] }; // will be updated + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; const objects = [obj1, obj2]; const spacesToAdd = [space1]; const spacesToRemove = [space2]; const params = setup({ objects, spacesToAdd, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE, space1] }, // result for obj1 -- will not be changed + { found: true, namespaces: [EXISTING_SPACE] } // result for obj2 -- will be updated to add space1 + ); mockBulkResults({ error: false }); // result for obj2 await updateObjectsSpaces(params); expect(client.bulk).toHaveBeenCalledTimes(1); - expectBulkArgs({ action: 'update', object: { ...obj2, namespaces: [space3, space1] } }); + expectBulkArgs({ + action: 'update', + object: { ...obj2, namespaces: [EXISTING_SPACE, space1] }, + }); expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); expect(params.logger.error).not.toHaveBeenCalled(); }); it('does not delete aliases for objects that were removed from spaces but were also added to All Spaces (*)', async () => { - const space2 = 'space-to-remove'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space2] }; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; const objects = [obj1]; const spacesToAdd = [ALL_NAMESPACES_STRING]; - const spacesToRemove = [space2]; + const spacesToRemove = [EXISTING_SPACE]; const params = setup({ objects, spacesToAdd, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE] } // result for obj1 -- will be updated to remove EXISTING_SPACE and add * + ); mockBulkResults({ error: false }); // result for obj1 await updateObjectsSpaces(params); @@ -463,33 +472,36 @@ describe('#updateObjectsSpaces', () => { expect(params.logger.error).not.toHaveBeenCalled(); }); - it('deletes aliases for objects that were removed from specific spaces using "deleteBehavior: exclusive"', async () => { + it('deletes aliases for objects that were removed from specific spaces using "deleteBehavior: inclusive"', async () => { const space1 = 'space-to-remove'; - const space2 = 'another-space-to-remove'; - const space3 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space3] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1, space2, space3] }; // will be updated - const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will be deleted + const space2 = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; const objects = [obj1, obj2, obj3]; - const spacesToRemove = [space1, space2]; + const spacesToRemove = [EXISTING_SPACE, space1]; const params = setup({ objects, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [ALL_NAMESPACES_STRING] }, // result for obj1 -- will not be changed + { found: true, namespaces: [EXISTING_SPACE, space1, space2] }, // result for obj2 -- will be updated to remove EXISTING_SPACE and space1 + { found: true, namespaces: [EXISTING_SPACE] } // result for obj3 -- will be deleted + ); mockBulkResults({ error: false }, { error: false }); // result2 for obj2 and obj3 await updateObjectsSpaces(params); expect(client.bulk).toHaveBeenCalledTimes(1); expectBulkArgs( - { action: 'update', object: { ...obj2, namespaces: [space3] } }, + { action: 'update', object: { ...obj2, namespaces: [space2] } }, { action: 'delete', object: obj3 } ); expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledTimes(2); expect(mockDeleteLegacyUrlAliases).toHaveBeenNthCalledWith( - 1, // the first call resulted in an error which generated a log message (see assertion below) + 1, expect.objectContaining({ type: obj2.type, id: obj2.id, - namespaces: [space1, space2], + namespaces: [EXISTING_SPACE, space1], deleteBehavior: 'inclusive', }) ); @@ -498,42 +510,40 @@ describe('#updateObjectsSpaces', () => { expect.objectContaining({ type: obj3.type, id: obj3.id, - namespaces: [space1], + namespaces: [EXISTING_SPACE], deleteBehavior: 'inclusive', }) ); expect(params.logger.error).not.toHaveBeenCalled(); }); - it('deletes aliases for objects that were removed from all spaces using "deleteBehavior: inclusive"', async () => { - const space1 = 'space-to-add'; - const space2 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space2] }; // will be updated to add space1 - const obj2 = { - type: SHAREABLE_OBJ_TYPE, - id: 'id-2', - spaces: [space2, ALL_NAMESPACES_STRING], // will be updated to add space1 and remove * - }; + it('deletes aliases for objects that were removed from all spaces using "deleteBehavior: exclusive"', async () => { + const otherSpace = 'space-to-add'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; const objects = [obj1, obj2]; - const spacesToAdd = [space1]; + const spacesToAdd = [EXISTING_SPACE, otherSpace]; const spacesToRemove = [ALL_NAMESPACES_STRING]; const params = setup({ objects, spacesToAdd, spacesToRemove }); - // this test case does not call mget - mockBulkResults({ error: false }); // result for obj1 + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE] }, // result for obj1 -- will be updated to add otherSpace + { found: true, namespaces: [ALL_NAMESPACES_STRING] } // result for obj2 -- will be updated to remove * and add EXISTING_SPACE and otherSpace + ); + mockBulkResults({ error: false }, { error: false }); // result for obj1 and obj2 await updateObjectsSpaces(params); expect(client.bulk).toHaveBeenCalledTimes(1); expectBulkArgs( - { action: 'update', object: { ...obj1, namespaces: [space2, space1] } }, - { action: 'update', object: { ...obj2, namespaces: [space2, space1] } } + { action: 'update', object: { ...obj1, namespaces: [EXISTING_SPACE, otherSpace] } }, + { action: 'update', object: { ...obj2, namespaces: [EXISTING_SPACE, otherSpace] } } ); expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledTimes(1); expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( expect.objectContaining({ type: obj2.type, id: obj2.id, - namespaces: [space2, space1], + namespaces: [EXISTING_SPACE, otherSpace], deleteBehavior: 'exclusive', }) ); @@ -541,20 +551,21 @@ describe('#updateObjectsSpaces', () => { }); it('logs a message when deleteLegacyUrlAliases returns an error', async () => { - const space1 = 'space-to-remove'; - const space2 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1, space2] }; // will be updated + const otherSpace = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; const objects = [obj1]; - const spacesToRemove = [space1]; + const spacesToRemove = [otherSpace]; const params = setup({ objects, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE, otherSpace] } // result for obj1 -- will be updated to remove otherSpace + ); mockBulkResults({ error: false }); // result for obj1 mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); // result for deleting aliases for obj1 await updateObjectsSpaces(params); expect(client.bulk).toHaveBeenCalledTimes(1); - expectBulkArgs({ action: 'update', object: { ...obj1, namespaces: [space2] } }); + expectBulkArgs({ action: 'update', object: { ...obj1, namespaces: [EXISTING_SPACE] } }); expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledTimes(1); // don't assert deleteLegacyUrlAliases args, we have tests for that above expect(params.logger.error).toHaveBeenCalledTimes(1); expect(params.logger.error).toHaveBeenCalledWith( @@ -566,68 +577,323 @@ describe('#updateObjectsSpaces', () => { describe('returns expected results', () => { it('when adding spaces', async () => { - const space1 = 'space-to-add'; - const space2 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space2] }; // will be updated + const otherSpace = 'space-to-add'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; const objects = [obj1, obj2]; - const spacesToAdd = [space1]; + const spacesToAdd = [otherSpace]; const params = setup({ objects, spacesToAdd }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [EXISTING_SPACE, otherSpace] }, // result for obj1 -- will not be changed + { found: true, namespaces: [EXISTING_SPACE] } // result for obj2 -- will be updated to add otherSpace + ); mockBulkResults({ error: false }); // result for obj2 const result = await updateObjectsSpaces(params); expect(result.objects).toEqual([ - { ...obj1, spaces: [space1] }, - { ...obj2, spaces: [space2, space1] }, + { ...obj1, spaces: [EXISTING_SPACE, otherSpace] }, + { ...obj2, spaces: [EXISTING_SPACE, otherSpace] }, ]); }); it('when removing spaces', async () => { - const space1 = 'space-to-remove'; - const space2 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space2] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space1, space2] }; // will be updated to remove space1 - const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3', spaces: [space1] }; // will be deleted (since it would have no spaces left) + const otherSpace = 'other-space'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; const objects = [obj1, obj2, obj3]; - const spacesToRemove = [space1]; + const spacesToRemove = [EXISTING_SPACE]; const params = setup({ objects, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [ALL_NAMESPACES_STRING] }, // result for obj1 -- will not be changed + { found: true, namespaces: [EXISTING_SPACE, otherSpace] }, // result for obj2 -- will be updated to remove EXISTING_SPACE + { found: true, namespaces: [EXISTING_SPACE] } // result for obj3 -- will be deleted (since it would have no spaces left) + ); mockBulkResults({ error: false }, { error: false }); // results for obj2 and obj3 const result = await updateObjectsSpaces(params); expect(result.objects).toEqual([ - { ...obj1, spaces: [space2] }, - { ...obj2, spaces: [space2] }, + { ...obj1, spaces: [ALL_NAMESPACES_STRING] }, + { ...obj2, spaces: [otherSpace] }, { ...obj3, spaces: [] }, ]); }); it('when adding and removing spaces', async () => { - const space1 = 'space-to-add'; - const space2 = 'space-to-remove'; - const space3 = 'other-space'; - const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1', spaces: [space1] }; // will not be changed - const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2', spaces: [space3] }; // will be updated to add space1 - const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3', spaces: [space1, space2] }; // will be updated to remove space2 - const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4', spaces: [space2, space3] }; // will be updated to add space1 and remove space2 + const otherSpace = 'space-to-add'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; + const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4' }; const objects = [obj1, obj2, obj3, obj4]; - const spacesToAdd = [space1]; - const spacesToRemove = [space2]; + const spacesToAdd = [otherSpace]; + const spacesToRemove = [EXISTING_SPACE]; const params = setup({ objects, spacesToAdd, spacesToRemove }); - // this test case does not call mget + mockMgetResults( + { found: true, namespaces: [ALL_NAMESPACES_STRING, otherSpace] }, // result for obj1 -- will not be changed + { found: true, namespaces: [ALL_NAMESPACES_STRING] }, // result for obj2 -- will be updated to add otherSpace + { found: true, namespaces: [EXISTING_SPACE, otherSpace] }, // result for obj3 -- will be updated to remove EXISTING_SPACE + { found: true, namespaces: [EXISTING_SPACE] } // result for obj4 -- will be updated to remove EXISTING_SPACE and add otherSpace + ); mockBulkResults({ error: false }, { error: false }, { error: false }); // results for obj2, obj3, and obj4 const result = await updateObjectsSpaces(params); expect(result.objects).toEqual([ - { ...obj1, spaces: [space1] }, - { ...obj2, spaces: [space3, space1] }, - { ...obj3, spaces: [space1] }, - { ...obj4, spaces: [space3, space1] }, + { ...obj1, spaces: [ALL_NAMESPACES_STRING, otherSpace] }, + { ...obj2, spaces: [ALL_NAMESPACES_STRING, otherSpace] }, + { ...obj3, spaces: [otherSpace] }, + { ...obj4, spaces: [otherSpace] }, ]); }); }); + + describe(`with security extension`, () => { + let mockSecurityExt: jest.Mocked; + let params: UpdateObjectsSpacesParams; + + describe(`errors`, () => { + beforeEach(() => { + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const objects = [obj1]; + const spacesToAdd = ['foo-space']; + mockSecurityExt = extensionsMock.createSecurityExtension(); + params = setup({ objects, spacesToAdd }, mockSecurityExt); + mockMgetResults({ found: true, namespaces: [EXISTING_SPACE] }); // result for obj1 + mockBulkResults({ error: false }); // result for obj1 + }); + + test(`propagates error from es client bulk get`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const error = SavedObjectsErrorHelpers.createBadRequestError('OOPS!'); + + mockGetBulkOperationError.mockReset(); + client.bulk.mockReset(); + client.bulk.mockImplementationOnce(() => { + throw error; + }); + + await expect(updateObjectsSpaces(params)).rejects.toThrow(error); + }); + + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + + await expect(updateObjectsSpaces(params)).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propagates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(updateObjectsSpaces(params)).rejects.toThrow(enforceError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`adds audit event when not unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect(updateObjectsSpaces(params)).rejects.toThrow(enforceError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE_OBJECTS_SPACES, + addToSpaces: params.spacesToAdd, + deleteFromSpaces: undefined, + savedObject: { type: params.objects[0].type, id: params.objects[0].id }, + error: enforceError, + }); + }); + + test(`returns error from es client bulk operation`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + mockGetBulkOperationError.mockReset(); + client.bulk.mockReset(); + mockBulkResults({ error: true }); + + const result = await updateObjectsSpaces(params); + expect(result).toEqual({ + objects: [ + { + error: BULK_ERROR, + id: params.objects[0].id, + spaces: [], + type: params.objects[0].type, + }, + ], + }); + }); + }); + + describe('success', () => { + const defaultSpace = 'default'; + const otherSpace = 'space-to-add'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; + const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4' }; + + const objects = [obj1, obj2, obj3, obj4]; + const spacesToAdd = [otherSpace]; + const spacesToRemove = [EXISTING_SPACE]; + + beforeEach(() => { + mockSecurityExt = extensionsMock.createSecurityExtension(); + params = setup({ objects, spacesToAdd, spacesToRemove }, mockSecurityExt); + mockMgetResults( + { found: true, namespaces: [ALL_NAMESPACES_STRING, otherSpace] }, // result for obj1 -- will not be changed + { found: true, namespaces: [ALL_NAMESPACES_STRING] }, // result for obj2 -- will be updated to add otherSpace + { found: true, namespaces: [EXISTING_SPACE, otherSpace] }, // result for obj3 -- will be updated to remove EXISTING_SPACE + { found: true, namespaces: [EXISTING_SPACE] } // result for obj4 -- will be updated to remove EXISTING_SPACE and add otherSpace + ); + mockBulkResults({ error: false }, { error: false }, { error: false }); // results for obj2, obj3, and obj4 + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + await updateObjectsSpaces(params); + + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['share_to_space']; + const expectedSpaces = new Set([defaultSpace, otherSpace, EXISTING_SPACE]); + const expectedTypes = new Set([SHAREABLE_OBJ_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + await updateObjectsSpaces(params); + + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'share_to_space', + }) + ); + const expectedTypesAndSpaces = new Map([ + [SHAREABLE_OBJ_TYPE, new Set([defaultSpace, EXISTING_SPACE, otherSpace])], + ]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`adds audit event per object when successful`, async () => { + await updateObjectsSpaces(params); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE_OBJECTS_SPACES, + savedObject: { type: obj.type, id: obj.id }, + outcome: 'unknown', + addToSpaces: spacesToAdd, + deleteFromSpaces: spacesToRemove, + error: undefined, + }); + }); + }); + }); + + describe('all spaces', () => { + const defaultSpace = 'default'; + const otherSpace = 'space-to-add'; + const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; + const obj2 = { type: SHAREABLE_OBJ_TYPE, id: 'id-2' }; + const obj3 = { type: SHAREABLE_OBJ_TYPE, id: 'id-3' }; + const obj4 = { type: SHAREABLE_OBJ_TYPE, id: 'id-4' }; + const objects = [obj1, obj2, obj3, obj4]; + + const setupForAllSpaces = (spacesToAdd: string[], spacesToRemove: string[]) => { + mockSecurityExt = extensionsMock.createSecurityExtension(); + params = setup({ objects, spacesToAdd, spacesToRemove }, mockSecurityExt); + mockMgetResults( + { found: true, namespaces: [ALL_NAMESPACES_STRING, otherSpace] }, // result for obj1 -- will not be changed + { found: true, namespaces: [ALL_NAMESPACES_STRING] }, // result for obj2 -- will be updated to add otherSpace + { found: true, namespaces: [EXISTING_SPACE, otherSpace] }, // result for obj3 -- will be updated to remove EXISTING_SPACE + { found: true, namespaces: [EXISTING_SPACE] } // result for obj4 -- will be updated to remove EXISTING_SPACE and add otherSpace + ); + mockBulkResults({ error: false }, { error: false }, { error: false }); // results for obj2, obj3, and obj4 + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + }; + + test(`calls checkAuthorization with '*' when spacesToAdd includes '*'`, async () => { + const spacesToAdd = ['*']; + const spacesToRemove = [otherSpace]; + setupForAllSpaces(spacesToAdd, spacesToRemove); + await updateObjectsSpaces(params); + + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['share_to_space']; + const expectedSpaces = new Set(['*', defaultSpace, otherSpace, EXISTING_SPACE]); + const expectedTypes = new Set([SHAREABLE_OBJ_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls checkAuthorization with '*' when spacesToRemove includes '*'`, async () => { + const spacesToAdd = [otherSpace]; + const spacesToRemove = ['*']; + setupForAllSpaces(spacesToAdd, spacesToRemove); + await updateObjectsSpaces(params); + + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['share_to_space']; + const expectedSpaces = new Set(['*', defaultSpace, otherSpace, EXISTING_SPACE]); + const expectedTypes = new Set([SHAREABLE_OBJ_TYPE]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + }); + }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts index 4053fcff4a8ea0..f5b810ea3f3af5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts @@ -18,7 +18,9 @@ import type { SavedObjectsUpdateObjectsSpacesResponse, SavedObjectsUpdateObjectsSpacesResponseObject, } from '@kbn/core-saved-objects-api-server'; -import type { +import { + AuditAction, + ISavedObjectsSecurityExtension, ISavedObjectTypeRegistry, SavedObjectsRawDocSource, } from '@kbn/core-saved-objects-server'; @@ -26,11 +28,13 @@ import { SavedObjectsErrorHelpers, ALL_NAMESPACES_STRING, type DecoratedError, + SavedObjectsUtils, } from '@kbn/core-saved-objects-utils-server'; import type { IndexMapping, SavedObjectsSerializer, } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObject } from '@kbn/core-saved-objects-common'; import { getBulkOperationError, getExpectedVersionProperties, @@ -57,6 +61,7 @@ export interface UpdateObjectsSpacesParams { serializer: SavedObjectsSerializer; logger: Logger; getIndexForType: (type: string) => string; + securityExtension: ISavedObjectsSecurityExtension | undefined; objects: SavedObjectsUpdateObjectsSpacesObject[]; spacesToAdd: string[]; spacesToRemove: string[]; @@ -86,6 +91,7 @@ export async function updateObjectsSpaces({ serializer, logger, getIndexForType, + securityExtension, objects, spacesToAdd, spacesToRemove, @@ -106,9 +112,12 @@ export async function updateObjectsSpaces({ let bulkGetRequestIndexCounter = 0; const expectedBulkGetResults: Array< - Either> + Either< + SavedObjectsUpdateObjectsSpacesResponseObject, + { type: string; id: string; version: string | undefined; esRequestIndex: number } + > > = objects.map((object) => { - const { type, id, spaces, version } = object; + const { type, id, version } = object; if (!allowedTypes.includes(type)) { const error = errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)); @@ -134,23 +143,26 @@ export async function updateObjectsSpaces({ value: { type, id, - spaces, version, - ...(!spaces && { esRequestIndex: bulkGetRequestIndexCounter++ }), + esRequestIndex: bulkGetRequestIndexCounter++, }, }; }); - const bulkGetDocs = expectedBulkGetResults.reduce((acc, x) => { - if (isRight(x) && x.value.esRequestIndex !== undefined) { - acc.push({ - _id: serializer.generateRawId(undefined, x.value.type, x.value.id), - _index: getIndexForType(x.value.type), - _source: ['type', 'namespaces'], - }); - } - return acc; - }, []); + const validObjects = expectedBulkGetResults.filter(isRight); + if (validObjects.length === 0) { + // We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. + return { + // Filter with the `isLeft` comparator simply because it's a convenient type guard, we know the only expected results are errors. + objects: expectedBulkGetResults.filter(isLeft).map(({ value }) => value), + }; + } + + const bulkGetDocs = validObjects.map((x) => ({ + _id: serializer.generateRawId(undefined, x.value.type, x.value.id), + _index: getIndexForType(x.value.type), + _source: ['type', 'namespaces'], + })); const bulkGetResponse = bulkGetDocs.length ? await client.mget( { body: { docs: bulkGetDocs } }, @@ -167,6 +179,59 @@ export async function updateObjectsSpaces({ ) { throw SavedObjectsErrorHelpers.createGenericNotFoundEsUnavailableError(); } + + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + const addToSpaces = spacesToAdd.length ? spacesToAdd : undefined; + const deleteFromSpaces = spacesToRemove.length ? spacesToRemove : undefined; + const typesAndSpaces = new Map>(); + const spacesToAuthorize = new Set(); + for (const { value } of validObjects) { + const { type, esRequestIndex: index } = value; + const preflightResult = index !== undefined ? bulkGetResponse?.body.docs[index] : undefined; + + const spacesToEnforce = + typesAndSpaces.get(type) ?? new Set([...spacesToAdd, ...spacesToRemove, namespaceString]); // Always enforce authZ for the active space + typesAndSpaces.set(type, spacesToEnforce); + for (const space of spacesToEnforce) { + spacesToAuthorize.add(space); + } + // @ts-expect-error MultiGetHit._source is optional + for (const space of preflightResult?._source?.namespaces ?? []) { + // Existing namespaces are included so we can later redact if necessary + // If this is a specific space, add it to the spaces we'll check privileges for (don't accidentally check for global privileges) + if (space === ALL_NAMESPACES_STRING) continue; + spacesToAuthorize.add(space); + } + } + + const authorizationResult = await securityExtension?.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: spacesToAuthorize, + actions: ['share_to_space'], + // If a user tries to share/unshare an object to/from '*', they need to have 'share_to_space' privileges for the Global Resource (e.g., + // All privileges for All Spaces). + options: { allowGlobalResource: true }, + }); + if (authorizationResult) { + securityExtension!.enforceAuthorization({ + typesAndSpaces, + action: 'share_to_space', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + for (const { value } of validObjects) { + securityExtension!.addAuditEvent({ + action: AuditAction.UPDATE_OBJECTS_SPACES, + savedObject: { type: value.type, id: value.id }, + addToSpaces, + deleteFromSpaces, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the update operation has not occurred yet + }); + } + }, + }); + } + const time = new Date().toISOString(); let bulkOperationRequestIndexCounter = 0; const bulkOperationParams: estypes.BulkOperationContainer[] = []; @@ -181,40 +246,25 @@ export async function updateObjectsSpaces({ return expectedBulkGetResult; } - const { id, type, spaces, version, esRequestIndex } = expectedBulkGetResult.value; - - let currentSpaces: string[] = spaces; - let versionProperties; - if (esRequestIndex !== undefined) { - const doc = bulkGetResponse?.body.docs[esRequestIndex]; - const isErrorDoc = isMgetError(doc); - - if ( - isErrorDoc || - !doc?.found || - // @ts-expect-error MultiGetHit._source is optional - !rawDocExistsInNamespace(registry, doc, namespace) - ) { - const error = errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)); - return { - tag: 'Left', - value: { id, type, spaces: [], error }, - }; - } - currentSpaces = doc._source?.namespaces ?? []; + const { id, type, version, esRequestIndex } = expectedBulkGetResult.value; + const doc = bulkGetResponse!.body.docs[esRequestIndex]; + if ( + isMgetError(doc) || + !doc?.found || // @ts-expect-error MultiGetHit._source is optional - versionProperties = getExpectedVersionProperties(version, doc); - } else if (spaces?.length === 0) { - // A SOC wrapper attempted to retrieve this object in a pre-flight request and it was not found. + !rawDocExistsInNamespace(registry, doc, namespace) + ) { const error = errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)); return { tag: 'Left', value: { id, type, spaces: [], error }, }; - } else { - versionProperties = getExpectedVersionProperties(version); } + const currentSpaces = doc._source?.namespaces ?? []; + // @ts-expect-error MultiGetHit._source is optional + const versionProperties = getExpectedVersionProperties(version, doc); + const { updatedSpaces, removedSpaces, isUpdateRequired } = analyzeSpaceChanges( currentSpaces, spacesToAdd, @@ -296,6 +346,13 @@ export async function updateObjectsSpaces({ } } + if (authorizationResult) { + const { namespaces: redactedSpaces } = securityExtension!.redactNamespaces({ + savedObject: { type, namespaces: updatedSpaces } as SavedObject, // Other SavedObject attributes aren't required + typeMap: authorizationResult.typeMap, + }); + return { id, type, spaces: redactedSpaces! }; + } return { id, type, spaces: updatedSpaces }; } ), diff --git a/packages/core/saved-objects/core-saved-objects-api-server/index.ts b/packages/core/saved-objects/core-saved-objects-api-server/index.ts index fdaa5685fbde03..467b5891ffb857 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/index.ts @@ -7,7 +7,10 @@ */ export type { SavedObjectsClientContract } from './src/saved_objects_client'; -export type { ISavedObjectsRepository } from './src/saved_objects_repository'; +export type { + ISavedObjectsRepository, + SavedObjectsFindInternalOptions, +} from './src/saved_objects_repository'; export type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions, diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts index e46a259dfd4cfe..4e9b741d8b658f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts @@ -7,7 +7,7 @@ */ import type { SavedObjectsFindOptions, SavedObjectsFindResponse } from './find'; -import type { SavedObjectsClientContract } from '../saved_objects_client'; +import { ISavedObjectsRepository } from '../saved_objects_repository'; /** * @public @@ -21,7 +21,7 @@ export type SavedObjectsCreatePointInTimeFinderOptions = Omit< * @public */ export type SavedObjectsPointInTimeFinderClient = Pick< - SavedObjectsClientContract, + ISavedObjectsRepository, 'find' | 'openPointInTimeForType' | 'closePointInTime' >; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index d7d2ca57ae3a28..a1f643bd3edc48 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -49,6 +49,19 @@ import type { SavedObjectsBulkDeleteResponse, } from './apis'; +/** + * @internal + */ +export interface SavedObjectsFindInternalOptions { + /** This is used for calls internal to the SO doamain that need to use a PIT finder but want to prevent extensions from functioning. + * We use the SOR's PointInTimeFinder internally when searching for aliases and shared origins for saved objects, but we + * need to disable the extensions for that to function correctly. + * Before, when we had SOC wrappers, the SOR's PointInTimeFinder did not have any of the wrapper functionality applied. + * This disableExtensions internal option preserves that behavior. + */ + disableExtensions?: boolean; +} + /** * The savedObjects repository contract. * @@ -150,7 +163,8 @@ export interface ISavedObjectsRepository { * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ find( - options: SavedObjectsFindOptions + options: SavedObjectsFindOptions, + internalOptions?: SavedObjectsFindInternalOptions ): Promise>; /** @@ -389,7 +403,8 @@ export interface ISavedObjectsRepository { */ openPointInTimeForType( type: string | string[], - options?: SavedObjectsOpenPointInTimeOptions + options?: SavedObjectsOpenPointInTimeOptions, + internalOptions?: SavedObjectsFindInternalOptions ): Promise; /** @@ -435,7 +450,8 @@ export interface ISavedObjectsRepository { */ closePointInTime( id: string, - options?: SavedObjectsClosePointInTimeOptions + options?: SavedObjectsClosePointInTimeOptions, + internalOptions?: SavedObjectsFindInternalOptions ): Promise; /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts index cedd5dc3698262..92a5fb380dc19d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/index.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -export { KibanaMigrator, buildActiveMappings, mergeTypes } from './src'; +export { DocumentMigrator, KibanaMigrator, buildActiveMappings, mergeTypes } from './src'; export type { KibanaMigratorOptions } from './src'; export { getAggregatedTypesDocuments } from './src/actions/check_for_unknown_docs'; export { addExcludedTypesToBoolQuery } from './src/model/helpers'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts index 817a284468c1f0..7f520531f22e66 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/index.ts @@ -9,3 +9,4 @@ export { KibanaMigrator, mergeTypes } from './kibana_migrator'; export type { KibanaMigratorOptions } from './kibana_migrator'; export { buildActiveMappings } from './core'; +export { DocumentMigrator } from './core'; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index fa70e0dc713545..bc582085ac26c2 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -543,7 +543,7 @@ export class ActionsPlugin implements Plugin savedObjects.getScopedClient(request, { - excludedWrappers: ['security'], + excludedExtensions: ['security'], includedHiddenTypes, }); @@ -606,7 +606,7 @@ export class ActionsPlugin implements Plugin { factory.create(request, savedObjectsService); expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request, { - excludedWrappers: ['security'], + excludedExtensions: ['security'], includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index d7dc9612b603a4..a6406c3137462d 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -91,7 +91,7 @@ export class RulesClientFactory { ruleTypeRegistry: this.ruleTypeRegistry, minimumScheduleInterval: this.minimumScheduleInterval, unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, { - excludedWrappers: ['security'], + excludedExtensions: ['security'], includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }), authorization: this.authorization.create(request), diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index 30d2e24a144a13..9ac75d61557c66 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -105,7 +105,7 @@ export class CasesClientFactory { includedHiddenTypes: SAVED_OBJECT_TYPES, // this tells the security plugin to not perform SO authorization and audit logging since we are handling // that manually using our Authorization class and audit logger. - excludedWrappers: ['security'], + excludedExtensions: ['security'], }); const services = this.createServices({ diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts index a1c598bef24134..cca1f021ad1b14 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts @@ -70,7 +70,7 @@ beforeEach(() => { mockRetrieveClient.find.mockResolvedValue({ total: 0, saved_objects: [], per_page: 0, page: 0 }); mockUpdateClient = savedObjectsClientMock.create(); mockSavedObjects.getScopedClient.mockImplementation((request, params) => - params?.excludedWrappers?.[0] === 'encryptedSavedObjects' + params?.excludedExtensions?.[0] === 'encryptedSavedObjects' ? mockRetrieveClient : mockUpdateClient ); @@ -89,7 +89,7 @@ it('correctly setups Saved Objects clients', async () => { expect(mockSavedObjects.getScopedClient).toHaveBeenCalledTimes(2); expect(mockSavedObjects.getScopedClient).toHaveBeenCalledWith(mockRequest, { includedHiddenTypes: ['type-id-2', 'type-id-4'], - excludedWrappers: ['encryptedSavedObjects'], + excludedExtensions: ['encryptedSavedObjects'], }); expect(mockSavedObjects.getScopedClient).toHaveBeenCalledWith(mockRequest, { includedHiddenTypes: ['type-id-2', 'type-id-4'], diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts index 6caf76e63508a8..becb5e6103213d 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts @@ -107,7 +107,7 @@ export class EncryptionKeyRotationService { const user = this.options.security?.authc.getCurrentUser(request) ?? undefined; const retrieveClient = savedObjects.getScopedClient(request, { includedHiddenTypes: registeredHiddenSavedObjectTypes, - excludedWrappers: ['encryptedSavedObjects'], + excludedExtensions: ['encryptedSavedObjects'], }); const updateClient = savedObjects.getScopedClient(request, { includedHiddenTypes: registeredHiddenSavedObjectTypes, diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts deleted file mode 100644 index ab8b03840c8199..00000000000000 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.test.ts +++ /dev/null @@ -1,2200 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { SavedObjectsBulkResolveResponse, SavedObjectsClientContract } from '@kbn/core/server'; -import { savedObjectsClientMock, savedObjectsTypeRegistryMock } from '@kbn/core/server/mocks'; -import { mockAuthenticatedUser } from '@kbn/security-plugin/common/model/authenticated_user.mock'; - -import type { EncryptedSavedObjectsService } from '../crypto'; -import { EncryptionError } from '../crypto'; -import { EncryptionErrorOperation } from '../crypto/encryption_error'; -import { encryptedSavedObjectsServiceMock } from '../crypto/index.mock'; -import { EncryptedSavedObjectsClientWrapper } from './encrypted_saved_objects_client_wrapper'; - -jest.mock('@kbn/core-saved-objects-utils-server', () => { - const { SavedObjectsUtils, ...actual } = jest.requireActual( - '@kbn/core-saved-objects-utils-server' - ); - return { - ...actual, - SavedObjectsUtils: { - namespaceStringToId: SavedObjectsUtils.namespaceStringToId, - isRandomId: SavedObjectsUtils.isRandomId, - generateId: () => 'mock-saved-object-id', - }, - }; -}); - -let wrapper: EncryptedSavedObjectsClientWrapper; -let mockBaseClient: jest.Mocked; -let mockBaseTypeRegistry: ReturnType; -let encryptedSavedObjectsServiceMockInstance: jest.Mocked; -beforeEach(() => { - mockBaseClient = savedObjectsClientMock.create(); - mockBaseTypeRegistry = savedObjectsTypeRegistryMock.create(); - encryptedSavedObjectsServiceMockInstance = encryptedSavedObjectsServiceMock.createWithTypes([ - { - type: 'known-type', - attributesToEncrypt: new Set([ - 'attrSecret', - { key: 'attrNotSoSecret', dangerouslyExposeValue: true }, - ]), - }, - ]); - - wrapper = new EncryptedSavedObjectsClientWrapper({ - service: encryptedSavedObjectsServiceMockInstance, - baseClient: mockBaseClient, - baseTypeRegistry: mockBaseTypeRegistry, - getCurrentUser: () => mockAuthenticatedUser(), - } as any); -}); - -afterEach(() => jest.clearAllMocks()); - -describe('#checkConflicts', () => { - it('redirects request to underlying base client', async () => { - const objects = [{ type: 'foo', id: 'bar' }]; - const options = { namespace: 'some-namespace' }; - const mockedResponse = { errors: [] }; - mockBaseClient.checkConflicts.mockResolvedValue(mockedResponse); - - await expect(wrapper.checkConflicts(objects, options)).resolves.toEqual(mockedResponse); - expect(mockBaseClient.checkConflicts).toHaveBeenCalledTimes(1); - expect(mockBaseClient.checkConflicts).toHaveBeenCalledWith(objects, options); - }); -}); - -describe('#create', () => { - it('redirects request to underlying base client if type is not registered', async () => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - const options = { id: 'some-non-uuid-v4-id' }; - const mockedResponse = { id: options.id, type: 'unknown-type', attributes, references: [] }; - - mockBaseClient.create.mockResolvedValue(mockedResponse); - - await expect(wrapper.create('unknown-type', attributes, options)).resolves.toEqual({ - ...mockedResponse, - id: options.id, - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }); - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith('unknown-type', attributes, options); - }); - - it('fails if type is registered and non-UUID ID is specified', async () => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - - await expect(wrapper.create('known-type', attributes, { id: 'some-id' })).rejects.toThrowError( - 'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.' - ); - - expect(mockBaseClient.create).not.toHaveBeenCalled(); - }); - - it('allows a specified ID when overwriting an existing object', async () => { - const attributes = { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }; - const options = { id: 'predefined-uuid', overwrite: true, version: 'some-version' }; - const mockedResponse = { - id: 'predefined-uuid', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - references: [], - }; - - mockBaseClient.create.mockResolvedValue(mockedResponse); - - expect(await wrapper.create('known-type', attributes, options)).toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'predefined-uuid' }, - { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith( - 'known-type', - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - { id: 'predefined-uuid', overwrite: true, version: 'some-version' } - ); - }); - - it('generates ID, encrypts attributes and strips them from response except for ones with `dangerouslyExposeValue` set to `true`', async () => { - const attributes = { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }; - const options = { overwrite: true }; - const mockedResponse = { - id: 'mock-saved-object-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - references: [], - }; - - mockBaseClient.create.mockResolvedValue(mockedResponse); - - expect(await wrapper.create('known-type', attributes, options)).toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'mock-saved-object-id' }, - { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith( - 'known-type', - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - { id: 'mock-saved-object-id', overwrite: true } - ); - }); - - describe('namespace', () => { - const doTest = async (namespace: string, expectNamespaceInDescriptor: boolean) => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - const options = { overwrite: true, namespace }; - const mockedResponse = { - id: 'mock-saved-object-id', - type: 'known-type', - attributes: { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - references: [], - }; - - mockBaseClient.create.mockResolvedValue(mockedResponse); - - expect(await wrapper.create('known-type', attributes, options)).toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrThree: 'three' }, - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { - type: 'known-type', - id: 'mock-saved-object-id', - namespace: expectNamespaceInDescriptor ? namespace : undefined, - }, - { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith( - 'known-type', - { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - { id: 'mock-saved-object-id', overwrite: true, namespace } - ); - }; - - it('uses `namespace` to encrypt attributes if it is specified when type is single-namespace', async () => { - await doTest('some-namespace', true); - }); - - it('does not use `namespace` to encrypt attributes if it is specified when type is not single-namespace', async () => { - mockBaseTypeRegistry.isSingleNamespace.mockReturnValue(false); - await doTest('some-namespace', false); - }); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.create.mockRejectedValue(failureReason); - - await expect( - wrapper.create('known-type', { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }) - ).rejects.toThrowError(failureReason); - - expect(mockBaseClient.create).toHaveBeenCalledTimes(1); - expect(mockBaseClient.create).toHaveBeenCalledWith( - 'known-type', - { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - { id: 'mock-saved-object-id' } - ); - }); -}); - -describe('#bulkCreate', () => { - it('does not fail if ID is specified for not registered type', async () => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - const options = { namespace: 'some-namespace' }; - const mockedResponse = { - saved_objects: [ - { - id: 'mock-saved-object-id', - type: 'known-type', - attributes, - references: [], - }, - { - id: 'some-id', - type: 'unknown-type', - attributes, - references: [], - }, - ], - }; - - mockBaseClient.bulkCreate.mockResolvedValue(mockedResponse); - - const bulkCreateParams = [ - { type: 'known-type', attributes }, - { id: 'some-id', type: 'unknown-type', attributes }, - ]; - - await expect(wrapper.bulkCreate(bulkCreateParams, options)).resolves.toEqual({ - saved_objects: [ - { ...mockedResponse.saved_objects[0], attributes: { attrOne: 'one', attrThree: 'three' } }, - mockedResponse.saved_objects[1], - ], - }); - - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( - [ - { - ...bulkCreateParams[0], - id: 'mock-saved-object-id', - attributes: { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - }, - bulkCreateParams[1], - ], - options - ); - }); - - it('fails if non-UUID ID is specified for registered type', async () => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - - const bulkCreateParams = [ - { id: 'some-id', type: 'known-type', attributes }, - { type: 'unknown-type', attributes }, - ]; - - await expect(wrapper.bulkCreate(bulkCreateParams)).rejects.toThrowError( - 'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.' - ); - - expect(mockBaseClient.bulkCreate).not.toHaveBeenCalled(); - }); - - it('allows a specified ID when overwriting an existing object', async () => { - const attributes = { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }; - const mockedResponse = { - saved_objects: [ - { - id: 'predefined-uuid', - type: 'known-type', - attributes: { ...attributes, attrSecret: '*secret*', attrNotSoSecret: '*not-so-secret*' }, - references: [], - }, - { - id: 'some-id', - type: 'unknown-type', - attributes, - references: [], - }, - ], - }; - - mockBaseClient.bulkCreate.mockResolvedValue(mockedResponse); - - const bulkCreateParams = [ - { id: 'predefined-uuid', type: 'known-type', attributes, version: 'some-version' }, - { type: 'unknown-type', attributes }, - ]; - - await expect(wrapper.bulkCreate(bulkCreateParams, { overwrite: true })).resolves.toEqual({ - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }, - mockedResponse.saved_objects[1], - ], - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'predefined-uuid' }, - { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( - [ - { - ...bulkCreateParams[0], - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - }, - bulkCreateParams[1], - ], - { overwrite: true } - ); - }); - - it('generates ID, encrypts attributes and strips them from response except for ones with `dangerouslyExposeValue` set to `true`', async () => { - const attributes = { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }; - const mockedResponse = { - saved_objects: [ - { - id: 'mock-saved-object-id', - type: 'known-type', - attributes: { ...attributes, attrSecret: '*secret*', attrNotSoSecret: '*not-so-secret*' }, - references: [], - }, - { - id: 'some-id', - type: 'unknown-type', - attributes, - references: [], - }, - ], - }; - - mockBaseClient.bulkCreate.mockResolvedValue(mockedResponse); - - const bulkCreateParams = [ - { type: 'known-type', attributes }, - { type: 'unknown-type', attributes }, - ]; - - await expect(wrapper.bulkCreate(bulkCreateParams)).resolves.toEqual({ - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }, - mockedResponse.saved_objects[1], - ], - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'mock-saved-object-id' }, - { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( - [ - { - ...bulkCreateParams[0], - id: 'mock-saved-object-id', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - }, - bulkCreateParams[1], - ], - undefined - ); - }); - - describe('namespace', () => { - const doTest = async (namespace: string, expectNamespaceInDescriptor: boolean) => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - const options = { namespace }; - const mockedResponse = { - saved_objects: [ - { id: 'mock-saved-object-id', type: 'known-type', attributes, references: [] }, - ], - }; - - mockBaseClient.bulkCreate.mockResolvedValue(mockedResponse); - - const bulkCreateParams = [{ type: 'known-type', attributes }]; - await expect(wrapper.bulkCreate(bulkCreateParams, options)).resolves.toEqual({ - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { attrOne: 'one', attrThree: 'three' }, - }, - ], - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { - type: 'known-type', - id: 'mock-saved-object-id', - namespace: expectNamespaceInDescriptor ? namespace : undefined, - }, - { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( - [ - { - ...bulkCreateParams[0], - id: 'mock-saved-object-id', - attributes: { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - }, - ], - options - ); - }; - - it('uses `namespace` to encrypt attributes if it is specified when type is single-namespace', async () => { - await doTest('some-namespace', true); - }); - - it('does not use `namespace` to encrypt attributes if it is specified when type is not single-namespace', async () => { - mockBaseTypeRegistry.isSingleNamespace.mockReturnValue(false); - await doTest('some-namespace', false); - }); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.bulkCreate.mockRejectedValue(failureReason); - - await expect( - wrapper.bulkCreate([ - { - type: 'known-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }, - ]) - ).rejects.toThrowError(failureReason); - - expect(mockBaseClient.bulkCreate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkCreate).toHaveBeenCalledWith( - [ - { - type: 'known-type', - id: 'mock-saved-object-id', - attributes: { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - }, - ], - undefined - ); - }); -}); - -describe('#bulkUpdate', () => { - it('redirects request to underlying base client if type is not registered', async () => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - const mockedResponse = { - saved_objects: [{ id: 'some-id', type: 'unknown-type', attributes, references: [] }], - }; - - mockBaseClient.bulkUpdate.mockResolvedValue(mockedResponse); - - await expect( - wrapper.bulkUpdate( - [{ type: 'unknown-type', id: 'some-id', attributes, version: 'some-version' }], - {} - ) - ).resolves.toEqual(mockedResponse); - - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledWith( - [{ type: 'unknown-type', id: 'some-id', attributes, version: 'some-version' }], - {} - ); - }); - - it('encrypts attributes and strips them from response except for ones with `dangerouslyExposeValue` set to `true`', async () => { - const docs = [ - { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - }, - { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one 2', - attrSecret: 'secret 2', - attrNotSoSecret: 'not-so-secret 2', - attrThree: 'three 2', - }, - }, - ]; - - const mockedResponse = { - saved_objects: docs.map((doc) => ({ - ...doc, - attributes: { - ...doc.attributes, - attrSecret: `*${doc.attributes.attrSecret}*`, - attrNotSoSecret: `*${doc.attributes.attrNotSoSecret}*`, - }, - references: undefined, - })), - }; - - mockBaseClient.bulkUpdate.mockResolvedValue(mockedResponse); - - await expect( - wrapper.bulkUpdate( - docs.map((doc) => ({ ...doc })), - {} - ) - ).resolves.toEqual({ - saved_objects: [ - { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - }, - { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one 2', - attrNotSoSecret: 'not-so-secret 2', - attrThree: 'three 2', - }, - }, - ], - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(2); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id' }, - { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - { user: mockAuthenticatedUser() } - ); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id-2' }, - { - attrOne: 'one 2', - attrSecret: 'secret 2', - attrNotSoSecret: 'not-so-secret 2', - attrThree: 'three 2', - }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledWith( - [ - { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - }, - { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one 2', - attrSecret: '*secret 2*', - attrNotSoSecret: '*not-so-secret 2*', - attrThree: 'three 2', - }, - }, - ], - {} - ); - }); - - describe('namespace', () => { - interface TestParams { - optionsNamespace: string | undefined; - objectNamespace: string | undefined; - expectOptionsNamespaceInDescriptor: boolean; - expectObjectNamespaceInDescriptor: boolean; - } - - const doTest = async ({ - optionsNamespace, - objectNamespace, - expectOptionsNamespaceInDescriptor, - expectObjectNamespaceInDescriptor, - }: TestParams) => { - const docs = [ - { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrThree: 'three', - }, - version: 'some-version', - namespace: objectNamespace, - }, - ]; - const options = { namespace: optionsNamespace }; - - mockBaseClient.bulkUpdate.mockResolvedValue({ - saved_objects: docs.map(({ namespace, ...doc }) => ({ ...doc, references: undefined })), - }); - - await expect(wrapper.bulkUpdate(docs, options)).resolves.toEqual({ - saved_objects: [ - { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrThree: 'three', - }, - version: 'some-version', - references: undefined, - }, - ], - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { - type: 'known-type', - id: 'some-id', - namespace: expectObjectNamespaceInDescriptor - ? objectNamespace - : expectOptionsNamespaceInDescriptor - ? optionsNamespace - : undefined, - }, - { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledWith( - [ - { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrThree: 'three', - }, - version: 'some-version', - namespace: objectNamespace, - references: undefined, - }, - ], - options - ); - }; - - it('does not use options `namespace` or object `namespace` to encrypt attributes if neither are specified', async () => { - await doTest({ - optionsNamespace: undefined, - objectNamespace: undefined, - expectOptionsNamespaceInDescriptor: false, - expectObjectNamespaceInDescriptor: false, - }); - }); - - describe('with a single-namespace type', () => { - it('uses options `namespace` to encrypt attributes if it is specified and object `namespace` is not', async () => { - await doTest({ - optionsNamespace: 'some-namespace', - objectNamespace: undefined, - expectOptionsNamespaceInDescriptor: true, - expectObjectNamespaceInDescriptor: false, - }); - }); - - it('uses object `namespace` to encrypt attributes if it is specified', async () => { - // object namespace supersedes options namespace - await doTest({ - optionsNamespace: 'some-namespace', - objectNamespace: 'another-namespace', - expectOptionsNamespaceInDescriptor: false, - expectObjectNamespaceInDescriptor: true, - }); - }); - }); - - describe('with a non-single-namespace type', () => { - it('does not use object `namespace` or options `namespace` to encrypt attributes if it is specified', async () => { - mockBaseTypeRegistry.isSingleNamespace.mockReturnValue(false); - await doTest({ - optionsNamespace: 'some-namespace', - objectNamespace: 'another-namespace', - expectOptionsNamespaceInDescriptor: false, - expectObjectNamespaceInDescriptor: false, - }); - }); - }); - }); - - it('fails if base client fails', async () => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - - const failureReason = new Error('Something bad happened...'); - mockBaseClient.bulkUpdate.mockRejectedValue(failureReason); - - await expect( - wrapper.bulkUpdate( - [{ type: 'unknown-type', id: 'some-id', attributes, version: 'some-version' }], - {} - ) - ).rejects.toThrowError(failureReason); - - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkUpdate).toHaveBeenCalledWith( - [{ type: 'unknown-type', id: 'some-id', attributes, version: 'some-version' }], - {} - ); - }); -}); - -describe('#delete', () => { - it('redirects request to underlying base client if type is not registered', async () => { - const options = { namespace: 'some-ns' }; - - await wrapper.delete('unknown-type', 'some-id', options); - - expect(mockBaseClient.delete).toHaveBeenCalledTimes(1); - expect(mockBaseClient.delete).toHaveBeenCalledWith('unknown-type', 'some-id', options); - }); - - it('redirects request to underlying base client if type is registered', async () => { - const options = { namespace: 'some-ns' }; - - await wrapper.delete('known-type', 'some-id', options); - - expect(mockBaseClient.delete).toHaveBeenCalledTimes(1); - expect(mockBaseClient.delete).toHaveBeenCalledWith('known-type', 'some-id', options); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.delete.mockRejectedValue(failureReason); - - await expect(wrapper.delete('known-type', 'some-id')).rejects.toThrowError(failureReason); - - expect(mockBaseClient.delete).toHaveBeenCalledTimes(1); - expect(mockBaseClient.delete).toHaveBeenCalledWith('known-type', 'some-id', undefined); - }); -}); - -describe('#bulkDelete', () => { - const obj1 = Object.freeze({ type: 'unknown-type', id: 'unknown-type-id-1' }); - const obj2 = Object.freeze({ type: 'unknown-type', id: 'unknown-type-id-2' }); - const namespace = 'some-ns'; - - it('redirects request to underlying base client if type is not registered', async () => { - await wrapper.bulkDelete([obj1, obj2], { namespace }); - expect(mockBaseClient.bulkDelete).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkDelete).toHaveBeenCalledWith([obj1, obj2], { namespace }); - }); - - it('redirects request to underlying base client if type is registered', async () => { - const knownObj1 = Object.freeze({ type: 'known-type', id: 'known-type-id-1' }); - const knownObj2 = Object.freeze({ type: 'known-type', id: 'known-type-id-2' }); - const options = { namespace: 'some-ns' }; - - await wrapper.bulkDelete([knownObj1, knownObj2], options); - - expect(mockBaseClient.bulkDelete).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkDelete).toHaveBeenCalledWith([knownObj1, knownObj2], { namespace }); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.bulkDelete.mockRejectedValue(failureReason); - - await expect(wrapper.bulkDelete([{ type: 'known-type', id: 'some-id' }])).rejects.toThrowError( - failureReason - ); - - expect(mockBaseClient.bulkDelete).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkDelete).toHaveBeenCalledWith( - [{ type: 'known-type', id: 'some-id' }], - undefined - ); - }); -}); - -describe('#find', () => { - it('redirects request to underlying base client and does not alter response if type is not registered', async () => { - const mockedResponse = { - saved_objects: [ - { - id: 'some-id', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - score: 1, - references: [], - }, - { - id: 'some-id-2', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - score: 1, - references: [], - }, - ], - total: 2, - per_page: 2, - page: 1, - }; - - mockBaseClient.find.mockResolvedValue(mockedResponse); - - const options = { type: 'unknown-type', search: 'query' }; - await expect(wrapper.find(options)).resolves.toEqual({ - ...mockedResponse, - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }, - { - ...mockedResponse.saved_objects[1], - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }, - ], - }); - expect(mockBaseClient.find).toHaveBeenCalledTimes(1); - expect(mockBaseClient.find).toHaveBeenCalledWith(options); - }); - - it('redirects request to underlying base client and strips encrypted attributes except for ones with `dangerouslyExposeValue` set to `true` if type is registered', async () => { - const mockedResponse = { - saved_objects: [ - { - id: 'some-id', - type: 'unknown-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - score: 1, - references: [], - }, - { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - score: 1, - references: [], - }, - ], - total: 2, - per_page: 2, - page: 1, - }; - - mockBaseClient.find.mockResolvedValue(mockedResponse); - - const options = { type: ['unknown-type', 'known-type'], search: 'query' }; - await expect(wrapper.find(options)).resolves.toEqual({ - ...mockedResponse, - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - }, - { - ...mockedResponse.saved_objects[1], - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }, - ], - }); - expect(mockBaseClient.find).toHaveBeenCalledTimes(1); - expect(mockBaseClient.find).toHaveBeenCalledWith(options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id-2' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('includes both attributes and error if decryption fails.', async () => { - const mockedResponse = { - saved_objects: [ - { - id: 'some-id', - type: 'unknown-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - score: 1, - references: [], - }, - { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - score: 1, - references: [], - }, - ], - total: 2, - per_page: 2, - page: 1, - }; - - mockBaseClient.find.mockResolvedValue(mockedResponse); - - const decryptionError = new EncryptionError( - 'something failed', - 'attrNotSoSecret', - EncryptionErrorOperation.Decryption - ); - encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes.mockResolvedValue({ - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }); - - const options = { type: ['unknown-type', 'known-type'], search: 'query' }; - await expect(wrapper.find(options)).resolves.toEqual({ - ...mockedResponse, - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - }, - { - ...mockedResponse.saved_objects[1], - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }, - ], - }); - expect(mockBaseClient.find).toHaveBeenCalledTimes(1); - expect(mockBaseClient.find).toHaveBeenCalledWith(options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id-2' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.find.mockRejectedValue(failureReason); - - await expect(wrapper.find({ type: 'known-type' })).rejects.toThrowError(failureReason); - - expect(mockBaseClient.find).toHaveBeenCalledTimes(1); - expect(mockBaseClient.find).toHaveBeenCalledWith({ type: 'known-type' }); - }); -}); - -describe('#bulkGet', () => { - it('redirects request to underlying base client and does not alter response if type is not registered', async () => { - const mockedResponse = { - saved_objects: [ - { - id: 'some-id', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - references: [], - }, - { - id: 'some-id-2', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - references: [], - }, - ], - total: 2, - per_page: 2, - page: 1, - }; - - mockBaseClient.bulkGet.mockResolvedValue(mockedResponse); - - const bulkGetParams = [ - { type: 'unknown-type', id: 'some-id' }, - { type: 'unknown-type', id: 'some-id-2' }, - ]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkGet(bulkGetParams, options)).resolves.toEqual({ - ...mockedResponse, - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }, - { - ...mockedResponse.saved_objects[1], - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }, - ], - }); - expect(mockBaseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkGet).toHaveBeenCalledWith(bulkGetParams, options); - }); - - it('redirects request to underlying base client and strips encrypted attributes except for ones with `dangerouslyExposeValue` set to `true` if type is registered', async () => { - const mockedResponse = { - saved_objects: [ - { - id: 'some-id', - type: 'unknown-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - ], - total: 2, - per_page: 2, - page: 1, - }; - - mockBaseClient.bulkGet.mockResolvedValue(mockedResponse); - - const bulkGetParams = [ - { type: 'unknown-type', id: 'some-id' }, - { type: 'known-type', id: 'some-id-2' }, - ]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkGet(bulkGetParams, options)).resolves.toEqual({ - ...mockedResponse, - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - }, - { - ...mockedResponse.saved_objects[1], - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }, - ], - }); - expect(mockBaseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkGet).toHaveBeenCalledWith(bulkGetParams, options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id-2', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('includes both attributes and error if decryption fails.', async () => { - const mockedResponse = { - saved_objects: [ - { - id: 'some-id', - type: 'unknown-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - ], - total: 2, - per_page: 2, - page: 1, - }; - - mockBaseClient.bulkGet.mockResolvedValue(mockedResponse); - - const decryptionError = new EncryptionError( - 'something failed', - 'attrNotSoSecret', - EncryptionErrorOperation.Decryption - ); - encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes.mockResolvedValue({ - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }); - - const bulkGetParams = [ - { type: 'unknown-type', id: 'some-id' }, - { type: 'known-type', id: 'some-id-2' }, - ]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkGet(bulkGetParams, options)).resolves.toEqual({ - ...mockedResponse, - saved_objects: [ - { - ...mockedResponse.saved_objects[0], - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - }, - { - ...mockedResponse.saved_objects[1], - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }, - ], - }); - expect(mockBaseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkGet).toHaveBeenCalledWith(bulkGetParams, options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id-2', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.bulkGet.mockRejectedValue(failureReason); - - await expect(wrapper.bulkGet([{ type: 'known-type', id: 'some-id' }])).rejects.toThrowError( - failureReason - ); - - expect(mockBaseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkGet).toHaveBeenCalledWith( - [{ type: 'known-type', id: 'some-id' }], - undefined - ); - }); - - it('redirects request to underlying base client and return errors result if type is registered', async () => { - const mockedResponse = { - saved_objects: [ - { - id: 'bad', - type: 'known-type', - error: { statusCode: 404, message: 'Not found' }, - }, - ], - total: 1, - per_page: 1, - page: 1, - }; - mockBaseClient.bulkGet.mockResolvedValue(mockedResponse as any); - const bulkGetParams = [{ type: 'known-type', id: 'bad' }]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkGet(bulkGetParams, options)).resolves.toEqual({ - ...mockedResponse, - saved_objects: [{ ...mockedResponse.saved_objects[0] }], - }); - expect(mockBaseClient.bulkGet).toHaveBeenCalledTimes(1); - }); -}); - -describe('#get', () => { - it('redirects request to underlying base client and does not alter response if type is not registered', async () => { - const mockedResponse = { - id: 'some-id', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - references: [], - }; - - mockBaseClient.get.mockResolvedValue(mockedResponse); - - const options = { namespace: 'some-ns' }; - await expect(wrapper.get('unknown-type', 'some-id', options)).resolves.toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }); - expect(mockBaseClient.get).toHaveBeenCalledTimes(1); - expect(mockBaseClient.get).toHaveBeenCalledWith('unknown-type', 'some-id', options); - }); - - it('redirects request to underlying base client and strips encrypted attributes except for ones with `dangerouslyExposeValue` set to `true` if type is registered', async () => { - const mockedResponse = { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - references: [], - }; - - mockBaseClient.get.mockResolvedValue(mockedResponse); - - const options = { namespace: 'some-ns' }; - await expect(wrapper.get('known-type', 'some-id', options)).resolves.toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }); - expect(mockBaseClient.get).toHaveBeenCalledTimes(1); - expect(mockBaseClient.get).toHaveBeenCalledWith('known-type', 'some-id', options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('includes both attributes and error if decryption fails.', async () => { - const mockedResponse = { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - references: [], - }; - - mockBaseClient.get.mockResolvedValue(mockedResponse); - - const decryptionError = new EncryptionError( - 'something failed', - 'attrNotSoSecret', - EncryptionErrorOperation.Decryption - ); - encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes.mockResolvedValue({ - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }); - - const options = { namespace: 'some-ns' }; - await expect(wrapper.get('known-type', 'some-id', options)).resolves.toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }); - expect(mockBaseClient.get).toHaveBeenCalledTimes(1); - expect(mockBaseClient.get).toHaveBeenCalledWith('known-type', 'some-id', options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.get.mockRejectedValue(failureReason); - - await expect(wrapper.get('known-type', 'some-id')).rejects.toThrowError(failureReason); - - expect(mockBaseClient.get).toHaveBeenCalledTimes(1); - expect(mockBaseClient.get).toHaveBeenCalledWith('known-type', 'some-id', undefined); - }); -}); - -describe('#bulkResolve', () => { - it('redirects request to underlying base client and does not alter response if type is not registered', async () => { - const mockedResponse = { - resolved_objects: [ - { - saved_object: { - id: 'some-id', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - references: [], - }, - }, - { - saved_object: { - id: 'some-id-2', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - references: [], - }, - }, - ], - }; - - mockBaseClient.bulkResolve.mockResolvedValue( - mockedResponse as unknown as SavedObjectsBulkResolveResponse - ); - - const bulkResolveParams = [ - { type: 'unknown-type', id: 'some-id' }, - { type: 'unknown-type', id: 'some-id-2' }, - ]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkResolve(bulkResolveParams, options)).resolves.toEqual(mockedResponse); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledWith(bulkResolveParams, options); - }); - - it('redirects request to underlying base client and strips encrypted attributes except for ones with `dangerouslyExposeValue` set to `true` if type is registered', async () => { - const mockedResponse = { - resolved_objects: [ - { - saved_object: { - id: 'some-id', - type: 'unknown-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - }, - { - saved_object: { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - }, - ], - }; - - mockBaseClient.bulkResolve.mockResolvedValue( - mockedResponse as unknown as SavedObjectsBulkResolveResponse - ); - - const bulkResolveParams = [ - { type: 'unknown-type', id: 'some-id' }, - { type: 'known-type', id: 'some-id-2' }, - ]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkResolve(bulkResolveParams, options)).resolves.toEqual({ - resolved_objects: [ - mockedResponse.resolved_objects[0], - { - saved_object: { - ...mockedResponse.resolved_objects[1].saved_object, - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }, - }, - ], - }); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledWith(bulkResolveParams, options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id-2', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('includes both attributes and error if decryption fails.', async () => { - const mockedResponse = { - resolved_objects: [ - { - saved_object: { - id: 'some-id', - type: 'unknown-type', - attributes: { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - }, - { - saved_object: { - id: 'some-id-2', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - namespaces: ['some-ns'], - references: [], - }, - }, - ], - }; - - mockBaseClient.bulkResolve.mockResolvedValue( - mockedResponse as unknown as SavedObjectsBulkResolveResponse - ); - - const decryptionError = new EncryptionError( - 'something failed', - 'attrNotSoSecret', - EncryptionErrorOperation.Decryption - ); - encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes.mockResolvedValue({ - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }); - - const bulkResolveParams = [ - { type: 'unknown-type', id: 'some-id' }, - { type: 'known-type', id: 'some-id-2' }, - ]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkResolve(bulkResolveParams, options)).resolves.toEqual({ - resolved_objects: [ - mockedResponse.resolved_objects[0], - { - saved_object: { - ...mockedResponse.resolved_objects[1].saved_object, - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }, - }, - ], - }); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledWith(bulkResolveParams, options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id-2', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.bulkResolve.mockRejectedValue(failureReason); - - await expect(wrapper.bulkResolve([{ type: 'known-type', id: 'some-id' }])).rejects.toThrowError( - failureReason - ); - - expect(mockBaseClient.bulkResolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledWith( - [{ type: 'known-type', id: 'some-id' }], - undefined - ); - }); - - it('redirects request to underlying base client and return errors result if type is registered', async () => { - const mockedResponse = { - resolved_objects: [ - { - saved_object: { - id: 'bad', - type: 'known-type', - error: { statusCode: 404, message: 'Not found' }, - }, - }, - ], - }; - mockBaseClient.bulkResolve.mockResolvedValue( - mockedResponse as unknown as SavedObjectsBulkResolveResponse - ); - const bulkGetParams = [{ type: 'known-type', id: 'bad' }]; - - const options = { namespace: 'some-ns' }; - await expect(wrapper.bulkResolve(bulkGetParams, options)).resolves.toEqual(mockedResponse); - expect(mockBaseClient.bulkResolve).toHaveBeenCalledTimes(1); - }); -}); - -describe('#resolve', () => { - it('redirects request to underlying base client and does not alter response if type is not registered', async () => { - const mockedResponse = { - saved_object: { - id: 'some-id', - type: 'unknown-type', - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - references: [], - }, - outcome: 'exactMatch' as 'exactMatch', - }; - - mockBaseClient.resolve.mockResolvedValue(mockedResponse); - - const options = { namespace: 'some-ns' }; - await expect(wrapper.resolve('unknown-type', 'some-id', options)).resolves.toEqual( - mockedResponse - ); - expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.resolve).toHaveBeenCalledWith('unknown-type', 'some-id', options); - }); - - it('redirects request to underlying base client and strips encrypted attributes except for ones with `dangerouslyExposeValue` set to `true` if type is registered', async () => { - const mockedResponse = { - saved_object: { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - references: [], - }, - outcome: 'exactMatch' as 'exactMatch', - }; - - mockBaseClient.resolve.mockResolvedValue(mockedResponse); - - const options = { namespace: 'some-ns' }; - await expect(wrapper.resolve('known-type', 'some-id', options)).resolves.toEqual({ - ...mockedResponse, - saved_object: { - ...mockedResponse.saved_object, - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }, - }); - expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.resolve).toHaveBeenCalledWith('known-type', 'some-id', options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('includes both attributes and error with modified outcome if decryption fails.', async () => { - const mockedResponse = { - saved_object: { - id: 'some-id', - type: 'known-type', - attributes: { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - references: [], - }, - outcome: 'exactMatch' as 'exactMatch', - }; - - mockBaseClient.resolve.mockResolvedValue(mockedResponse); - - const decryptionError = new EncryptionError( - 'something failed', - 'attrNotSoSecret', - EncryptionErrorOperation.Decryption - ); - encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes.mockResolvedValue({ - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }); - - const options = { namespace: 'some-ns' }; - await expect(wrapper.resolve('known-type', 'some-id', options)).resolves.toEqual({ - ...mockedResponse, - saved_object: { - ...mockedResponse.saved_object, - attributes: { attrOne: 'one', attrThree: 'three' }, - error: decryptionError, - }, - }); - expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.resolve).toHaveBeenCalledWith('known-type', 'some-id', options); - - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledTimes( - 1 - ); - expect(encryptedSavedObjectsServiceMockInstance.stripOrDecryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id', namespace: 'some-ns' }, - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - undefined, - { user: mockAuthenticatedUser() } - ); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.resolve.mockRejectedValue(failureReason); - - await expect(wrapper.resolve('known-type', 'some-id')).rejects.toThrowError(failureReason); - - expect(mockBaseClient.resolve).toHaveBeenCalledTimes(1); - expect(mockBaseClient.resolve).toHaveBeenCalledWith('known-type', 'some-id', undefined); - }); -}); - -describe('#update', () => { - it('redirects request to underlying base client if type is not registered', async () => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - const options = { version: 'some-version' }; - const mockedResponse = { id: 'some-id', type: 'unknown-type', attributes, references: [] }; - - mockBaseClient.update.mockResolvedValue(mockedResponse); - - await expect(wrapper.update('unknown-type', 'some-id', attributes, options)).resolves.toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - }); - expect(mockBaseClient.update).toHaveBeenCalledTimes(1); - expect(mockBaseClient.update).toHaveBeenCalledWith( - 'unknown-type', - 'some-id', - attributes, - options - ); - }); - - it('encrypts attributes and strips them from response except for ones with `dangerouslyExposeValue` set to `true`', async () => { - const attributes = { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }; - const options = { version: 'some-version' }; - const mockedResponse = { - id: 'some-id', - type: 'known-type', - attributes: { - ...attributes, - attrSecret: `*${attributes.attrSecret}*`, - attrNotSoSecret: `*${attributes.attrNotSoSecret}*`, - }, - references: [], - }; - - mockBaseClient.update.mockResolvedValue(mockedResponse); - - await expect(wrapper.update('known-type', 'some-id', attributes, options)).resolves.toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrNotSoSecret: 'not-so-secret', attrThree: 'three' }, - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { type: 'known-type', id: 'some-id' }, - { - attrOne: 'one', - attrSecret: 'secret', - attrNotSoSecret: 'not-so-secret', - attrThree: 'three', - }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.update).toHaveBeenCalledTimes(1); - expect(mockBaseClient.update).toHaveBeenCalledWith( - 'known-type', - 'some-id', - { - attrOne: 'one', - attrSecret: '*secret*', - attrNotSoSecret: '*not-so-secret*', - attrThree: 'three', - }, - options - ); - }); - - describe('namespace', () => { - const doTest = async (namespace: string, expectNamespaceInDescriptor: boolean) => { - const attributes = { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }; - const options = { version: 'some-version', namespace }; - const mockedResponse = { id: 'some-id', type: 'known-type', attributes, references: [] }; - - mockBaseClient.update.mockResolvedValue(mockedResponse); - - await expect(wrapper.update('known-type', 'some-id', attributes, options)).resolves.toEqual({ - ...mockedResponse, - attributes: { attrOne: 'one', attrThree: 'three' }, - }); - - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledTimes(1); - expect(encryptedSavedObjectsServiceMockInstance.encryptAttributes).toHaveBeenCalledWith( - { - type: 'known-type', - id: 'some-id', - namespace: expectNamespaceInDescriptor ? namespace : undefined, - }, - { attrOne: 'one', attrSecret: 'secret', attrThree: 'three' }, - { user: mockAuthenticatedUser() } - ); - - expect(mockBaseClient.update).toHaveBeenCalledTimes(1); - expect(mockBaseClient.update).toHaveBeenCalledWith( - 'known-type', - 'some-id', - { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - options - ); - }; - - it('uses `namespace` to encrypt attributes if it is specified when type is single-namespace', async () => { - await doTest('some-namespace', true); - }); - - it('does not use `namespace` to encrypt attributes if it is specified when type is not single-namespace', async () => { - mockBaseTypeRegistry.isSingleNamespace.mockReturnValue(false); - await doTest('some-namespace', false); - }); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.update.mockRejectedValue(failureReason); - - await expect( - wrapper.update('known-type', 'some-id', { - attrOne: 'one', - attrSecret: 'secret', - attrThree: 'three', - }) - ).rejects.toThrowError(failureReason); - - expect(mockBaseClient.update).toHaveBeenCalledTimes(1); - expect(mockBaseClient.update).toHaveBeenCalledWith( - 'known-type', - 'some-id', - { attrOne: 'one', attrSecret: '*secret*', attrThree: 'three' }, - undefined - ); - }); -}); - -describe('#removeReferencesTo', () => { - it('redirects request to underlying base client', async () => { - const options = { namespace: 'some-ns' }; - - await wrapper.removeReferencesTo('some-type', 'some-id', options); - - expect(mockBaseClient.removeReferencesTo).toHaveBeenCalledTimes(1); - expect(mockBaseClient.removeReferencesTo).toHaveBeenCalledWith('some-type', 'some-id', options); - }); - - it('returns response from underlying client', async () => { - const returnValue = { - updated: 12, - }; - mockBaseClient.removeReferencesTo.mockResolvedValue(returnValue); - - const result = await wrapper.removeReferencesTo('known-type', 'some-id'); - - expect(result).toBe(returnValue); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.removeReferencesTo.mockRejectedValue(failureReason); - - await expect(wrapper.removeReferencesTo('known-type', 'some-id')).rejects.toThrowError( - failureReason - ); - - expect(mockBaseClient.removeReferencesTo).toHaveBeenCalledTimes(1); - }); -}); - -describe('#openPointInTimeForType', () => { - it('redirects request to underlying base client', async () => { - const options = { keepAlive: '1m' }; - - await wrapper.openPointInTimeForType('some-type', options); - - expect(mockBaseClient.openPointInTimeForType).toHaveBeenCalledTimes(1); - expect(mockBaseClient.openPointInTimeForType).toHaveBeenCalledWith('some-type', options); - }); - - it('returns response from underlying client', async () => { - const returnValue = { - id: 'abc123', - }; - mockBaseClient.openPointInTimeForType.mockResolvedValue(returnValue); - - const result = await wrapper.openPointInTimeForType('known-type'); - - expect(result).toBe(returnValue); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.openPointInTimeForType.mockRejectedValue(failureReason); - - await expect(wrapper.openPointInTimeForType('known-type')).rejects.toThrowError(failureReason); - - expect(mockBaseClient.openPointInTimeForType).toHaveBeenCalledTimes(1); - }); -}); - -describe('#closePointInTime', () => { - it('redirects request to underlying base client', async () => { - const id = 'abc123'; - await wrapper.closePointInTime(id); - - expect(mockBaseClient.closePointInTime).toHaveBeenCalledTimes(1); - expect(mockBaseClient.closePointInTime).toHaveBeenCalledWith(id, undefined); - }); - - it('returns response from underlying client', async () => { - const returnValue = { - succeeded: true, - num_freed: 1, - }; - mockBaseClient.closePointInTime.mockResolvedValue(returnValue); - - const result = await wrapper.closePointInTime('abc123'); - - expect(result).toBe(returnValue); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.closePointInTime.mockRejectedValue(failureReason); - - await expect(wrapper.closePointInTime('abc123')).rejects.toThrowError(failureReason); - - expect(mockBaseClient.closePointInTime).toHaveBeenCalledTimes(1); - }); - - describe('#collectMultiNamespaceReferences', () => { - it('redirects request to underlying base client', async () => { - const objects = [{ type: 'foo', id: 'bar' }]; - const options = { namespace: 'some-ns' }; - await wrapper.collectMultiNamespaceReferences(objects, options); - - expect(mockBaseClient.collectMultiNamespaceReferences).toHaveBeenCalledTimes(1); - expect(mockBaseClient.collectMultiNamespaceReferences).toHaveBeenCalledWith(objects, options); - }); - - it('returns response from underlying client', async () => { - const returnValue = { objects: [] }; - mockBaseClient.collectMultiNamespaceReferences.mockResolvedValue(returnValue); - - const objects = [{ type: 'foo', id: 'bar' }]; - const result = await wrapper.collectMultiNamespaceReferences(objects); - - expect(result).toBe(returnValue); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.collectMultiNamespaceReferences.mockRejectedValue(failureReason); - - const objects = [{ type: 'foo', id: 'bar' }]; - await expect(wrapper.collectMultiNamespaceReferences(objects)).rejects.toThrowError( - failureReason - ); - - expect(mockBaseClient.collectMultiNamespaceReferences).toHaveBeenCalledTimes(1); - }); - }); - - describe('#updateObjectsSpaces', () => { - const objects = [{ type: 'foo', id: 'bar' }]; - const spacesToAdd = ['space-x']; - const spacesToRemove = ['space-y']; - const options = {}; - it('redirects request to underlying base client', async () => { - await wrapper.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options); - - expect(mockBaseClient.updateObjectsSpaces).toHaveBeenCalledTimes(1); - expect(mockBaseClient.updateObjectsSpaces).toHaveBeenCalledWith( - objects, - spacesToAdd, - spacesToRemove, - options - ); - }); - - it('returns response from underlying client', async () => { - const returnValue = { objects: [] }; - mockBaseClient.updateObjectsSpaces.mockResolvedValue(returnValue); - - const result = await wrapper.updateObjectsSpaces( - objects, - spacesToAdd, - spacesToRemove, - options - ); - - expect(result).toBe(returnValue); - }); - - it('fails if base client fails', async () => { - const failureReason = new Error('Something bad happened...'); - mockBaseClient.updateObjectsSpaces.mockRejectedValue(failureReason); - - await expect( - wrapper.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options) - ).rejects.toThrowError(failureReason); - - expect(mockBaseClient.updateObjectsSpaces).toHaveBeenCalledTimes(1); - }); - }); -}); - -describe('#createPointInTimeFinder', () => { - it('redirects request to underlying base client with default dependencies', () => { - const options = { type: ['a', 'b'], search: 'query' }; - wrapper.createPointInTimeFinder(options); - - expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, { - client: wrapper, - }); - }); - - it('redirects request to underlying base client with custom dependencies', () => { - const options = { type: ['a', 'b'], search: 'query' }; - const dependencies = { - client: { - find: jest.fn(), - openPointInTimeForType: jest.fn(), - closePointInTime: jest.fn(), - }, - }; - wrapper.createPointInTimeFinder(options, dependencies); - - expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(mockBaseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies); - }); -}); diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts deleted file mode 100644 index e2fcfd2a6ef251..00000000000000 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/encrypted_saved_objects_client_wrapper.ts +++ /dev/null @@ -1,399 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - ISavedObjectTypeRegistry, - SavedObject, - SavedObjectsBaseOptions, - SavedObjectsBulkCreateObject, - SavedObjectsBulkDeleteObject, - SavedObjectsBulkDeleteOptions, - SavedObjectsBulkGetObject, - SavedObjectsBulkResolveObject, - SavedObjectsBulkResponse, - SavedObjectsBulkUpdateObject, - SavedObjectsBulkUpdateResponse, - SavedObjectsCheckConflictsObject, - SavedObjectsClientContract, - SavedObjectsClosePointInTimeOptions, - SavedObjectsCollectMultiNamespaceReferencesObject, - SavedObjectsCollectMultiNamespaceReferencesOptions, - SavedObjectsCollectMultiNamespaceReferencesResponse, - SavedObjectsCreateOptions, - SavedObjectsCreatePointInTimeFinderDependencies, - SavedObjectsCreatePointInTimeFinderOptions, - SavedObjectsFindOptions, - SavedObjectsFindResponse, - SavedObjectsOpenPointInTimeOptions, - SavedObjectsRemoveReferencesToOptions, - SavedObjectsRemoveReferencesToResponse, - SavedObjectsUpdateObjectsSpacesObject, - SavedObjectsUpdateObjectsSpacesOptions, - SavedObjectsUpdateOptions, - SavedObjectsUpdateResponse, -} from '@kbn/core/server'; -import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '@kbn/core/server'; -import type { AuthenticatedUser } from '@kbn/security-plugin/common/model'; - -import type { EncryptedSavedObjectsService } from '../crypto'; -import { getDescriptorNamespace } from './get_descriptor_namespace'; - -interface EncryptedSavedObjectsClientOptions { - baseClient: SavedObjectsClientContract; - baseTypeRegistry: ISavedObjectTypeRegistry; - service: Readonly; - getCurrentUser: () => AuthenticatedUser | undefined; -} - -export class EncryptedSavedObjectsClientWrapper implements SavedObjectsClientContract { - constructor( - private readonly options: EncryptedSavedObjectsClientOptions, - public readonly errors = SavedObjectsErrorHelpers - ) {} - - public async checkConflicts( - objects: SavedObjectsCheckConflictsObject[] = [], - options?: SavedObjectsBaseOptions - ) { - return await this.options.baseClient.checkConflicts(objects, options); - } - - public async create( - type: string, - attributes: T = {} as T, - options: SavedObjectsCreateOptions = {} - ) { - if (!this.options.service.isRegistered(type)) { - return await this.options.baseClient.create(type, attributes, options); - } - - const id = this.getValidId(options.id, options.version, options.overwrite); - const namespace = getDescriptorNamespace( - this.options.baseTypeRegistry, - type, - options.namespace - ); - return await this.handleEncryptedAttributesInResponse( - await this.options.baseClient.create( - type, - (await this.options.service.encryptAttributes( - { type, id, namespace }, - attributes as Record, - { user: this.options.getCurrentUser() } - )) as T, - { ...options, id } - ), - attributes, - namespace - ); - } - - public async bulkCreate( - objects: Array>, - options?: SavedObjectsBaseOptions & Pick - ) { - // We encrypt attributes for every object in parallel and that can potentially exhaust libuv or - // NodeJS thread pool. If it turns out to be a problem, we can consider switching to the - // sequential processing. - const encryptedObjects = await Promise.all( - objects.map(async (object) => { - if (!this.options.service.isRegistered(object.type)) { - return object; - } - - const id = this.getValidId(object.id, object.version, options?.overwrite); - const namespace = getDescriptorNamespace( - this.options.baseTypeRegistry, - object.type, - options?.namespace - ); - return { - ...object, - id, - attributes: await this.options.service.encryptAttributes( - { type: object.type, id, namespace }, - object.attributes as Record, - { user: this.options.getCurrentUser() } - ), - } as SavedObjectsBulkCreateObject; - }) - ); - - return await this.handleEncryptedAttributesInBulkResponse( - await this.options.baseClient.bulkCreate(encryptedObjects, options), - objects - ); - } - - public async bulkUpdate( - objects: Array>, - options?: SavedObjectsBaseOptions - ) { - // We encrypt attributes for every object in parallel and that can potentially exhaust libuv or - // NodeJS thread pool. If it turns out to be a problem, we can consider switching to the - // sequential processing. - const encryptedObjects = await Promise.all( - objects.map(async (object) => { - const { type, id, attributes, namespace: objectNamespace } = object; - if (!this.options.service.isRegistered(type)) { - return object; - } - const namespace = getDescriptorNamespace( - this.options.baseTypeRegistry, - type, - objectNamespace ?? options?.namespace - ); - return { - ...object, - attributes: await this.options.service.encryptAttributes( - { type, id, namespace }, - attributes, - { user: this.options.getCurrentUser() } - ), - }; - }) - ); - - return await this.handleEncryptedAttributesInBulkResponse( - await this.options.baseClient.bulkUpdate(encryptedObjects, options), - objects - ); - } - - public async delete(type: string, id: string, options?: SavedObjectsBaseOptions) { - return await this.options.baseClient.delete(type, id, options); - } - - public async bulkDelete( - objects: SavedObjectsBulkDeleteObject[], - options?: SavedObjectsBulkDeleteOptions - ) { - return await this.options.baseClient.bulkDelete(objects, options); - } - - public async find(options: SavedObjectsFindOptions) { - return await this.handleEncryptedAttributesInBulkResponse( - await this.options.baseClient.find(options), - undefined - ); - } - - public async bulkGet( - objects: SavedObjectsBulkGetObject[] = [], - options?: SavedObjectsBaseOptions - ) { - return await this.handleEncryptedAttributesInBulkResponse( - await this.options.baseClient.bulkGet(objects, options), - undefined - ); - } - - public async get(type: string, id: string, options?: SavedObjectsBaseOptions) { - return await this.handleEncryptedAttributesInResponse( - await this.options.baseClient.get(type, id, options), - undefined as unknown, - getDescriptorNamespace(this.options.baseTypeRegistry, type, options?.namespace) - ); - } - - public async bulkResolve( - objects: SavedObjectsBulkResolveObject[], - options?: SavedObjectsBaseOptions - ) { - const bulkResolveResult = await this.options.baseClient.bulkResolve(objects, options); - - for (const resolved of bulkResolveResult.resolved_objects) { - const savedObject = resolved.saved_object; - await this.handleEncryptedAttributesInResponse( - savedObject, - undefined as unknown, - getDescriptorNamespace( - this.options.baseTypeRegistry, - savedObject.type, - savedObject.namespaces ? savedObject.namespaces[0] : undefined - ) - ); - } - - return bulkResolveResult; - } - - public async resolve(type: string, id: string, options?: SavedObjectsBaseOptions) { - const resolveResult = await this.options.baseClient.resolve(type, id, options); - const object = await this.handleEncryptedAttributesInResponse( - resolveResult.saved_object, - undefined as unknown, - getDescriptorNamespace(this.options.baseTypeRegistry, type, options?.namespace) - ); - return { - ...resolveResult, - saved_object: object, - }; - } - - public async update( - type: string, - id: string, - attributes: Partial, - options?: SavedObjectsUpdateOptions - ) { - if (!this.options.service.isRegistered(type)) { - return await this.options.baseClient.update(type, id, attributes, options); - } - const namespace = getDescriptorNamespace( - this.options.baseTypeRegistry, - type, - options?.namespace - ); - return this.handleEncryptedAttributesInResponse( - await this.options.baseClient.update( - type, - id, - await this.options.service.encryptAttributes({ type, id, namespace }, attributes, { - user: this.options.getCurrentUser(), - }), - options - ), - attributes, - namespace - ); - } - - public async removeReferencesTo( - type: string, - id: string, - options: SavedObjectsRemoveReferencesToOptions = {} - ): Promise { - return await this.options.baseClient.removeReferencesTo(type, id, options); - } - - public async openPointInTimeForType( - type: string | string[], - options: SavedObjectsOpenPointInTimeOptions = {} - ) { - return await this.options.baseClient.openPointInTimeForType(type, options); - } - - public async closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions) { - return await this.options.baseClient.closePointInTime(id, options); - } - - public createPointInTimeFinder( - findOptions: SavedObjectsCreatePointInTimeFinderOptions, - dependencies?: SavedObjectsCreatePointInTimeFinderDependencies - ) { - return this.options.baseClient.createPointInTimeFinder(findOptions, { - client: this, - // Include dependencies last so that subsequent SO client wrappers have their settings applied. - ...dependencies, - }); - } - - public async collectMultiNamespaceReferences( - objects: SavedObjectsCollectMultiNamespaceReferencesObject[], - options?: SavedObjectsCollectMultiNamespaceReferencesOptions - ): Promise { - return await this.options.baseClient.collectMultiNamespaceReferences(objects, options); - } - - public async updateObjectsSpaces( - objects: SavedObjectsUpdateObjectsSpacesObject[], - spacesToAdd: string[], - spacesToRemove: string[], - options?: SavedObjectsUpdateObjectsSpacesOptions - ) { - return await this.options.baseClient.updateObjectsSpaces( - objects, - spacesToAdd, - spacesToRemove, - options - ); - } - - /** - * Strips encrypted attributes from any non-bulk Saved Objects API response. If type isn't - * registered, response is returned as is. - * @param response Raw response returned by the underlying base client. - * @param [originalAttributes] Optional list of original attributes of the saved object. - * @param [namespace] Optional namespace that was used for the saved objects operation. - */ - private async handleEncryptedAttributesInResponse< - T, - R extends SavedObjectsUpdateResponse | SavedObject - >(response: R, originalAttributes?: T, namespace?: string): Promise { - if (response.attributes && this.options.service.isRegistered(response.type)) { - // Error is returned when decryption fails, and in this case encrypted attributes will be - // stripped from the returned attributes collection. That will let consumer decide whether to - // fail or handle recovery gracefully. - const { attributes, error } = await this.options.service.stripOrDecryptAttributes( - { id: response.id, type: response.type, namespace }, - response.attributes as Record, - originalAttributes as Record, - { user: this.options.getCurrentUser() } - ); - - response.attributes = attributes as T; - if (error) { - response.error = error as any; - } - } - - return response; - } - - /** - * Strips encrypted attributes from any bulk Saved Objects API response. If type for any bulk - * response portion isn't registered, it is returned as is. - * @param response Raw response returned by the underlying base client. - * @param [objects] Optional list of saved objects with original attributes. - */ - private async handleEncryptedAttributesInBulkResponse< - T, - R extends - | SavedObjectsBulkResponse - | SavedObjectsFindResponse - | SavedObjectsBulkUpdateResponse, - O extends Array> | Array> - >(response: R, objects?: O) { - for (const [index, savedObject] of response.saved_objects.entries()) { - await this.handleEncryptedAttributesInResponse( - savedObject, - objects?.[index].attributes ?? undefined, - getDescriptorNamespace( - this.options.baseTypeRegistry, - savedObject.type, - savedObject.namespaces ? savedObject.namespaces[0] : undefined - ) - ); - } - - return response; - } - - // Saved objects with encrypted attributes should have IDs that are hard to guess especially - // since IDs are part of the AAD used during encryption, that's why we control them within this - // wrapper and don't allow consumers to specify their own IDs directly unless overwriting the original document. - private getValidId( - id: string | undefined, - version: string | undefined, - overwrite: boolean | undefined - ) { - if (id) { - // only allow a specified ID if we're overwriting an existing ESO with a Version - // this helps us ensure that the document really was previously created using ESO - // and not being used to get around the specified ID limitation - const canSpecifyID = (overwrite && version) || SavedObjectsUtils.isRandomId(id); - if (!canSpecifyID) { - throw this.errors.createBadRequestError( - 'Predefined IDs are not allowed for saved objects with encrypted attributes unless the ID is a UUID.' - ); - } - return id; - } - return SavedObjectsUtils.generateId(); - } -} diff --git a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts index 39c3cee2fdeb13..d324f3a6fcd1cc 100644 --- a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts @@ -120,7 +120,7 @@ describe('upgrade agent policy schema version', () => { beforeAll(async () => { soClient = kbnServer.coreStart.savedObjects.getScopedClient(fakeRequest, { - excludedWrappers: ['security'], + excludedExtensions: ['security'], }); esClient = kbnServer.coreStart.elasticsearch.client.asInternalUser; }); diff --git a/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts b/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts index 4ec403ab4a3186..c6eb6873067c07 100644 --- a/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts @@ -154,7 +154,7 @@ describe('Uprade package install version', () => { beforeAll(async () => { soClient = kbnServer.coreStart.savedObjects.getScopedClient(fakeRequest, { - excludedWrappers: ['security'], + excludedExtensions: ['security'], }); const res = await soClient.find({ diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index bbd55a517753a7..72eb69e9aa5c97 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -363,7 +363,7 @@ export class FleetPlugin get internalSoClient() { return appContextService .getSavedObjects() - .getScopedClient(request, { excludedWrappers: ['security'] }); + .getScopedClient(request, { excludedExtensions: ['security'] }); }, }, get spaceId() { diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 3257f3e969ec80..6c241377081e82 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -161,7 +161,7 @@ class AppContextService { public getInternalUserSOClient(request: KibanaRequest) { // soClient as kibana internal users, be careful on how you use it, security is not enabled return appContextService.getSavedObjects().getScopedClient(request, { - excludedWrappers: ['security'], + excludedExtensions: ['security'], }); } diff --git a/x-pack/plugins/security/server/audit/audit_events.ts b/x-pack/plugins/security/server/audit/audit_events.ts index 1d077115970a62..5e38d5d53f224e 100644 --- a/x-pack/plugins/security/server/audit/audit_events.ts +++ b/x-pack/plugins/security/server/audit/audit_events.ts @@ -5,7 +5,10 @@ * 2.0. */ -import type { AddAuditEventParams as SavedObjectEventParams } from '@kbn/core-saved-objects-server'; +import type { + AuditAction, + AddAuditEventParams as SavedObjectEventParams, +} from '@kbn/core-saved-objects-server'; import type { EcsEventOutcome, EcsEventType, KibanaRequest, LogMeta } from '@kbn/core/server'; import type { AuthenticationProvider } from '../../common/model'; @@ -225,23 +228,9 @@ export function accessAgreementAcknowledgedEvent({ }; } -export enum SavedObjectAction { - CREATE = 'saved_object_create', - GET = 'saved_object_get', - RESOLVE = 'saved_object_resolve', - UPDATE = 'saved_object_update', - DELETE = 'saved_object_delete', - FIND = 'saved_object_find', - REMOVE_REFERENCES = 'saved_object_remove_references', - OPEN_POINT_IN_TIME = 'saved_object_open_point_in_time', - CLOSE_POINT_IN_TIME = 'saved_object_close_point_in_time', - COLLECT_MULTINAMESPACE_REFERENCES = 'saved_object_collect_multinamespace_references', // this is separate from 'saved_object_get' because the user is only accessing an object's metadata - UPDATE_OBJECTS_SPACES = 'saved_object_update_objects_spaces', // this is separate from 'saved_object_update' because the user is only updating an object's metadata -} - type VerbsTuple = [string, string, string]; -const savedObjectAuditVerbs: Record = { +const savedObjectAuditVerbs: Record = { saved_object_create: ['create', 'creating', 'created'], saved_object_get: ['access', 'accessing', 'accessed'], saved_object_resolve: ['resolve', 'resolving', 'resolved'], @@ -275,7 +264,7 @@ const savedObjectAuditVerbs: Record = { ], }; -const savedObjectAuditTypes: Record = { +const savedObjectAuditTypes: Record = { saved_object_create: 'creation', saved_object_get: 'access', saved_object_resolve: 'access', @@ -289,16 +278,6 @@ const savedObjectAuditTypes: Record = { saved_object_update_objects_spaces: 'change', }; -// This definition comes from '@kbn/core-saved-objects-server' now per Joe's changes and Pierre's migration to packages -// export interface SavedObjectEventParams { -// action: SavedObjectAction; -// outcome?: EcsEventOutcome; -// savedObject?: NonNullable['saved_object']; -// addToSpaces?: readonly string[]; -// deleteFromSpaces?: readonly string[]; -// error?: Error; -// } - export function savedObjectEvent({ action, savedObject, diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 9e5724b6a393b8..573af8dbb7b670 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -311,7 +311,6 @@ export class SecurityPlugin audit: this.auditSetup, authz: this.authorizationSetup, savedObjects: core.savedObjects, - getSpacesService: () => spaces?.spacesService, }); this.registerDeprecations(core, license); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.mocks.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.mocks.ts deleted file mode 100644 index 9e772f5394cc2b..00000000000000 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.mocks.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ensureAuthorized } from './ensure_authorized'; - -export const mockEnsureAuthorized = jest.fn() as jest.MockedFunction; - -jest.mock('./ensure_authorized', () => { - return { - ...jest.requireActual('./ensure_authorized'), - ensureAuthorized: mockEnsureAuthorized, - }; -}); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts deleted file mode 100644 index bce6786f067756..00000000000000 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.test.ts +++ /dev/null @@ -1,1984 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import { mockEnsureAuthorized } from './secure_saved_objects_client_wrapper.test.mocks'; - -import type { - EcsEventOutcome, - SavedObject, - SavedObjectReferenceWithContext, - SavedObjectsErrorHelpers, - SavedObjectsResolveResponse, - SavedObjectsUpdateObjectsSpacesResponseObject, -} from '@kbn/core/server'; -import { savedObjectsClientMock } from '@kbn/core/server/mocks'; - -import type { AuditEvent } from '../audit'; -import { auditLoggerMock } from '../audit/mocks'; -import { Actions } from '../authorization'; -import type { SavedObjectActions } from '../authorization/actions/saved_object'; -import { SecureSavedObjectsClientWrapper } from './secure_saved_objects_client_wrapper'; - -jest.mock('@kbn/core-saved-objects-utils-server', () => { - const { SavedObjectsUtils, ...actual } = jest.requireActual( - '@kbn/core-saved-objects-utils-server' - ); - return { - ...actual, - SavedObjectsUtils: { - ...SavedObjectsUtils, - createEmptyFindResponse: SavedObjectsUtils.createEmptyFindResponse, - generateId: () => 'mock-saved-object-id', - }, - }; -}); - -let clientOpts: ReturnType; -let client: SecureSavedObjectsClientWrapper; -const USERNAME = Symbol(); - -const createSecureSavedObjectsClientWrapperOptions = () => { - const actions = new Actions('some-version'); - jest - .spyOn(actions.savedObject, 'get') - .mockImplementation((type: string, action: string) => `mock-saved_object:${type}/${action}`); - - const forbiddenError = new Error('Mock ForbiddenError'); - const generalError = new Error('Mock GeneralError'); - - const errors = { - decorateForbiddenError: jest.fn().mockReturnValue(forbiddenError), - decorateGeneralError: jest.fn().mockReturnValue(generalError), - createBadRequestError: jest.fn().mockImplementation((message) => new Error(message)), - isNotFoundError: jest.fn().mockReturnValue(false), - } as unknown as jest.Mocked; - const getSpacesService = jest.fn().mockReturnValue({ - namespaceToSpaceId: (namespace?: string) => (namespace ? namespace : 'default'), - }); - - return { - actions, - baseClient: savedObjectsClientMock.create(), - checkSavedObjectsPrivilegesAsCurrentUser: jest.fn(), - errors, - getSpacesService, - auditLogger: auditLoggerMock.create(), - forbiddenError, - generalError, - }; -}; - -const expectGeneralError = async (fn: Function, args: Record) => { - // mock the checkPrivileges.globally rejection - const rejection = new Error('An actual error would happen here'); - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(rejection); - - await expect(fn.bind(client)(...Object.values(args))).rejects.toThrowError( - clientOpts.generalError - ); - expect(clientOpts.errors.decorateGeneralError).toHaveBeenCalledTimes(1); -}; - -/** - * Fails the first authorization check, passes any others - * Requires that function args are passed in as key/value pairs - * The argument properties must be in the correct order to be spread properly - */ -const expectForbiddenError = async (fn: Function, args: Record, action?: string) => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure - ); - - await expect(fn.bind(client)(...Object.values(args))).rejects.toThrowError( - clientOpts.forbiddenError - ); - - expect(clientOpts.errors.decorateForbiddenError).toHaveBeenCalledTimes(1); -}; - -const expectSuccess = async (fn: Function, args: Record, action?: string) => { - return await fn.bind(client)(...Object.values(args)); -}; - -const expectPrivilegeCheck = async ( - fn: Function, - args: Record, - namespaceOrNamespaces: string | undefined | Array -) => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure - ); - - await expect(fn.bind(client)(...Object.values(args))).rejects.toThrow(); // test is simpler with error case - const getResults = ( - clientOpts.actions.savedObject.get as jest.MockedFunction - ).mock.results; - const actions = getResults.map((x) => x.value); - - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(1); - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledWith( - actions, - namespaceOrNamespaces - ); -}; - -const expectObjectNamespaceFiltering = async ( - fn: Function, - args: Record, - privilegeChecks = 1 -) => { - for (let i = 0; i < privilegeChecks; i++) { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( - getMockCheckPrivilegesSuccess // privilege check for authorization - ); - } - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure // privilege check for namespace filtering - ); - - const authorizedNamespace = args.options?.namespace || 'default'; - const namespaces = ['some-other-namespace', '*', authorizedNamespace]; - const returnValue = { namespaces, foo: 'bar' }; - // we don't know which base client method will be called; mock them all - clientOpts.baseClient.create.mockReturnValue(returnValue as any); - clientOpts.baseClient.get.mockReturnValue(returnValue as any); - // 'resolve' is excluded because it has a specific test case written for it - clientOpts.baseClient.update.mockReturnValue(returnValue as any); - - const result = await fn.bind(client)(...Object.values(args)); - // we will never redact the "All Spaces" ID - expect(result).toEqual(expect.objectContaining({ namespaces: ['*', authorizedNamespace, '?'] })); - - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes( - privilegeChecks + 1 - ); - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( - 'login:', - ['some-other-namespace'] - // when we check what namespaces to redact, we don't check privileges for '*', only actual space IDs - // we don't check privileges for authorizedNamespace either, as that was already checked earlier in the operation - ); -}; - -const expectAuditEvent = ( - action: string, - outcome: EcsEventOutcome, - savedObject?: Required['kibana']['saved_object'] -) => { - expect(clientOpts.auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: expect.objectContaining({ - action, - outcome, - }), - kibana: savedObject - ? expect.objectContaining({ - saved_object: { type: savedObject.type, id: savedObject.id }, - }) - : expect.anything(), - }) - ); -}; - -const expectObjectsNamespaceFiltering = async (fn: Function, args: Record) => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( - getMockCheckPrivilegesSuccess // privilege check for authorization - ); - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure // privilege check for namespace filtering - ); - - // the 'find' operation has options.namespaces, the others have options.namespace - const authorizedNamespaces = - args.options.namespaces ?? (args.options.namespace ? [args.options.namespace] : ['default']); - const returnValue = { - saved_objects: [ - { namespaces: ['*'] }, - { namespaces: authorizedNamespaces }, - { namespaces: ['some-other-namespace', ...authorizedNamespaces] }, - ], - }; - - // we don't know which base client method will be called; mock them all - clientOpts.baseClient.bulkCreate.mockReturnValue(returnValue as any); - clientOpts.baseClient.bulkGet.mockReturnValue(returnValue as any); - clientOpts.baseClient.bulkUpdate.mockReturnValue(returnValue as any); - clientOpts.baseClient.find.mockReturnValue(returnValue as any); - - const result = await fn.bind(client)(...Object.values(args)); - expect(result).toEqual({ - saved_objects: [ - { namespaces: ['*'] }, - { namespaces: authorizedNamespaces }, - { namespaces: [...authorizedNamespaces, '?'] }, - ], - }); - - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( - 'login:', - ['some-other-namespace'] - // when we check what namespaces to redact, we don't check privileges for '*', only actual space IDs - // we don't check privileges for authorizedNamespaces either, as that was already checked earlier in the operation - ); -}; - -function getMockCheckPrivilegesSuccess(actions: string | string[], namespaces?: string | string[]) { - const _namespaces = Array.isArray(namespaces) ? namespaces : [namespaces || 'default']; - const _actions = Array.isArray(actions) ? actions : [actions]; - return { - hasAllRequested: true, - username: USERNAME, - privileges: { - kibana: _namespaces - .map((resource) => - _actions.map((action) => ({ - resource, - privilege: action, - authorized: true, - })) - ) - .flat(), - }, - }; -} - -/** - * Fails the authorization check for the first privilege, and passes any others - * This check may be for an action for two different types in the same namespace - * Or, it may be for an action for the same type in two different namespaces - * Either way, the first privilege check returned is false, and any others return true - */ -function getMockCheckPrivilegesFailure(actions: string | string[], namespaces?: string | string[]) { - const _namespaces = Array.isArray(namespaces) ? namespaces : [namespaces || 'default']; - const _actions = Array.isArray(actions) ? actions : [actions]; - return { - hasAllRequested: false, - username: USERNAME, - privileges: { - kibana: _namespaces - .map((resource, idxa) => - _actions.map((action, idxb) => ({ - resource, - privilege: action, - authorized: idxa > 0 || idxb > 0, - })) - ) - .flat(), - }, - }; -} - -/** - * Before each test, create the Client with its Options - */ -beforeEach(() => { - clientOpts = createSecureSavedObjectsClientWrapperOptions(); - client = new SecureSavedObjectsClientWrapper(clientOpts); - - // succeed legacyEnsureAuthorized privilege checks by default - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesSuccess - ); - - mockEnsureAuthorized.mockReset(); -}); - -describe('#bulkCreate', () => { - const attributes = { some: 'attr' }; - const obj1 = Object.freeze({ type: 'foo', id: 'sup', attributes }); - const obj2 = Object.freeze({ type: 'bar', id: 'everyone', attributes }); - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - const objects = [obj1]; - await expectGeneralError(client.bulkCreate, { objects }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectForbiddenError(client.bulkCreate, { objects, options }); - }); - - test(`returns result of baseClient.bulkCreate when authorized`, async () => { - const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; - clientOpts.baseClient.bulkCreate.mockReturnValue(apiCallReturnValue as any); - - const objects = [obj1, obj2]; - const options = { namespace }; - const result = await expectSuccess(client.bulkCreate, { objects, options }); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectPrivilegeCheck(client.bulkCreate, { objects, options }, [namespace]); - }); - - test(`checks privileges for user, actions, namespace, and initialNamespaces`, async () => { - const objects = [ - { ...obj1, initialNamespaces: 'another-ns' }, - { ...obj2, initialNamespaces: 'yet-another-ns' }, - ]; - const options = { namespace }; - await expectPrivilegeCheck(client.bulkCreate, { objects, options }, [ - namespace, - 'another-ns', - 'yet-another-ns', - ]); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectObjectsNamespaceFiltering(client.bulkCreate, { objects, options }); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; - clientOpts.baseClient.bulkCreate.mockReturnValue(apiCallReturnValue as any); - const objects = [obj1, obj2]; - const options = { namespace }; - await expectSuccess(client.bulkCreate, { objects, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_create', 'unknown', { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_create', 'unknown', { type: obj2.type, id: obj2.id }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.bulkCreate([obj1, obj2], { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_create', 'failure', { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_create', 'failure', { type: obj2.type, id: obj2.id }); - }); -}); - -describe('#bulkGet', () => { - const obj1 = Object.freeze({ type: 'foo', id: 'foo-id' }); - const obj2 = Object.freeze({ type: 'bar', id: 'bar-id' }); - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - const objects = [obj1]; - await expectGeneralError(client.bulkGet, { objects }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectForbiddenError(client.bulkGet, { objects, options }); - }); - - test(`returns result of baseClient.bulkGet when authorized`, async () => { - const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; - clientOpts.baseClient.bulkGet.mockReturnValue(apiCallReturnValue as any); - - const objects = [obj1, obj2]; - const options = { namespace }; - const result = await expectSuccess(client.bulkGet, { objects, options }); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, namespace, and (object) namespaces`, async () => { - const objects = [ - { ...obj1, namespaces: ['another-ns'] }, - { ...obj2, namespaces: ['yet-another-ns'] }, - ]; - const options = { namespace }; - await expectPrivilegeCheck(client.bulkGet, { objects, options }, [ - namespace, - 'another-ns', - 'yet-another-ns', - ]); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectObjectsNamespaceFiltering(client.bulkGet, { objects, options }); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = { saved_objects: [obj1, obj2], foo: 'bar' }; - clientOpts.baseClient.bulkGet.mockReturnValue(apiCallReturnValue as any); - const objects = [obj1, obj2]; - const options = { namespace }; - await expectSuccess(client.bulkGet, { objects, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_get', 'success', obj1); - expectAuditEvent('saved_object_get', 'success', obj2); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.bulkGet([obj1, obj2], { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_get', 'failure', obj1); - expectAuditEvent('saved_object_get', 'failure', obj2); - }); -}); - -describe('#bulkResolve', () => { - const obj1 = Object.freeze({ type: 'foo', id: 'foo-id' }); - const obj2 = Object.freeze({ type: 'bar', id: 'bar-id' }); - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - const objects = [obj1]; - await expectGeneralError(client.bulkResolve, { objects }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectForbiddenError(client.bulkResolve, { objects, options }, 'bulk_resolve'); - }); - - test(`returns result of baseClient.bulkResolve when authorized`, async () => { - const apiCallReturnValue = { resolved_objects: [] }; - clientOpts.baseClient.bulkResolve.mockResolvedValue(apiCallReturnValue); - - const objects = [obj1, obj2]; - const options = { namespace }; - const result = await expectSuccess(client.bulkResolve, { objects, options }, 'bulk_resolve'); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectPrivilegeCheck(client.bulkResolve, { objects, options }, namespace); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( - getMockCheckPrivilegesSuccess // privilege check for authorization - ); - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure // privilege check for namespace filtering - ); - - clientOpts.baseClient.bulkResolve.mockResolvedValue({ - resolved_objects: [ - // omit other fields from the SavedObjectsResolveResponse such as outcome, as they are not needed for this test case - { saved_object: { namespaces: ['*'] } } as unknown as SavedObjectsResolveResponse, - { saved_object: { namespaces: [namespace] } } as unknown as SavedObjectsResolveResponse, - { - saved_object: { namespaces: ['some-other-namespace', namespace] }, - } as unknown as SavedObjectsResolveResponse, - ], - }); - - const result = await client.bulkResolve(objects, options); - expect(result).toEqual({ - resolved_objects: [ - { saved_object: { namespaces: ['*'] } }, - { saved_object: { namespaces: [namespace] } }, - { saved_object: { namespaces: [namespace, '?'] } }, - ], - }); - - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( - 'login:', - ['some-other-namespace'] - // when we check what namespaces to redact, we don't check privileges for '*', only actual space IDs - // we don't check privileges for authorizedNamespaces either, as that was already checked earlier in the operation - ); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = { - resolved_objects: [ - { saved_object: obj1 } as unknown as SavedObjectsResolveResponse, - { saved_object: obj2 } as unknown as SavedObjectsResolveResponse, - ], - }; - clientOpts.baseClient.bulkResolve.mockResolvedValue(apiCallReturnValue); - const objects = [obj1, obj2]; - const options = { namespace }; - await expectSuccess(client.bulkResolve, { objects, options }, 'bulk_resolve'); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_resolve', 'success', obj1); - expectAuditEvent('saved_object_resolve', 'success', obj2); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.bulkResolve([obj1, obj2], { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_resolve', 'failure', obj1); - expectAuditEvent('saved_object_resolve', 'failure', obj2); - }); -}); - -describe('#bulkUpdate', () => { - const obj1 = Object.freeze({ type: 'foo', id: 'foo-id', attributes: { some: 'attr' } }); - const obj2 = Object.freeze({ type: 'bar', id: 'bar-id', attributes: { other: 'attr' } }); - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - const objects = [obj1]; - await expectGeneralError(client.bulkUpdate, { objects }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectForbiddenError(client.bulkUpdate, { objects, options }); - }); - - test(`returns result of baseClient.bulkUpdate when authorized`, async () => { - const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; - clientOpts.baseClient.bulkUpdate.mockReturnValue(apiCallReturnValue as any); - - const objects = [obj1, obj2]; - const options = { namespace }; - const result = await expectSuccess(client.bulkUpdate, { objects, options }); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - const namespaces = [options.namespace]; // the bulkUpdate function always checks privileges as an array - await expectPrivilegeCheck(client.bulkUpdate, { objects, options }, namespaces); - }); - - test(`checks privileges for object namespaces if present`, async () => { - const objects = [ - { ...obj1, namespace: 'foo-ns' }, - { ...obj2, namespace: 'bar-ns' }, - ]; - const namespaces = [undefined, 'foo-ns', 'bar-ns']; - const options = {}; // use the default namespace for the options - await expectPrivilegeCheck(client.bulkUpdate, { objects, options }, namespaces); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectObjectsNamespaceFiltering(client.bulkUpdate, { objects, options }); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; - clientOpts.baseClient.bulkUpdate.mockReturnValue(apiCallReturnValue as any); - const objects = [obj1, obj2]; - const options = { namespace }; - await expectSuccess(client.bulkUpdate, { objects, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_update', 'unknown', { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_update', 'unknown', { type: obj2.type, id: obj2.id }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.bulkUpdate([obj1, obj2], { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_update', 'failure', { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_update', 'failure', { type: obj2.type, id: obj2.id }); - }); -}); - -describe('#bulkDelete', () => { - const obj1 = Object.freeze({ type: 'foo', id: 'foo-id' }); - const obj2 = Object.freeze({ type: 'bar', id: 'bar-id' }); - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - const objects = [obj1]; - await expectGeneralError(client.bulkDelete, { objects }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectForbiddenError(client.bulkDelete, { objects, options }); - }); - - test(`returns result of baseClient.bulkDelete when authorized`, async () => { - const apiCallReturnValue = { - statuses: [obj1, obj2].map((obj) => { - return { ...obj, success: true }; - }), - }; - clientOpts.baseClient.bulkDelete.mockReturnValue(apiCallReturnValue as any); - - const objects = [obj1, obj2]; - const options = { namespace }; - const result = await expectSuccess(client.bulkDelete, { objects, options }); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectPrivilegeCheck(client.bulkDelete, { objects, options }, namespace); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = { - statuses: [obj1, obj2].map((obj) => { - return { ...obj, success: true }; - }), - }; - clientOpts.baseClient.bulkDelete.mockReturnValue(apiCallReturnValue as any); - - const objects = [obj1, obj2]; - const options = { namespace }; - await expectSuccess(client.bulkDelete, { objects, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_delete', 'success', { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_delete', 'success', { type: obj2.type, id: obj2.id }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.bulkDelete([obj1, obj2], { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_delete', 'failure', { type: obj1.type, id: obj1.id }); - expectAuditEvent('saved_object_delete', 'failure', { type: obj2.type, id: obj2.id }); - }); -}); - -describe('#checkConflicts', () => { - const obj1 = Object.freeze({ type: 'foo', id: 'foo-id' }); - const obj2 = Object.freeze({ type: 'bar', id: 'bar-id' }); - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when checkPrivileges.globally rejects promise`, async () => { - const objects = [obj1, obj2]; - await expectGeneralError(client.checkConflicts, { objects }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectForbiddenError(client.checkConflicts, { objects, options }, 'checkConflicts'); - }); - - test(`returns result of baseClient.create when authorized`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.checkConflicts.mockResolvedValue(apiCallReturnValue as any); - - const objects = [obj1, obj2]; - const options = { namespace }; - const result = await expectSuccess( - client.checkConflicts, - { objects, options }, - 'checkConflicts' - ); - expect(result).toBe(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const objects = [obj1, obj2]; - const options = { namespace }; - await expectPrivilegeCheck(client.checkConflicts, { objects, options }, namespace); - }); -}); - -describe('#create', () => { - const type = 'foo'; - const attributes = { some_attr: 's' }; - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when checkPrivileges.globally rejects promise`, async () => { - await expectGeneralError(client.create, { type }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const options = { id: 'mock-saved-object-id', namespace }; - await expectForbiddenError(client.create, { type, attributes, options }); - }); - - test(`returns result of baseClient.create when authorized`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.create.mockResolvedValue(apiCallReturnValue as any); - - const options = { id: 'mock-saved-object-id', namespace }; - const result = await expectSuccess(client.create, { - type, - attributes, - options, - }); - expect(result).toBe(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const options = { namespace }; - await expectPrivilegeCheck(client.create, { type, attributes, options }, [namespace]); - }); - - test(`checks privileges for user, actions, namespace, and initialNamespaces`, async () => { - const options = { namespace, initialNamespaces: ['another-ns', 'yet-another-ns'] }; - await expectPrivilegeCheck(client.create, { type, attributes, options }, [ - namespace, - 'another-ns', - 'yet-another-ns', - ]); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const options = { namespace }; - await expectObjectNamespaceFiltering(client.create, { type, attributes, options }); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.create.mockResolvedValue(apiCallReturnValue as any); - const options = { id: 'mock-saved-object-id', namespace }; - await expectSuccess(client.create, { type, attributes, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_create', 'unknown', { type, id: expect.any(String) }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.create(type, attributes, { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_create', 'failure', { type, id: expect.any(String) }); - }); -}); - -describe('#delete', () => { - const type = 'foo'; - const id = `${type}-id`; - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - await expectGeneralError(client.delete, { type, id }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const options = { namespace }; - await expectForbiddenError(client.delete, { type, id, options }); - }); - - test(`returns result of internalRepository.delete when authorized`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.delete.mockReturnValue(apiCallReturnValue as any); - - const options = { namespace }; - const result = await expectSuccess(client.delete, { type, id, options }); - expect(result).toBe(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const options = { namespace }; - await expectPrivilegeCheck(client.delete, { type, id, options }, namespace); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.delete.mockReturnValue(apiCallReturnValue as any); - const options = { namespace }; - await expectSuccess(client.delete, { type, id, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_delete', 'unknown', { type, id }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.delete(type, id)).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_delete', 'failure', { type, id }); - }); -}); - -describe('#find', () => { - const type1 = 'foo'; - const type2 = 'bar'; - const namespaces = ['some-ns']; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - await expectGeneralError(client.find, { type: type1 }); - }); - - test(`returns empty result when unauthorized`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure - ); - - const options = Object.freeze({ type: type1, namespaces: ['some-ns'] }); - const result = await client.find(options); - - expect(clientOpts.baseClient.find).not.toHaveBeenCalled(); - expect(result).toEqual({ page: 1, per_page: 20, total: 0, saved_objects: [] }); - }); - - test(`returns result of baseClient.find when fully authorized`, async () => { - const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; - clientOpts.baseClient.find.mockReturnValue(apiCallReturnValue as any); - - const options = { type: type1, namespaces }; - const result = await expectSuccess(client.find, { options }); - expect(clientOpts.baseClient.find.mock.calls[0][0]).toEqual({ - ...options, - typeToNamespacesMap: undefined, - }); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`returns result of baseClient.find when partially authorized`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockResolvedValue({ - hasAllRequested: false, - username: USERNAME, - privileges: { - kibana: [ - { resource: 'some-ns', privilege: 'mock-saved_object:foo/find', authorized: true }, - { resource: 'some-ns', privilege: 'mock-saved_object:bar/find', authorized: true }, - { resource: 'some-ns', privilege: 'mock-saved_object:baz/find', authorized: false }, - { resource: 'some-ns', privilege: 'mock-saved_object:qux/find', authorized: false }, - { resource: 'another-ns', privilege: 'mock-saved_object:foo/find', authorized: true }, - { resource: 'another-ns', privilege: 'mock-saved_object:bar/find', authorized: false }, - { resource: 'another-ns', privilege: 'mock-saved_object:baz/find', authorized: true }, - { resource: 'another-ns', privilege: 'mock-saved_object:qux/find', authorized: false }, - { resource: 'forbidden-ns', privilege: 'mock-saved_object:foo/find', authorized: false }, - { resource: 'forbidden-ns', privilege: 'mock-saved_object:bar/find', authorized: false }, - { resource: 'forbidden-ns', privilege: 'mock-saved_object:baz/find', authorized: false }, - { resource: 'forbidden-ns', privilege: 'mock-saved_object:qux/find', authorized: false }, - ], - }, - }); - - const apiCallReturnValue = { saved_objects: [], foo: 'bar' }; - clientOpts.baseClient.find.mockReturnValue(apiCallReturnValue as any); - - const options = Object.freeze({ - type: ['foo', 'bar', 'baz', 'qux'], - namespaces: ['some-ns', 'another-ns', 'forbidden-ns'], - }); - const result = await client.find(options); - // 'expect(clientOpts.baseClient.find).toHaveBeenCalledWith' resulted in false negatives, resorting to manually comparing mock call args - expect(clientOpts.baseClient.find.mock.calls[0][0]).toEqual({ - ...options, - typeToNamespacesMap: new Map([ - ['foo', ['some-ns', 'another-ns']], - ['bar', ['some-ns']], - ['baz', ['another-ns']], - // qux is not authorized, so there is no entry for it - // forbidden-ns is completely forbidden, so there are no entries with this namespace - ]), - type: '', - namespaces: [], - }); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`throws BadRequestError when searching across namespaces when spaces is disabled`, async () => { - clientOpts = createSecureSavedObjectsClientWrapperOptions(); - clientOpts.getSpacesService.mockReturnValue(undefined); - client = new SecureSavedObjectsClientWrapper(clientOpts); - - // succeed privilege checks by default - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesSuccess - ); - - const options = { type: [type1, type2], namespaces }; - await expect(client.find(options)).rejects.toThrowErrorMatchingInlineSnapshot( - `"_find across namespaces is not permitted when the Spaces plugin is disabled."` - ); - }); - - test(`checks privileges for user, actions, and namespaces`, async () => { - const options = { type: [type1, type2], namespaces }; - await expectPrivilegeCheck(client.find, { options }, namespaces); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const options = { type: [type1, type2], namespaces }; - await expectObjectsNamespaceFiltering(client.find, { options }); - }); - - test(`adds audit event when successful`, async () => { - const obj1 = { type: 'foo', id: 'sup' }; - const obj2 = { type: 'bar', id: 'everyone' }; - const apiCallReturnValue = { saved_objects: [obj1, obj2], foo: 'bar' }; - clientOpts.baseClient.find.mockReturnValue(apiCallReturnValue as any); - const options = Object.freeze({ type: type1, namespaces: ['some-ns'] }); - await expectSuccess(client.find, { options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(2); - expectAuditEvent('saved_object_find', 'success', obj1); - expectAuditEvent('saved_object_find', 'success', obj2); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure - ); - await client.find({ type: type1 }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_find', 'failure'); - }); -}); - -describe('#get', () => { - const type = 'foo'; - const id = `${type}-id`; - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - await expectGeneralError(client.get, { type, id }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const options = { namespace }; - await expectForbiddenError(client.get, { type, id, options }); - }); - - test(`returns result of baseClient.get when authorized`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.get.mockReturnValue(apiCallReturnValue as any); - - const options = { namespace }; - const result = await expectSuccess(client.get, { type, id, options }); - expect(result).toBe(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const options = { namespace }; - await expectPrivilegeCheck(client.get, { type, id, options }, namespace); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const options = { namespace }; - await expectObjectNamespaceFiltering(client.get, { type, id, options }); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.get.mockReturnValue(apiCallReturnValue as any); - const options = { namespace }; - await expectSuccess(client.get, { type, id, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_get', 'success', { type, id }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.get(type, id, { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_get', 'failure', { type, id }); - }); -}); - -describe('#openPointInTimeForType', () => { - const type = 'foo'; - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - await expectGeneralError(client.openPointInTimeForType, { type }); - }); - - test(`returns result of baseClient.openPointInTimeForType when authorized`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.openPointInTimeForType.mockReturnValue(apiCallReturnValue as any); - - const options = { namespaces: [namespace] }; - const result = await expectSuccess(client.openPointInTimeForType, { type, options }); - expect(result).toBe(apiCallReturnValue); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.openPointInTimeForType.mockReturnValue(apiCallReturnValue as any); - const options = { namespaces: [namespace] }; - await expectSuccess(client.openPointInTimeForType, { type, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_open_point_in_time', 'unknown'); - }); - - test(`throws an error when unauthorized`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure - ); - const options = { namespaces: [namespace] }; - await expect(() => client.openPointInTimeForType(type, options)).rejects.toThrowError( - 'unauthorized' - ); - }); - - test(`adds audit event when unauthorized`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure - ); - const options = { namespaces: [namespace] }; - await expect(() => client.openPointInTimeForType(type, options)).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_open_point_in_time', 'failure'); - }); - - test(`filters types based on authorization`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockResolvedValue({ - hasAllRequested: false, - username: USERNAME, - privileges: { - kibana: [ - { - resource: 'some-ns', - privilege: 'mock-saved_object:foo/open_point_in_time', - authorized: true, - }, - { - resource: 'some-ns', - privilege: 'mock-saved_object:bar/open_point_in_time', - authorized: true, - }, - { - resource: 'some-ns', - privilege: 'mock-saved_object:baz/open_point_in_time', - authorized: false, - }, - { - resource: 'some-ns', - privilege: 'mock-saved_object:qux/open_point_in_time', - authorized: false, - }, - { - resource: 'another-ns', - privilege: 'mock-saved_object:foo/open_point_in_time', - authorized: true, - }, - { - resource: 'another-ns', - privilege: 'mock-saved_object:bar/open_point_in_time', - authorized: false, - }, - { - resource: 'another-ns', - privilege: 'mock-saved_object:baz/open_point_in_time', - authorized: true, - }, - { - resource: 'another-ns', - privilege: 'mock-saved_object:qux/open_point_in_time', - authorized: false, - }, - { - resource: 'forbidden-ns', - privilege: 'mock-saved_object:foo/open_point_in_time', - authorized: false, - }, - { - resource: 'forbidden-ns', - privilege: 'mock-saved_object:bar/open_point_in_time', - authorized: false, - }, - { - resource: 'forbidden-ns', - privilege: 'mock-saved_object:baz/open_point_in_time', - authorized: false, - }, - { - resource: 'forbidden-ns', - privilege: 'mock-saved_object:qux/open_point_in_time', - authorized: false, - }, - ], - }, - }); - - await client.openPointInTimeForType(['foo', 'bar', 'baz', 'qux'], { - namespaces: ['some-ns', 'another-ns', 'forbidden-ns'], - }); - - expect(clientOpts.baseClient.openPointInTimeForType).toHaveBeenCalledWith( - ['foo', 'bar', 'baz'], - { - namespaces: ['some-ns', 'another-ns', 'forbidden-ns'], - } - ); - }); -}); - -describe('#closePointInTime', () => { - const id = 'abc123'; - const namespace = 'some-ns'; - - test(`returns result of baseClient.closePointInTime`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.closePointInTime.mockReturnValue(apiCallReturnValue as any); - - const options = { namespace }; - const result = await client.closePointInTime(id, options); - expect(result).toBe(apiCallReturnValue); - }); - - test(`adds audit event`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.closePointInTime.mockReturnValue(apiCallReturnValue as any); - - const options = { namespace }; - await client.closePointInTime(id, options); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_close_point_in_time', 'unknown'); - }); -}); - -describe('#createPointInTimeFinder', () => { - it('redirects request to underlying base client with default dependencies', () => { - const options = { type: ['a', 'b'], search: 'query' }; - client.createPointInTimeFinder(options); - - expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, { - client, - }); - }); - - it('redirects request to underlying base client with custom dependencies', () => { - const options = { type: ['a', 'b'], search: 'query' }; - const dependencies = { - client: { - find: jest.fn(), - openPointInTimeForType: jest.fn(), - closePointInTime: jest.fn(), - }, - }; - client.createPointInTimeFinder(options, dependencies); - - expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(clientOpts.baseClient.createPointInTimeFinder).toHaveBeenCalledWith( - options, - dependencies - ); - }); -}); - -describe('#resolve', () => { - const type = 'foo'; - const id = `${type}-id`; - const namespace = 'some-ns'; - const resolvedId = 'another-id'; // success audit records include the resolved ID, not the requested ID - const mockResult = { saved_object: { id: resolvedId } }; // mock result needs to have ID for audit logging - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - await expectGeneralError(client.resolve, { type, id }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const options = { namespace }; - await expectForbiddenError(client.resolve, { type, id, options }, 'resolve'); - }); - - test(`returns result of baseClient.resolve when authorized`, async () => { - const apiCallReturnValue = mockResult; - clientOpts.baseClient.resolve.mockReturnValue(apiCallReturnValue as any); - - const options = { namespace }; - const result = await expectSuccess(client.resolve, { type, id, options }, 'resolve'); - expect(result).toEqual(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const options = { namespace }; - await expectPrivilegeCheck(client.resolve, { type, id, options }, namespace); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const options = { namespace }; - - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( - getMockCheckPrivilegesSuccess // privilege check for authorization - ); - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - getMockCheckPrivilegesFailure // privilege check for namespace filtering - ); - - const namespaces = ['some-other-namespace', '*', namespace]; - const returnValue = { saved_object: { namespaces, id: resolvedId, foo: 'bar' } }; - clientOpts.baseClient.resolve.mockReturnValue(returnValue as any); - - const result = await client.resolve(type, id, options); - // we will never redact the "All Spaces" ID - expect(result).toEqual({ - saved_object: expect.objectContaining({ namespaces: ['*', namespace, '?'] }), - }); - - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith( - 'login:', - ['some-other-namespace'] - // when we check what namespaces to redact, we don't check privileges for '*', only actual space IDs - // we don't check privileges for authorizedNamespace either, as that was already checked earlier in the operation - ); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = mockResult; - clientOpts.baseClient.resolve.mockReturnValue(apiCallReturnValue as any); - const options = { namespace }; - await expectSuccess(client.resolve, { type, id, options }, 'resolve'); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_resolve', 'success', { type, id: resolvedId }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.resolve(type, id, { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_resolve', 'failure', { type, id }); - }); -}); - -describe('#update', () => { - const type = 'foo'; - const id = `${type}-id`; - const attributes = { some: 'attr' }; - const namespace = 'some-ns'; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - await expectGeneralError(client.update, { type, id, attributes }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - const options = { namespace }; - await expectForbiddenError(client.update, { type, id, attributes, options }); - }); - - test(`returns result of baseClient.update when authorized`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.update.mockReturnValue(apiCallReturnValue as any); - - const options = { namespace }; - const result = await expectSuccess(client.update, { type, id, attributes, options }); - expect(result).toBe(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - const options = { namespace }; - await expectPrivilegeCheck(client.update, { type, id, attributes, options }, namespace); - }); - - test(`filters namespaces that the user doesn't have access to`, async () => { - const options = { namespace }; - await expectObjectNamespaceFiltering(client.update, { type, id, attributes, options }); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.update.mockReturnValue(apiCallReturnValue as any); - const options = { namespace }; - await expectSuccess(client.update, { type, id, attributes, options }); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_update', 'unknown', { type, id }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.update(type, id, attributes, { namespace })).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_update', 'failure', { type, id }); - }); -}); - -describe('#removeReferencesTo', () => { - const type = 'foo'; - const id = `${type}-id`; - const namespace = 'some-ns'; - const options = { namespace }; - - test(`throws decorated GeneralError when hasPrivileges rejects promise`, async () => { - await expectGeneralError(client.removeReferencesTo, { type, id, options }); - }); - - test(`throws decorated ForbiddenError when unauthorized`, async () => { - await expectForbiddenError( - client.removeReferencesTo, - { type, id, options }, - 'removeReferences' - ); - }); - - test(`returns result of baseClient.removeReferencesTo when authorized`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.removeReferencesTo.mockReturnValue(apiCallReturnValue as any); - - const result = await expectSuccess( - client.removeReferencesTo, - { type, id, options }, - 'removeReferences' - ); - expect(result).toBe(apiCallReturnValue); - }); - - test(`checks privileges for user, actions, and namespace`, async () => { - await expectPrivilegeCheck(client.removeReferencesTo, { type, id, options }, namespace); - }); - - test(`adds audit event when successful`, async () => { - const apiCallReturnValue = Symbol(); - clientOpts.baseClient.removeReferencesTo.mockReturnValue(apiCallReturnValue as any); - await client.removeReferencesTo(type, id); - - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_remove_references', 'unknown', { type, id }); - }); - - test(`adds audit event when not successful`, async () => { - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockRejectedValue(new Error()); - await expect(() => client.removeReferencesTo(type, id)).rejects.toThrow(); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(1); - expectAuditEvent('saved_object_remove_references', 'failure', { type, id }); - }); -}); - -/** - * Naming conventions used in this group of tests: - * * 'reqObj' is an object that the consumer requests (SavedObjectsCollectMultiNamespaceReferencesObject) - * * 'obj' is the object result that was fetched from Elasticsearch (SavedObjectReferenceWithContext) - */ -describe('#collectMultiNamespaceReferences', () => { - const AUDIT_ACTION = 'saved_object_collect_multinamespace_references'; - const spaceX = 'space-x'; - const spaceY = 'space-y'; - const spaceZ = 'space-z'; - - /** Returns a valid inboundReferences field for mock baseClient results. */ - function getInboundRefsFrom( - ...objects: Array<{ type: string; id: string }> - ): Pick { - return { - inboundReferences: objects.map(({ type, id }) => { - return { type, id, name: `ref-${type}:${id}` }; - }), - }; - } - - beforeEach(() => { - // by default, the result is a success, each object exists in the current space and another space - clientOpts.baseClient.collectMultiNamespaceReferences.mockImplementation((objects) => - Promise.resolve({ - objects: objects.map(({ type, id }) => ({ - type, - id, - spaces: [spaceX, spaceY, spaceZ], - inboundReferences: [], - })), - }) - ); - }); - - describe('errors', () => { - const reqObj1 = { type: 'a', id: '1' }; - const reqObj2 = { type: 'b', id: '2' }; - const reqObj3 = { type: 'c', id: '3' }; - - test(`throws an error if the base client operation fails`, async () => { - clientOpts.baseClient.collectMultiNamespaceReferences.mockRejectedValue(new Error('Oh no!')); - await expect(() => - client.collectMultiNamespaceReferences([reqObj1], { namespace: spaceX }) - ).rejects.toThrowError('Oh no!'); - expect(clientOpts.baseClient.collectMultiNamespaceReferences).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).not.toHaveBeenCalled(); - expect(clientOpts.auditLogger.log).not.toHaveBeenCalled(); - }); - - describe(`throws decorated ForbiddenError and adds audit events when unauthorized`, () => { - test(`with purpose 'collectMultiNamespaceReferences'`, async () => { - // Use the default mocked results for the base client call. - // This fails because the user is not authorized to bulk_get type 'c' in the current space. - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('a', { bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] } }) - .set('b', { bulk_get: { authorizedSpaces: [spaceX, spaceY] } }) - .set('c', { bulk_get: { authorizedSpaces: [spaceY] } }), - }); - const options = { namespace: spaceX }; // spaceX is the current space - await expect(() => - client.collectMultiNamespaceReferences([reqObj1, reqObj2, reqObj3], options) - ).rejects.toThrowError(clientOpts.forbiddenError); - expect(clientOpts.baseClient.collectMultiNamespaceReferences).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(3); - expectAuditEvent(AUDIT_ACTION, 'failure', reqObj1); - expectAuditEvent(AUDIT_ACTION, 'failure', reqObj2); - expectAuditEvent(AUDIT_ACTION, 'failure', reqObj3); - }); - - test(`with purpose 'updateObjectsSpaces'`, async () => { - // Use the default mocked results for the base client call. - // This fails because the user is not authorized to share_to_space type 'c' in the current space. - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('a', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - share_to_space: { isGloballyAuthorized: true, authorizedSpaces: [] }, - }) - .set('b', { - bulk_get: { authorizedSpaces: [spaceX, spaceY] }, - share_to_space: { authorizedSpaces: [spaceX, spaceY] }, - }) - .set('c', { - bulk_get: { authorizedSpaces: [spaceX, spaceY] }, - share_to_space: { authorizedSpaces: [spaceY] }, - }), - }); - const options = { namespace: spaceX, purpose: 'updateObjectsSpaces' as const }; // spaceX is the current space - await expect(() => - client.collectMultiNamespaceReferences([reqObj1, reqObj2, reqObj3], options) - ).rejects.toThrowError(clientOpts.forbiddenError); - expect(clientOpts.baseClient.collectMultiNamespaceReferences).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(3); - expectAuditEvent(AUDIT_ACTION, 'failure', reqObj1); - expectAuditEvent(AUDIT_ACTION, 'failure', reqObj2); - expectAuditEvent(AUDIT_ACTION, 'failure', reqObj3); - }); - }); - - test(`throws an error if the base client result includes a requested object without a valid inbound reference`, async () => { - // We *shouldn't* ever get an inbound reference that is not also present in the base client response objects array. - const spaces = [spaceX]; - - const obj1 = { ...reqObj1, spaces, inboundReferences: [] }; - const obj2 = { - type: 'a', - id: '2', - spaces, - ...getInboundRefsFrom({ type: 'some-type', id: 'some-id' }), - }; - clientOpts.baseClient.collectMultiNamespaceReferences.mockResolvedValueOnce({ - objects: [obj1, obj2], - }); - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map().set('a', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - }), - }); - // When the loop gets to obj2, it will determine that the user is authorized for the object but *not* for the graph. However, it will - // also determine that there is *no* valid inbound reference tying this object back to what was requested. In this case, throw an - // error. - - const options = { namespace: spaceX }; // spaceX is the current space - await expect(() => - client.collectMultiNamespaceReferences([reqObj1], options) - ).rejects.toThrowError('Unexpected inbound reference to "some-type:some-id"'); - }); - }); - - describe(`checks privileges`, () => { - // Other test cases below contain more complex assertions for privilege checks, but these focus on the current space (default vs non-default) - const reqObj1 = { type: 'a', id: '1' }; - const obj1 = { ...reqObj1, spaces: ['*'], inboundReferences: [] }; - - beforeEach(() => { - clientOpts.baseClient.collectMultiNamespaceReferences.mockResolvedValueOnce({ - objects: [obj1], - }); - mockEnsureAuthorized.mockResolvedValue({ - status: 'fully_authorized', - typeActionMap: new Map().set('a', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, // success case for the simplest test - }), - }); - }); - - test(`in the default space`, async () => { - await client.collectMultiNamespaceReferences([reqObj1]); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - ['a'], // unique types of the fetched objects - ['bulk_get'], // actions - ['default'], // unique spaces that the fetched objects exist in, along with the current space - { requireFullAuthorization: false } - ); - }); - - test(`in a non-default space`, async () => { - await client.collectMultiNamespaceReferences([reqObj1], { namespace: spaceX }); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - ['a'], // unique types of the fetched objects - ['bulk_get'], // actions - [spaceX], // unique spaces that the fetched objects exist in, along with the current space - { requireFullAuthorization: false } - ); - }); - }); - - describe(`checks privileges, filters/redacts objects correctly, and records audit events`, () => { - const reqObj1 = { type: 'a', id: '1' }; - const reqObj2 = { type: 'b', id: '2' }; - const spaces = [spaceX, spaceY, spaceZ]; - const spacesWithMatchingAliases = [spaceX, spaceY, spaceZ]; - const spacesWithMatchingOrigins = [spaceX, spaceY, spaceZ]; - - // Actual object graph: - // ─► obj1 (a:1) ─┬─► obj3 (c:3) ───► obj5 (c:5) ─► obj8 (c:8) ─┐ - // │ ▲ │ - // │ │ │ - // └─► obj4 (d:4) ─┬─► obj6 (c:6) ◄──────────────┘ - // ─► obj2 (b:2) └─► obj7 (c:7) - // - // Object graph that the consumer sees after authorization: - // ─► obj1 (a:1) ─┬─► obj3 (c:3) ───► obj5 (c:5) ─► obj8 (c:8) ─► obj6 (c:6) ─┐ - // │ ▲ │ - // │ └───────────────────────────────────┘ - // └─► obj4 (d:4) - // ─► obj2 (b:2) - const obj1 = { - ...reqObj1, - spaces, - inboundReferences: [], - // We include spacesWithMatchingAliases and spacesWithMatchingOrigins on this object of type 'a' (which the user is authorized to access globally) to assert that they are not redacted - spacesWithMatchingAliases, - spacesWithMatchingOrigins, - }; - const obj2 = { ...reqObj2, spaces: [], inboundReferences: [] }; // non-multi-namespace types and hidden types will be returned with an empty spaces array - const obj3 = { - type: 'c', - id: '3', - spaces, - ...getInboundRefsFrom(obj1), - // We include spacesWithMatchingAliases and spacesWithMatchingOrigins on this object of type 'c' (which the user is partially authorized for) to assert that they are redacted - spacesWithMatchingAliases, - spacesWithMatchingOrigins, - }; - const obj4 = { type: 'd', id: '4', spaces, ...getInboundRefsFrom(obj1) }; - const obj5 = { - type: 'c', - id: '5', - spaces: ['*'], - ...getInboundRefsFrom(obj3, { type: 'c', id: '6' }), - }; - const obj6 = { - type: 'c', - id: '6', - spaces, - ...getInboundRefsFrom(obj4, { type: 'c', id: '8' }), - }; - const obj7 = { type: 'c', id: '7', spaces, ...getInboundRefsFrom(obj4) }; - const obj8 = { type: 'c', id: '8', spaces, ...getInboundRefsFrom(obj5) }; - - beforeEach(() => { - clientOpts.baseClient.collectMultiNamespaceReferences.mockResolvedValueOnce({ - objects: [obj1, obj2, obj3, obj4, obj5, obj6, obj7, obj8], - }); - }); - - test(`with purpose 'collectMultiNamespaceReferences'`, async () => { - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('a', { bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] } }) - .set('b', { bulk_get: { authorizedSpaces: [spaceX] } }) - .set('c', { bulk_get: { authorizedSpaces: [spaceX] } }), - // the user is not authorized to read type 'd' - }); - - const options = { namespace: spaceX }; // spaceX is the current space - const result = await client.collectMultiNamespaceReferences([reqObj1, reqObj2], options); - expect(result).toEqual({ - objects: [ - obj1, // obj1's spaces, spacesWithMatchingAliases, and spacesWithMatchingOrigins arrays are not redacted because the user is globally authorized to access it - obj2, // obj2 has an empty spaces array (see above) - { - ...obj3, - spaces: [spaceX, '?', '?'], - spacesWithMatchingAliases: [spaceX, '?', '?'], - spacesWithMatchingOrigins: [spaceX, '?', '?'], - }, - { ...obj4, spaces: [], isMissing: true }, // obj4 is marked as Missing because the user was not authorized to access it - obj5, // obj5's spaces array is not redacted, because it exists in All Spaces - // obj7 is not included at all because the user was not authorized to access its inbound reference (obj4) - { ...obj8, spaces: [spaceX, '?', '?'] }, - { ...obj6, spaces: [spaceX, '?', '?'], ...getInboundRefsFrom(obj8) }, // obj6 is at the back of the list and its inboundReferences array is redacted because the user is not authorized to access one of its inbound references, obj4 - ], - }); - expect(clientOpts.baseClient.collectMultiNamespaceReferences).toHaveBeenCalledTimes(1); - expect(clientOpts.baseClient.collectMultiNamespaceReferences).toHaveBeenCalledWith( - [reqObj1, reqObj2], - options - ); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - ['a', 'b', 'c', 'd'], // unique types of the fetched objects - ['bulk_get'], // actions - [spaceX, spaceY, spaceZ], // unique spaces that the fetched objects exist in, along with the current space - { requireFullAuthorization: false } - ); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(5); - expectAuditEvent(AUDIT_ACTION, 'success', obj1); - expectAuditEvent(AUDIT_ACTION, 'success', obj3); - expectAuditEvent(AUDIT_ACTION, 'success', obj5); - expectAuditEvent(AUDIT_ACTION, 'success', obj8); - expectAuditEvent(AUDIT_ACTION, 'success', obj6); - // obj2, obj4, and obj7 are intentionally excluded from the audit record because we did not return any information about them to the user - }); - - test(`with purpose 'updateObjectsSpaces'`, async () => { - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('a', { - share_to_space: { authorizedSpaces: [spaceX] }, - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - // Even though the user can only share type 'a' in spaceX, we won't redact spaceY or spaceZ because the user has global read privileges - }) - .set('b', { - share_to_space: { authorizedSpaces: [spaceX] }, - bulk_get: { authorizedSpaces: [spaceX, spaceY] }, - }) - .set('c', { - share_to_space: { authorizedSpaces: [spaceX] }, - bulk_get: { authorizedSpaces: [spaceX, spaceY] }, - // Even though the user can only share type 'c' in spaceX, we won't redact spaceY because the user has read privileges there - }), - // the user is not authorized to read or share type 'd' - }); - - const options = { namespace: spaceX, purpose: 'updateObjectsSpaces' as const }; // spaceX is the current space - const result = await client.collectMultiNamespaceReferences([reqObj1, reqObj2], options); - expect(result).toEqual({ - objects: [ - obj1, // obj1's spaces, spacesWithMatchingAliases, and spacesWithMatchingOrigins arrays are not redacted because the user is globally authorized to access it - obj2, // obj2 has an empty spaces array (see above) - { - ...obj3, - spaces: [spaceX, spaceY, '?'], - spacesWithMatchingAliases: [spaceX, spaceY, '?'], - spacesWithMatchingOrigins: [spaceX, spaceY, '?'], - }, - { ...obj4, spaces: [], isMissing: true }, // obj4 is marked as Missing because the user was not authorized to access it - obj5, // obj5's spaces array is not redacted, because it exists in All Spaces - // obj7 is not included at all because the user was not authorized to access its inbound reference (obj4) - { ...obj8, spaces: [spaceX, spaceY, '?'] }, - { ...obj6, spaces: [spaceX, spaceY, '?'], ...getInboundRefsFrom(obj8) }, // obj6 is at the back of the list and its inboundReferences array is redacted because the user is not authorized to access one of its inbound references, obj4 - ], - }); - expect(clientOpts.baseClient.collectMultiNamespaceReferences).toHaveBeenCalledTimes(1); - expect(clientOpts.baseClient.collectMultiNamespaceReferences).toHaveBeenCalledWith( - [reqObj1, reqObj2], - options - ); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - ['a', 'b', 'c', 'd'], // unique types of the fetched objects - ['bulk_get', 'share_to_space'], // actions - [spaceX, spaceY, spaceZ], // unique spaces that the fetched objects exist in, along with the current space - { requireFullAuthorization: false } - ); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(5); - expectAuditEvent(AUDIT_ACTION, 'success', obj1); - expectAuditEvent(AUDIT_ACTION, 'success', obj3); - expectAuditEvent(AUDIT_ACTION, 'success', obj5); - expectAuditEvent(AUDIT_ACTION, 'success', obj8); - expectAuditEvent(AUDIT_ACTION, 'success', obj6); - // obj2, obj4, and obj7 are intentionally excluded from the audit record because we did not return any information about them to the user - }); - }); -}); - -describe('#updateObjectsSpaces', () => { - const AUDIT_ACTION = 'saved_object_update_objects_spaces'; - const spaceA = 'space-a'; - const spaceB = 'space-b'; - const spaceC = 'space-c'; - const spaceD = 'space-d'; - const obj1 = { type: 'x', id: '1' }; - const obj2 = { type: 'y', id: '2' }; - const obj3 = { type: 'z', id: '3' }; - const obj4 = { type: 'z', id: '4' }; - const obj5 = { type: 'z', id: '5' }; - - describe('errors', () => { - test(`throws an error if the base client bulkGet operation fails`, async () => { - clientOpts.baseClient.bulkGet.mockRejectedValue(new Error('Oh no!')); - await expect(() => - client.updateObjectsSpaces([obj1], [spaceA], [spaceB], { namespace: spaceC }) - ).rejects.toThrowError('Oh no!'); - expect(clientOpts.baseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).not.toHaveBeenCalled(); - expect(clientOpts.auditLogger.log).not.toHaveBeenCalled(); - }); - - test(`throws decorated ForbiddenError and adds audit events when unauthorized`, async () => { - clientOpts.baseClient.bulkGet.mockResolvedValue({ - saved_objects: [ - { ...obj1, namespaces: [spaceB, spaceC, spaceD] }, - { ...obj2, namespaces: [spaceB, spaceC, spaceD] }, - { ...obj3, namespaces: [spaceB, spaceC, spaceD] }, - ] as SavedObject[], - }); - // This fails because the user is not authorized to share_to_space type 'z' in the current space. - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('x', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - share_to_space: { isGloballyAuthorized: true, authorizedSpaces: [] }, - }) - .set('y', { - bulk_get: { authorizedSpaces: [spaceA, spaceB, spaceC, spaceD] }, - share_to_space: { authorizedSpaces: [spaceA, spaceB, spaceC, spaceD] }, - }) - .set('z', { - bulk_get: { authorizedSpaces: [spaceA, spaceB, spaceC] }, - share_to_space: { authorizedSpaces: [spaceA, spaceB] }, - }), - }); - - const objects = [obj1, obj2, obj3]; - const spacesToAdd = [spaceA]; - const spacesToRemove = [spaceB]; - const options = { namespace: spaceC }; // spaceC is the current space - await expect(() => - client.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options) - ).rejects.toThrowError(clientOpts.forbiddenError); - expect(clientOpts.baseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(3); - expectAuditEvent(AUDIT_ACTION, 'failure', obj1); - expectAuditEvent(AUDIT_ACTION, 'failure', obj2); - expectAuditEvent(AUDIT_ACTION, 'failure', obj3); - expect(clientOpts.baseClient.updateObjectsSpaces).not.toHaveBeenCalled(); - }); - - test(`throws an error if the base client updateObjectsSpaces operation fails`, async () => { - clientOpts.baseClient.bulkGet.mockResolvedValue({ - saved_objects: [ - { ...obj1, namespaces: [spaceB, spaceC, spaceD] }, - { ...obj2, namespaces: [spaceB, spaceC, spaceD] }, - { ...obj3, namespaces: [spaceB, spaceC, spaceD] }, - ] as SavedObject[], - }); - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('x', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - share_to_space: { isGloballyAuthorized: true, authorizedSpaces: [] }, - }) - .set('y', { - bulk_get: { authorizedSpaces: [spaceA, spaceB, spaceC, spaceD] }, - share_to_space: { authorizedSpaces: [spaceA, spaceB, spaceC] }, - }) - .set('z', { - bulk_get: { authorizedSpaces: [spaceA, spaceB, spaceC] }, - share_to_space: { authorizedSpaces: [spaceA, spaceB, spaceC] }, - }), - }); - clientOpts.baseClient.updateObjectsSpaces.mockRejectedValue(new Error('Oh no!')); - - const objects = [obj1, obj2, obj3]; - const spacesToAdd = [spaceA]; - const spacesToRemove = [spaceB]; - const options = { namespace: spaceC }; // spaceC is the current space - await expect(() => - client.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options) - ).rejects.toThrowError('Oh no!'); - expect(clientOpts.baseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(3); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj1); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj2); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj3); - expect(clientOpts.baseClient.updateObjectsSpaces).toHaveBeenCalledTimes(1); - }); - }); - - test(`checks privileges, filters/redacts objects correctly, and records audit events`, async () => { - const bulkGetResults = [ - { ...obj1, namespaces: [spaceB, spaceC, spaceD], version: 'v1' }, - { ...obj2, namespaces: [spaceB, spaceC, spaceD], version: 'v2' }, - { ...obj3, namespaces: [spaceB, spaceC, spaceD], version: 'v3' }, - { ...obj4, namespaces: ['*'], version: 'v4' }, // obj4 exists in all spaces - { ...obj5, namespaces: [spaceB, spaceC, spaceD], version: 'v5' }, - ] as SavedObject[]; - clientOpts.baseClient.bulkGet.mockResolvedValue({ saved_objects: bulkGetResults }); - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('x', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - share_to_space: { isGloballyAuthorized: true, authorizedSpaces: [] }, - }) - .set('y', { - bulk_get: { authorizedSpaces: [spaceA, spaceB, spaceC, spaceD] }, - share_to_space: { authorizedSpaces: [spaceA, spaceB, spaceC] }, - }) - .set('z', { - bulk_get: { authorizedSpaces: [spaceA, spaceB, spaceC] }, // the user is not authorized to bulkGet type 'z' in spaceD, so it will be redacted from the results - share_to_space: { authorizedSpaces: [spaceA, spaceB, spaceC] }, - }), - }); - clientOpts.baseClient.updateObjectsSpaces.mockResolvedValue({ - objects: [ - // Each object was added to spaceA and removed from spaceB - { ...obj1, spaces: [spaceA, spaceC, spaceD] }, - { ...obj2, spaces: [spaceA, spaceC, spaceD] }, - { ...obj3, spaces: [spaceA, spaceC, spaceD] }, - { ...obj4, spaces: ['*', spaceA] }, // even though this object exists in all spaces, we won't pass '*' to ensureAuthorized - { ...obj5, spaces: [], error: new Error('Oh no!') }, // we encountered an error when attempting to update obj5 - ] as SavedObjectsUpdateObjectsSpacesResponseObject[], - }); - - const objects = [obj1, obj2, obj3, obj4, obj5]; - const spacesToAdd = [spaceA]; - const spacesToRemove = [spaceB]; - const options = { namespace: spaceC }; // spaceC is the current space - const result = await client.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options); - expect(result).toEqual({ - objects: [ - { ...obj1, spaces: [spaceA, spaceC, spaceD] }, // obj1's spaces array is not redacted because the user is globally authorized to access it - { ...obj2, spaces: [spaceA, spaceC, spaceD] }, // obj2's spaces array is not redacted because the user is authorized to access it in each space - { ...obj3, spaces: [spaceA, spaceC, '?'] }, // obj3's spaces array is redacted because the user is not authorized to access it in spaceD - { ...obj4, spaces: ['*', spaceA] }, - { ...obj5, spaces: [], error: new Error('Oh no!') }, - ], - }); - - expect(clientOpts.baseClient.bulkGet).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - ['x', 'y', 'z'], // unique types of the fetched objects - ['bulk_get', 'share_to_space'], // actions - [spaceC, spaceA, spaceB, spaceD], // unique spaces of: the current space, spacesToAdd, spacesToRemove, and spaces that the fetched objects exist in (excludes '*') - { requireFullAuthorization: false } - ); - expect(clientOpts.auditLogger.log).toHaveBeenCalledTimes(5); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj1); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj2); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj3); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj4); - expectAuditEvent(AUDIT_ACTION, 'unknown', obj5); - expect(clientOpts.baseClient.updateObjectsSpaces).toHaveBeenCalledTimes(1); - expect(clientOpts.baseClient.updateObjectsSpaces).toHaveBeenCalledWith( - bulkGetResults.map(({ namespaces: spaces, ...otherAttrs }) => ({ spaces, ...otherAttrs })), - spacesToAdd, - spacesToRemove, - options - ); - }); - - test(`checks privileges for the global resource when spacesToAdd includes '*'`, async () => { - const bulkGetResults = [{ ...obj1, namespaces: [spaceA], version: 'v1' }] as SavedObject[]; - clientOpts.baseClient.bulkGet.mockResolvedValue({ saved_objects: bulkGetResults }); - mockEnsureAuthorized.mockResolvedValue({ - status: 'fully_authorized', - typeActionMap: new Map().set('x', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - share_to_space: { isGloballyAuthorized: true, authorizedSpaces: [] }, - }), - }); - clientOpts.baseClient.updateObjectsSpaces.mockResolvedValue({ - objects: [ - // The object was removed from spaceA and added to '*' - { ...obj1, spaces: ['*'] }, - ] as SavedObjectsUpdateObjectsSpacesResponseObject[], - }); - - const objects = [obj1]; - const spacesToAdd = ['*']; - const spacesToRemove = [spaceA]; - const options = { namespace: spaceC }; // spaceC is the current space - await client.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options); - - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - ['x'], // unique types of the fetched objects - ['bulk_get', 'share_to_space'], // actions - [spaceC, '*', spaceA], // unique spaces of: the current space, spacesToAdd, spacesToRemove, and spaces that the fetched objects exist in (excludes '*') - { requireFullAuthorization: false } - ); - }); - - test(`checks privileges for the global resource when spacesToRemove includes '*'`, async () => { - const bulkGetResults = [{ ...obj1, namespaces: ['*'], version: 'v1' }] as SavedObject[]; - clientOpts.baseClient.bulkGet.mockResolvedValue({ saved_objects: bulkGetResults }); - mockEnsureAuthorized.mockResolvedValue({ - status: 'fully_authorized', - typeActionMap: new Map().set('x', { - bulk_get: { isGloballyAuthorized: true, authorizedSpaces: [] }, - share_to_space: { isGloballyAuthorized: true, authorizedSpaces: [] }, - }), - }); - clientOpts.baseClient.updateObjectsSpaces.mockResolvedValue({ - objects: [ - // The object was removed from spaceA and added to '*' - { ...obj1, spaces: ['*'] }, - ] as SavedObjectsUpdateObjectsSpacesResponseObject[], - }); - - const objects = [obj1]; - const spacesToAdd = [spaceA]; - const spacesToRemove = ['*']; - const options = { namespace: spaceC }; // spaceC is the current space - await client.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, options); - - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - ['x'], // unique types of the fetched objects - ['bulk_get', 'share_to_space'], // actions - [spaceC, spaceA, '*'], // unique spaces of: the current space, spacesToAdd, spacesToRemove, and spaces that the fetched objects exist in (excludes '*') - { requireFullAuthorization: false } - ); - }); -}); - -describe('other', () => { - test(`assigns errors from constructor to .errors`, () => { - expect(client.errors).toBe(clientOpts.errors); - }); - - test(`namespace redaction fails safe`, async () => { - const type = 'foo'; - const id = `${type}-id`; - const namespace = 'some-ns'; - const namespaces = ['some-other-namespace', '*', namespace]; - const returnValue = { namespaces, foo: 'bar' }; - clientOpts.baseClient.get.mockReturnValue(returnValue as any); - - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementationOnce( - getMockCheckPrivilegesSuccess // privilege check for authorization - ); - clientOpts.checkSavedObjectsPrivilegesAsCurrentUser.mockImplementation( - // privilege check for namespace filtering - (_actions: string | string[], _namespaces?: string | string[]) => ({ - hasAllRequested: false, - username: USERNAME, - privileges: { - kibana: [ - // this is a contrived scenario as we *shouldn't* get both an unauthorized and authorized result for a given resource... - // however, in case we do, we should fail-safe (authorized + unauthorized = unauthorized) - { resource: 'some-other-namespace', privilege: 'login:', authorized: false }, - { resource: 'some-other-namespace', privilege: 'login:', authorized: true }, - ], - }, - }) - ); - - const result = await client.get(type, id, { namespace }); - // we will never redact the "All Spaces" ID - expect(result).toEqual(expect.objectContaining({ namespaces: ['*', namespace, '?'] })); - - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenCalledTimes(2); - expect(clientOpts.checkSavedObjectsPrivilegesAsCurrentUser).toHaveBeenLastCalledWith('login:', [ - 'some-other-namespace', - ]); - }); -}); diff --git a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts b/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts deleted file mode 100644 index b8e253b7f31601..00000000000000 --- a/x-pack/plugins/security/server/saved_objects/secure_saved_objects_client_wrapper.ts +++ /dev/null @@ -1,1201 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - SavedObjectReferenceWithContext, - SavedObjectsBaseOptions, - SavedObjectsBulkCreateObject, - SavedObjectsBulkDeleteObject, - SavedObjectsBulkDeleteOptions, - SavedObjectsBulkDeleteResponse, - SavedObjectsBulkGetObject, - SavedObjectsBulkResolveObject, - SavedObjectsBulkUpdateObject, - SavedObjectsCheckConflictsObject, - SavedObjectsClientContract, - SavedObjectsClosePointInTimeOptions, - SavedObjectsCollectMultiNamespaceReferencesObject, - SavedObjectsCollectMultiNamespaceReferencesOptions, - SavedObjectsCollectMultiNamespaceReferencesResponse, - SavedObjectsCreateOptions, - SavedObjectsCreatePointInTimeFinderDependencies, - SavedObjectsCreatePointInTimeFinderOptions, - SavedObjectsFindOptions, - SavedObjectsOpenPointInTimeOptions, - SavedObjectsRemoveReferencesToOptions, - SavedObjectsUpdateObjectsSpacesObject, - SavedObjectsUpdateObjectsSpacesOptions, - SavedObjectsUpdateOptions, -} from '@kbn/core/server'; -import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '@kbn/core/server'; - -import { ALL_SPACES_ID, UNKNOWN_SPACE } from '../../common/constants'; -import type { AuditLogger } from '../audit'; -import { SavedObjectAction, savedObjectEvent } from '../audit'; -import type { Actions, CheckSavedObjectsPrivileges } from '../authorization'; -import type { CheckPrivilegesResponse } from '../authorization/types'; -import type { SpacesService } from '../plugin'; -import type { - EnsureAuthorizedDependencies, - EnsureAuthorizedOptions, - EnsureAuthorizedResult, -} from './ensure_authorized'; -import { - ensureAuthorized, - getEnsureAuthorizedActionResult, - isAuthorizedForObjectInAllSpaces, -} from './ensure_authorized'; - -interface SecureSavedObjectsClientWrapperOptions { - actions: Actions; - auditLogger: AuditLogger; - baseClient: SavedObjectsClientContract; - errors: typeof SavedObjectsErrorHelpers; - checkSavedObjectsPrivilegesAsCurrentUser: CheckSavedObjectsPrivileges; - getSpacesService(): SpacesService | undefined; -} - -interface SavedObjectNamespaces { - namespaces?: string[]; -} - -interface SavedObjectsNamespaces { - saved_objects: SavedObjectNamespaces[]; -} - -interface LegacyEnsureAuthorizedOptions { - args?: Record; - auditAction?: string; - requireFullAuthorization?: boolean; -} - -interface LegacyEnsureAuthorizedResult { - status: 'fully_authorized' | 'partially_authorized' | 'unauthorized'; - typeMap: Map; -} - -interface LegacyEnsureAuthorizedTypeResult { - authorizedSpaces: string[]; - isGloballyAuthorized?: boolean; -} - -export class SecureSavedObjectsClientWrapper implements SavedObjectsClientContract { - private readonly actions: Actions; - private readonly auditLogger: AuditLogger; - private readonly baseClient: SavedObjectsClientContract; - private readonly checkSavedObjectsPrivilegesAsCurrentUser: CheckSavedObjectsPrivileges; - private getSpacesService: () => SpacesService | undefined; - public readonly errors: typeof SavedObjectsErrorHelpers; - - constructor({ - actions, - auditLogger, - baseClient, - checkSavedObjectsPrivilegesAsCurrentUser, - errors, - getSpacesService, - }: SecureSavedObjectsClientWrapperOptions) { - this.errors = errors; - this.actions = actions; - this.auditLogger = auditLogger; - this.baseClient = baseClient; - this.checkSavedObjectsPrivilegesAsCurrentUser = checkSavedObjectsPrivilegesAsCurrentUser; - this.getSpacesService = getSpacesService; - } - - public async create( - type: string, - attributes: T = {} as T, - options: SavedObjectsCreateOptions = {} - ) { - const optionsWithId = { ...options, id: options.id ?? SavedObjectsUtils.generateId() }; - const namespaces = [optionsWithId.namespace, ...(optionsWithId.initialNamespaces || [])]; - try { - const args = { type, attributes, options: optionsWithId }; - await this.legacyEnsureAuthorized(type, 'create', namespaces, { args }); - } catch (error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.CREATE, - savedObject: { type, id: optionsWithId.id }, - error, - }) - ); - throw error; - } - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.CREATE, - outcome: 'unknown', - savedObject: { type, id: optionsWithId.id }, - }) - ); - - const savedObject = await this.baseClient.create(type, attributes, optionsWithId); - return await this.redactSavedObjectNamespaces(savedObject, namespaces); - } - - public async checkConflicts( - objects: SavedObjectsCheckConflictsObject[] = [], - options: SavedObjectsBaseOptions = {} - ) { - const args = { objects, options }; - const types = this.getUniqueObjectTypes(objects); - await this.legacyEnsureAuthorized(types, 'bulk_create', options.namespace, { - args, - auditAction: 'checkConflicts', - }); - - const response = await this.baseClient.checkConflicts(objects, options); - return response; - } - - public async bulkCreate( - objects: Array>, - options: SavedObjectsBaseOptions = {} - ) { - const objectsWithId = objects.map((obj) => ({ - ...obj, - id: obj.id ?? SavedObjectsUtils.generateId(), - })); - const namespaces = objectsWithId.reduce( - (acc, { initialNamespaces = [] }) => acc.concat(initialNamespaces), - [options.namespace] - ); - try { - const args = { objects: objectsWithId, options }; - await this.legacyEnsureAuthorized( - this.getUniqueObjectTypes(objectsWithId), - 'bulk_create', - namespaces, - { - args, - } - ); - } catch (error) { - objectsWithId.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.CREATE, - savedObject: { type, id }, - error, - }) - ) - ); - throw error; - } - objectsWithId.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.CREATE, - outcome: 'unknown', - savedObject: { type, id }, - }) - ) - ); - - const response = await this.baseClient.bulkCreate(objectsWithId, options); - return await this.redactSavedObjectsNamespaces(response, namespaces); - } - - public async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - try { - const args = { type, id, options }; - await this.legacyEnsureAuthorized(type, 'delete', options.namespace, { args }); - } catch (error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.DELETE, - savedObject: { type, id }, - error, - }) - ); - throw error; - } - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.DELETE, - outcome: 'unknown', - savedObject: { type, id }, - }) - ); - - return await this.baseClient.delete(type, id, options); - } - - public async bulkDelete( - objects: SavedObjectsBulkDeleteObject[], - options: SavedObjectsBulkDeleteOptions - ): Promise { - try { - const args = { objects, options }; - await this.legacyEnsureAuthorized( - this.getUniqueObjectTypes(objects), - 'bulk_delete', - options?.namespace, - { - args, - } - ); - } catch (error) { - objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.DELETE, - savedObject: { type, id }, - error, - }) - ) - ); - throw error; - } - const response = await this.baseClient.bulkDelete(objects, options); - response?.statuses.forEach(({ id, type, success, error }) => { - const auditEventOutcome = success === true ? 'success' : 'failure'; - const auditEventOutcomeError = error ? (error as unknown as Error) : undefined; - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.DELETE, - savedObject: { type, id }, - outcome: auditEventOutcome, - error: auditEventOutcomeError, - }) - ); - }); - return response; - } - - public async find(options: SavedObjectsFindOptions) { - if ( - this.getSpacesService() == null && - Array.isArray(options.namespaces) && - options.namespaces.length > 0 - ) { - throw this.errors.createBadRequestError( - `_find across namespaces is not permitted when the Spaces plugin is disabled.` - ); - } - - const args = { options }; - const { status, typeMap } = await this.legacyEnsureAuthorized( - options.type, - 'find', - options.namespaces, - { args, requireFullAuthorization: false } - ); - - if (status === 'unauthorized') { - // return empty response - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.FIND, - error: new Error(status), - }) - ); - return SavedObjectsUtils.createEmptyFindResponse(options); - } - - const typeToNamespacesMap = Array.from(typeMap).reduce>( - (acc, [type, { authorizedSpaces, isGloballyAuthorized }]) => - isGloballyAuthorized ? acc.set(type, options.namespaces) : acc.set(type, authorizedSpaces), - new Map() - ); - - const response = await this.baseClient.find({ - ...options, - typeToNamespacesMap: undefined, // if the user is fully authorized, use `undefined` as the typeToNamespacesMap to prevent privilege escalation - ...(status === 'partially_authorized' && { typeToNamespacesMap, type: '', namespaces: [] }), // the repository requires that `type` and `namespaces` must be empty if `typeToNamespacesMap` is defined - }); - - response.saved_objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.FIND, - savedObject: { type, id }, - }) - ) - ); - - return await this.redactSavedObjectsNamespaces(response, options.namespaces ?? [undefined]); - } - - public async bulkGet( - objects: SavedObjectsBulkGetObject[] = [], - options: SavedObjectsBaseOptions = {} - ) { - try { - const namespaces = objects.reduce( - (acc, { namespaces: objNamespaces = [] }) => acc.concat(objNamespaces), - [options.namespace] - ); - const args = { objects, options }; - await this.legacyEnsureAuthorized( - this.getUniqueObjectTypes(objects), - 'bulk_get', - namespaces, - { - args, - } - ); - } catch (error) { - objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.GET, - savedObject: { type, id }, - error, - }) - ) - ); - throw error; - } - - const response = await this.baseClient.bulkGet(objects, options); - - response.saved_objects.forEach(({ error, type, id }) => { - if (!error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.GET, - savedObject: { type, id }, - }) - ); - } - }); - - return await this.redactSavedObjectsNamespaces(response, [options.namespace]); - } - - public async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - try { - const args = { type, id, options }; - await this.legacyEnsureAuthorized(type, 'get', options.namespace, { args }); - } catch (error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.GET, - savedObject: { type, id }, - error, - }) - ); - throw error; - } - - const savedObject = await this.baseClient.get(type, id, options); - - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.GET, - savedObject: { type, id }, - }) - ); - - return await this.redactSavedObjectNamespaces(savedObject, [options.namespace]); - } - - public async bulkResolve( - objects: SavedObjectsBulkResolveObject[], - options: SavedObjectsBaseOptions = {} - ) { - try { - const args = { objects, options }; - await this.legacyEnsureAuthorized( - this.getUniqueObjectTypes(objects), - 'bulk_get', - options.namespace, - { args, auditAction: 'bulk_resolve' } - ); - } catch (error) { - objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.RESOLVE, - savedObject: { type, id }, - error, - }) - ) - ); - throw error; - } - - const response = await this.baseClient.bulkResolve(objects, options); - - response.resolved_objects.forEach(({ saved_object: { error, type, id } }) => { - if (!error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.RESOLVE, - savedObject: { type, id }, - }) - ); - } - }); - - // the generic redactSavedObjectsNamespaces function cannot be used here due to the nested structure of the - // resolved objects, so we handle redaction in a bespoke manner for bulkResolve - - if (this.getSpacesService() === undefined) { - return response; - } - - const previouslyAuthorizedSpaceIds = [ - this.getSpacesService()!.namespaceToSpaceId(options.namespace), - ]; - // all users can see the "all spaces" ID, and we don't need to recheck authorization for any namespaces that we just checked earlier - const namespaces = uniq( - response.resolved_objects.flatMap((resolved) => resolved.saved_object.namespaces || []) - ).filter((x) => x !== ALL_SPACES_ID && !previouslyAuthorizedSpaceIds.includes(x)); - - const privilegeMap = await this.getNamespacesPrivilegeMap( - namespaces, - previouslyAuthorizedSpaceIds - ); - - return { - ...response, - resolved_objects: response.resolved_objects.map((resolved) => ({ - ...resolved, - saved_object: { - ...resolved.saved_object, - namespaces: - resolved.saved_object.namespaces && - this.redactAndSortNamespaces(resolved.saved_object.namespaces, privilegeMap), - }, - })), - }; - } - - public async resolve( - type: string, - id: string, - options: SavedObjectsBaseOptions = {} - ) { - try { - const args = { type, id, options }; - await this.legacyEnsureAuthorized(type, 'get', options.namespace, { - args, - auditAction: 'resolve', - }); - } catch (error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.RESOLVE, - savedObject: { type, id }, - error, - }) - ); - throw error; - } - - const resolveResult = await this.baseClient.resolve(type, id, options); - - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.RESOLVE, - savedObject: { type, id: resolveResult.saved_object.id }, - }) - ); - - return { - ...resolveResult, - saved_object: await this.redactSavedObjectNamespaces(resolveResult.saved_object, [ - options.namespace, - ]), - }; - } - - public async update( - type: string, - id: string, - attributes: Partial, - options: SavedObjectsUpdateOptions = {} - ) { - try { - const args = { type, id, attributes, options }; - await this.legacyEnsureAuthorized(type, 'update', options.namespace, { args }); - } catch (error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE, - savedObject: { type, id }, - error, - }) - ); - throw error; - } - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE, - outcome: 'unknown', - savedObject: { type, id }, - }) - ); - - const savedObject = await this.baseClient.update(type, id, attributes, options); - return await this.redactSavedObjectNamespaces(savedObject, [options.namespace]); - } - - public async bulkUpdate( - objects: Array> = [], - options: SavedObjectsBaseOptions = {} - ) { - const objectNamespaces = objects - // The repository treats an `undefined` object namespace is treated as the absence of a namespace, falling back to options.namespace; - // in this case, filter it out here so we don't accidentally check for privileges in the Default space when we shouldn't be doing so. - .filter(({ namespace }) => namespace !== undefined) - .map(({ namespace }) => namespace!); - const namespaces = [options?.namespace, ...objectNamespaces]; - try { - const args = { objects, options }; - await this.legacyEnsureAuthorized( - this.getUniqueObjectTypes(objects), - 'bulk_update', - namespaces, - { - args, - } - ); - } catch (error) { - objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE, - savedObject: { type, id }, - error, - }) - ) - ); - throw error; - } - objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE, - outcome: 'unknown', - savedObject: { type, id }, - }) - ) - ); - - const response = await this.baseClient.bulkUpdate(objects, options); - return await this.redactSavedObjectsNamespaces(response, namespaces); - } - - public async removeReferencesTo( - type: string, - id: string, - options: SavedObjectsRemoveReferencesToOptions = {} - ) { - try { - const args = { type, id, options }; - await this.legacyEnsureAuthorized(type, 'delete', options.namespace, { - args, - auditAction: 'removeReferences', - }); - } catch (error) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.REMOVE_REFERENCES, - savedObject: { type, id }, - error, - }) - ); - throw error; - } - - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.REMOVE_REFERENCES, - savedObject: { type, id }, - outcome: 'unknown', - }) - ); - - return await this.baseClient.removeReferencesTo(type, id, options); - } - - public async openPointInTimeForType( - type: string | string[], - options: SavedObjectsOpenPointInTimeOptions - ) { - const args = { type, options }; - const { status, typeMap } = await this.legacyEnsureAuthorized( - type, - 'open_point_in_time', - options?.namespaces, - { - args, - // Partial authorization is acceptable in this case because this method is only designed - // to be used with `find`, which already allows for partial authorization. - requireFullAuthorization: false, - } - ); - - if (status === 'unauthorized') { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.OPEN_POINT_IN_TIME, - error: new Error(status), - }) - ); - throw SavedObjectsErrorHelpers.decorateForbiddenError(new Error(status)); - } - - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.OPEN_POINT_IN_TIME, - outcome: 'unknown', - }) - ); - - const allowedTypes = [...typeMap.keys()]; // only allow the user to open a PIT against indices for type(s) they are authorized to access - return await this.baseClient.openPointInTimeForType(allowedTypes, options); - } - - public async closePointInTime(id: string, options?: SavedObjectsClosePointInTimeOptions) { - // We are intentionally omitting a call to `ensureAuthorized` here, because `closePointInTime` - // doesn't take in `types`, which are required to perform authorization. As there is no way - // to know what index/indices a PIT was created against, we have no practical means of - // authorizing users. We've decided we are okay with this because: - // (a) Elasticsearch only requires `read` privileges on an index in order to open/close - // a PIT against it, and; - // (b) By the time a user is accessing this service, they are already authenticated - // to Kibana, which is our closest equivalent to Elasticsearch's `read`. - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.CLOSE_POINT_IN_TIME, - outcome: 'unknown', - }) - ); - - return await this.baseClient.closePointInTime(id, options); - } - - public createPointInTimeFinder( - findOptions: SavedObjectsCreatePointInTimeFinderOptions, - dependencies?: SavedObjectsCreatePointInTimeFinderDependencies - ) { - // We don't need to perform an authorization check here or add an audit log, because - // `createPointInTimeFinder` is simply a helper that calls `find`, `openPointInTimeForType`, - // and `closePointInTime` internally, so authz checks and audit logs will already be applied. - return this.baseClient.createPointInTimeFinder(findOptions, { - client: this, - // Include dependencies last so that subsequent SO client wrappers have their settings applied. - ...dependencies, - }); - } - - public async collectMultiNamespaceReferences( - objects: SavedObjectsCollectMultiNamespaceReferencesObject[], - options: SavedObjectsCollectMultiNamespaceReferencesOptions = {} - ): Promise { - const currentSpaceId = SavedObjectsUtils.namespaceIdToString(options.namespace); // We need this whether the Spaces plugin is enabled or not. - - // We don't know the space(s) that each object exists in, so we'll collect the objects and references first, then check authorization. - const response = await this.baseClient.collectMultiNamespaceReferences(objects, options); - const uniqueTypes = this.getUniqueObjectTypes(response.objects); - const uniqueSpaces = this.getUniqueSpaces( - currentSpaceId, - ...response.objects.flatMap( - ({ spaces, spacesWithMatchingAliases = [], spacesWithMatchingOrigins = [] }) => [ - ...spaces, - ...spacesWithMatchingAliases, - ...spacesWithMatchingOrigins, - ] - ) - ); - - const { typeActionMap } = await this.ensureAuthorized( - uniqueTypes, - options.purpose === 'updateObjectsSpaces' ? ['bulk_get', 'share_to_space'] : ['bulk_get'], - uniqueSpaces, - { requireFullAuthorization: false } - ); - - // The user must be authorized to access every requested object in the current space. - // Note: non-multi-namespace object types will have an empty spaces array. - const authAction = options.purpose === 'updateObjectsSpaces' ? 'share_to_space' : 'bulk_get'; - try { - this.ensureAuthorizedInAllSpaces(objects, authAction, typeActionMap, [currentSpaceId]); - } catch (error) { - objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.COLLECT_MULTINAMESPACE_REFERENCES, - savedObject: { type, id }, - error, - }) - ) - ); - throw error; - } - - // The user is authorized to access all of the requested objects in the space(s) that they exist in. - // Now: 1. omit any result objects that the user has no access to, 2. for the rest, redact any space(s) that the user is not authorized - // for, and 3. create audit records for any objects that will be returned to the user. - const requestedObjectsSet = objects.reduce( - (acc, { type, id }) => acc.add(`${type}:${id}`), - new Set() - ); - const retrievedObjectsSet = response.objects.reduce( - (acc, { type, id }) => acc.add(`${type}:${id}`), - new Set() - ); - const traversedObjects = new Set(); - const filteredObjectsMap = new Map(); - const getIsAuthorizedForInboundReference = (inbound: { type: string; id: string }) => { - const found = filteredObjectsMap.get(`${inbound.type}:${inbound.id}`); - return found && !found.isMissing; // If true, this object can be linked back to one of the requested objects - }; - let objectsToProcess = [...response.objects]; - while (objectsToProcess.length > 0) { - const obj = objectsToProcess.shift()!; - const { type, id, spaces, inboundReferences } = obj; - const objKey = `${type}:${id}`; - traversedObjects.add(objKey); - // Is the user authorized to access this object in all required space(s)? - const isAuthorizedForObject = isAuthorizedForObjectInAllSpaces( - type, - authAction, - typeActionMap, - [currentSpaceId] - ); - // Redact the inbound references so we don't leak any info about other objects that the user is not authorized to access - const redactedInboundReferences = inboundReferences.filter((inbound) => { - if (inbound.type === type && inbound.id === id) { - // circular reference, don't redact it - return true; - } - return getIsAuthorizedForInboundReference(inbound); - }); - // If the user is not authorized to access at least one inbound reference of this object, then we should omit this object. - const isAuthorizedForGraph = - requestedObjectsSet.has(objKey) || // If true, this is one of the requested objects, and we checked authorization above - redactedInboundReferences.some(getIsAuthorizedForInboundReference); - - if (isAuthorizedForObject && isAuthorizedForGraph) { - if (spaces.length) { - // Don't generate audit records for "empty results" with zero spaces (requested object was a non-multi-namespace type or hidden type) - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.COLLECT_MULTINAMESPACE_REFERENCES, - savedObject: { type, id }, - }) - ); - } - filteredObjectsMap.set(objKey, obj); - } else if (!isAuthorizedForObject && isAuthorizedForGraph) { - filteredObjectsMap.set(objKey, { ...obj, spaces: [], isMissing: true }); - } else if (isAuthorizedForObject && !isAuthorizedForGraph) { - const hasUntraversedInboundReferences = inboundReferences.some( - (ref) => - !traversedObjects.has(`${ref.type}:${ref.id}`) && - retrievedObjectsSet.has(`${ref.type}:${ref.id}`) - ); - - if (hasUntraversedInboundReferences) { - // this object has inbound reference(s) that we haven't traversed yet; bump it to the back of the list - objectsToProcess = [...objectsToProcess, obj]; - } else { - // There should never be a missing inbound reference. - // If there is, then something has gone terribly wrong. - const missingInboundReference = inboundReferences.find( - (ref) => - !traversedObjects.has(`${ref.type}:${ref.id}`) && - !retrievedObjectsSet.has(`${ref.type}:${ref.id}`) - ); - - if (missingInboundReference) { - throw new Error( - `Unexpected inbound reference to "${missingInboundReference.type}:${missingInboundReference.id}"` - ); - } - } - } - } - - const filteredAndRedactedObjects = [...filteredObjectsMap.values()].map((obj) => { - const { - type, - id, - spaces, - spacesWithMatchingAliases, - spacesWithMatchingOrigins, - inboundReferences, - } = obj; - // Redact the inbound references so we don't leak any info about other objects that the user is not authorized to access - const redactedInboundReferences = inboundReferences.filter((inbound) => { - if (inbound.type === type && inbound.id === id) { - // circular reference, don't redact it - return true; - } - return getIsAuthorizedForInboundReference(inbound); - }); - const redactedSpaces = getRedactedSpaces(type, 'bulk_get', typeActionMap, spaces); - const redactedSpacesWithMatchingAliases = - spacesWithMatchingAliases && - getRedactedSpaces(type, 'bulk_get', typeActionMap, spacesWithMatchingAliases); - const redactedSpacesWithMatchingOrigins = - spacesWithMatchingOrigins && - getRedactedSpaces(type, 'bulk_get', typeActionMap, spacesWithMatchingOrigins); - return { - ...obj, - spaces: redactedSpaces, - ...(redactedSpacesWithMatchingAliases && { - spacesWithMatchingAliases: redactedSpacesWithMatchingAliases, - }), - ...(redactedSpacesWithMatchingOrigins && { - spacesWithMatchingOrigins: redactedSpacesWithMatchingOrigins, - }), - inboundReferences: redactedInboundReferences, - }; - }); - - return { - objects: filteredAndRedactedObjects, - }; - } - - public async updateObjectsSpaces( - objects: SavedObjectsUpdateObjectsSpacesObject[], - spacesToAdd: string[], - spacesToRemove: string[], - options: SavedObjectsUpdateObjectsSpacesOptions = {} - ) { - const { namespace } = options; - const currentSpaceId = SavedObjectsUtils.namespaceIdToString(namespace); // We need this whether the Spaces plugin is enabled or not. - - const allSpacesSet = new Set([currentSpaceId, ...spacesToAdd, ...spacesToRemove]); - const bulkGetResponse = await this.baseClient.bulkGet(objects, { namespace }); - const objectsToUpdate = objects.map(({ type, id }, i) => { - const { namespaces: spaces = [], version } = bulkGetResponse.saved_objects[i]; - // If 'namespaces' is undefined, the object was not found (or it is namespace-agnostic). - // Either way, we will pass in an empty 'spaces' array to the base client, which will cause it to skip this object. - for (const space of spaces) { - if (space !== ALL_SPACES_ID) { - // If this is a specific space, add it to the spaces we'll check privileges for (don't accidentally check for global privileges) - allSpacesSet.add(space); - } - } - return { type, id, spaces, version }; - }); - - const uniqueTypes = this.getUniqueObjectTypes(objects); - const { typeActionMap } = await this.ensureAuthorized( - uniqueTypes, - ['bulk_get', 'share_to_space'], - Array.from(allSpacesSet), - { requireFullAuthorization: false } - ); - - const addToSpaces = spacesToAdd.length ? spacesToAdd : undefined; - const deleteFromSpaces = spacesToRemove.length ? spacesToRemove : undefined; - try { - // The user must be authorized to share every requested object in each of: the current space, spacesToAdd, and spacesToRemove. - const spaces = this.getUniqueSpaces(currentSpaceId, ...spacesToAdd, ...spacesToRemove); - this.ensureAuthorizedInAllSpaces(objects, 'share_to_space', typeActionMap, spaces); - } catch (error) { - objects.forEach(({ type, id }) => - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE_OBJECTS_SPACES, - savedObject: { type, id }, - addToSpaces, - deleteFromSpaces, - error, - }) - ) - ); - throw error; - } - for (const { type, id } of objectsToUpdate) { - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE_OBJECTS_SPACES, - outcome: 'unknown', - savedObject: { type, id }, - addToSpaces, - deleteFromSpaces, - }) - ); - } - - const response = await this.baseClient.updateObjectsSpaces( - objectsToUpdate, - spacesToAdd, - spacesToRemove, - { namespace } - ); - // Now that we have updated the objects' spaces, redact any spaces that the user is not authorized to see from the response. - const redactedObjects = response.objects.map((obj) => { - const { type, spaces } = obj; - const redactedSpaces = getRedactedSpaces(type, 'bulk_get', typeActionMap, spaces); - return { ...obj, spaces: redactedSpaces }; - }); - - return { objects: redactedObjects }; - } - - private async checkPrivileges( - actions: string | string[], - namespaceOrNamespaces?: string | Array - ) { - try { - return await this.checkSavedObjectsPrivilegesAsCurrentUser(actions, namespaceOrNamespaces); - } catch (error) { - throw this.errors.decorateGeneralError(error, error.body && error.body.reason); - } - } - - private async legacyEnsureAuthorized( - typeOrTypes: string | string[], - action: string, - namespaceOrNamespaces: undefined | string | Array, - options: LegacyEnsureAuthorizedOptions = {} - ): Promise { - const { requireFullAuthorization = true } = options; - const types = Array.isArray(typeOrTypes) ? typeOrTypes : [typeOrTypes]; - const actionsToTypesMap = new Map( - types.map((type) => [this.actions.savedObject.get(type, action), type]) - ); - const actions = Array.from(actionsToTypesMap.keys()); - const result = await this.checkPrivileges(actions, namespaceOrNamespaces); - - const { hasAllRequested, privileges } = result; - - const missingPrivileges = this.getMissingPrivileges(privileges); - const typeMap = privileges.kibana.reduce>( - (acc, { resource, privilege, authorized }) => { - if (!authorized) { - return acc; - } - const type = actionsToTypesMap.get(privilege)!; // always defined - const value = acc.get(type) ?? { authorizedSpaces: [] }; - if (resource === undefined) { - return acc.set(type, { ...value, isGloballyAuthorized: true }); - } - const authorizedSpaces = value.authorizedSpaces.concat(resource); - return acc.set(type, { ...value, authorizedSpaces }); - }, - new Map() - ); - - if (hasAllRequested) { - return { typeMap, status: 'fully_authorized' }; - } else if (!requireFullAuthorization) { - const isPartiallyAuthorized = privileges.kibana.some(({ authorized }) => authorized); - if (isPartiallyAuthorized) { - return { typeMap, status: 'partially_authorized' }; - } else { - return { typeMap, status: 'unauthorized' }; - } - } else { - const targetTypes = uniq( - missingPrivileges.map(({ privilege }) => actionsToTypesMap.get(privilege)).sort() - ).join(','); - const msg = `Unable to ${action} ${targetTypes}`; - throw this.errors.decorateForbiddenError(new Error(msg)); - } - } - - /** Unlike `legacyEnsureAuthorized`, this accepts multiple actions, and it does not utilize legacy audit logging */ - private async ensureAuthorized( - types: string[], - actions: T[], - namespaces: string[], - options?: EnsureAuthorizedOptions - ) { - const ensureAuthorizedDependencies: EnsureAuthorizedDependencies = { - actions: this.actions, - errors: this.errors, - checkSavedObjectsPrivilegesAsCurrentUser: this.checkSavedObjectsPrivilegesAsCurrentUser, - }; - return ensureAuthorized(ensureAuthorizedDependencies, types, actions, namespaces, options); - } - - /** - * If `ensureAuthorized` was called with `requireFullAuthorization: false`, this can be used with the result to ensure that a given - * array of objects are authorized in the required space(s). - */ - private ensureAuthorizedInAllSpaces( - objects: Array<{ type: string }>, - action: T, - typeActionMap: EnsureAuthorizedResult['typeActionMap'], - spaces: string[] - ) { - const uniqueTypes = uniq(objects.map(({ type }) => type)); - const unauthorizedTypes = new Set(); - for (const type of uniqueTypes) { - if (!isAuthorizedForObjectInAllSpaces(type, action, typeActionMap, spaces)) { - unauthorizedTypes.add(type); - } - } - if (unauthorizedTypes.size > 0) { - const targetTypes = Array.from(unauthorizedTypes).sort().join(','); - const msg = `Unable to ${action} ${targetTypes}`; - throw this.errors.decorateForbiddenError(new Error(msg)); - } - } - - private getMissingPrivileges(privileges: CheckPrivilegesResponse['privileges']) { - return privileges.kibana - .filter(({ authorized }) => !authorized) - .map(({ resource, privilege }) => ({ spaceId: resource, privilege })); - } - - private getUniqueObjectTypes(objects: Array<{ type: string }>) { - return uniq(objects.map((o) => o.type)); - } - - /** - * Given a list of spaces, returns a unique array of spaces. - * Excludes `'*'`, which is an identifier for All Spaces but is not an actual space. - */ - private getUniqueSpaces(...spaces: string[]) { - const set = new Set(spaces); - set.delete(ALL_SPACES_ID); - return Array.from(set); - } - - private async getNamespacesPrivilegeMap( - namespaces: string[], - previouslyAuthorizedSpaceIds: string[] - ) { - const namespacesToCheck = namespaces.filter( - (namespace) => !previouslyAuthorizedSpaceIds.includes(namespace) - ); - const initialPrivilegeMap = previouslyAuthorizedSpaceIds.reduce( - (acc, spaceId) => acc.set(spaceId, true), - new Map() - ); - if (namespacesToCheck.length === 0) { - return initialPrivilegeMap; - } - const action = this.actions.login; - const checkPrivilegesResult = await this.checkPrivileges(action, namespacesToCheck); - // check if the user can log into each namespace - const map = checkPrivilegesResult.privileges.kibana.reduce((acc, { resource, authorized }) => { - // there should never be a case where more than one privilege is returned for a given space - // if there is, fail-safe (authorized + unauthorized = unauthorized) - if (resource && (!authorized || !acc.has(resource))) { - acc.set(resource, authorized); - } - return acc; - }, initialPrivilegeMap); - return map; - } - - private redactAndSortNamespaces(spaceIds: string[], privilegeMap: Map) { - return spaceIds - .map((x) => (x === ALL_SPACES_ID || privilegeMap.get(x) ? x : UNKNOWN_SPACE)) - .sort(namespaceComparator); - } - - private async redactSavedObjectNamespaces( - savedObject: T, - previouslyAuthorizedNamespaces: Array - ): Promise { - if ( - this.getSpacesService() === undefined || - savedObject.namespaces == null || - savedObject.namespaces.length === 0 - ) { - return savedObject; - } - - const previouslyAuthorizedSpaceIds = previouslyAuthorizedNamespaces.map((x) => - this.getSpacesService()!.namespaceToSpaceId(x) - ); - // all users can see the "all spaces" ID, and we don't need to recheck authorization for any namespaces that we just checked earlier - const namespaces = savedObject.namespaces.filter( - (x) => x !== ALL_SPACES_ID && !previouslyAuthorizedSpaceIds.includes(x) - ); - - const privilegeMap = await this.getNamespacesPrivilegeMap( - namespaces, - previouslyAuthorizedSpaceIds - ); - - return { - ...savedObject, - namespaces: this.redactAndSortNamespaces(savedObject.namespaces, privilegeMap), - }; - } - - private async redactSavedObjectsNamespaces( - response: T, - previouslyAuthorizedNamespaces: Array - ): Promise { - // WARNING: the bulkResolve function has a bespoke implementation of this; any changes here should be applied there too. - - if (this.getSpacesService() === undefined) { - return response; - } - - const previouslyAuthorizedSpaceIds = previouslyAuthorizedNamespaces.map((x) => - this.getSpacesService()!.namespaceToSpaceId(x) - ); - const { saved_objects: savedObjects } = response; - // all users can see the "all spaces" ID, and we don't need to recheck authorization for any namespaces that we just checked earlier - const namespaces = uniq( - savedObjects.flatMap((savedObject) => savedObject.namespaces || []) - ).filter((x) => x !== ALL_SPACES_ID && !previouslyAuthorizedSpaceIds.includes(x)); - - const privilegeMap = await this.getNamespacesPrivilegeMap( - namespaces, - previouslyAuthorizedSpaceIds - ); - - return { - ...response, - saved_objects: savedObjects.map((savedObject) => ({ - ...savedObject, - namespaces: - savedObject.namespaces && - this.redactAndSortNamespaces(savedObject.namespaces, privilegeMap), - })), - }; - } -} - -/** - * Returns all unique elements of an array. - */ -function uniq(arr: T[]): T[] { - return Array.from(new Set(arr)); -} - -/** - * Utility function to sort potentially redacted namespaces. - * Sorts in a case-insensitive manner, and ensures that redacted namespaces ('?') always show up at the end of the array. - */ -function namespaceComparator(a: string, b: string) { - const A = a.toUpperCase(); - const B = b.toUpperCase(); - if (A === UNKNOWN_SPACE && B !== UNKNOWN_SPACE) { - return 1; - } else if (A !== UNKNOWN_SPACE && B === UNKNOWN_SPACE) { - return -1; - } - return A > B ? 1 : A < B ? -1 : 0; -} - -function getRedactedSpaces( - objectType: string, - action: T, - typeActionMap: EnsureAuthorizedResult['typeActionMap'], - spacesToRedact: string[] -) { - const actionResult = getEnsureAuthorizedActionResult(objectType, action, typeActionMap); - const { authorizedSpaces, isGloballyAuthorized } = actionResult; - const authorizedSpacesSet = new Set(authorizedSpaces); - return spacesToRedact - .map((x) => - isGloballyAuthorized || x === ALL_SPACES_ID || authorizedSpacesSet.has(x) ? x : UNKNOWN_SPACE - ) - .sort(namespaceComparator); -} diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts index 092721b5e07655..98752ed496a0ed 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts @@ -61,7 +61,7 @@ export class PolicyWatcher { url: { href: {} }, raw: { req: { url: '/' } }, } as unknown as KibanaRequest; - return soStart.getScopedClient(fakeRequest, { excludedWrappers: ['security'] }); + return soStart.getScopedClient(fakeRequest, { excludedExtensions: ['security'] }); } public start(licenseService: LicenseService) { diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts index 9866e866a7ea3d..9dc2f8341cb303 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts @@ -46,7 +46,7 @@ export const createInternalReadonlySoClient = ( } as unknown as KibanaRequest; const internalSoClient = savedObjectsServiceStart.getScopedClient(fakeRequest, { - excludedWrappers: ['security'], + excludedExtensions: ['security'], }); return new Proxy(internalSoClient, { diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts index f548f5e2f6c999..6c5d91f075a555 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts @@ -8,5 +8,5 @@ import type { SavedObjectsClientProviderOptions } from '@kbn/core/server'; export const COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS: SavedObjectsClientProviderOptions = { - excludedWrappers: ['spaces'], + excludedExtensions: ['spaces'], }; diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 876c9b772fddfa..5b8c43649de2e7 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -163,7 +163,7 @@ describe('copy to space', () => { await copyToSpace.routeHandler(mockRouteContext, request, kibanaResponseFactory); expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledWith(request, { - excludedWrappers: ['spaces'], + excludedExtensions: ['spaces'], }); }); @@ -326,7 +326,7 @@ describe('copy to space', () => { await resolveConflicts.routeHandler(mockRouteContext, request, kibanaResponseFactory); expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledWith(request, { - excludedWrappers: ['spaces'], + excludedExtensions: ['spaces'], }); }); diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_client_wrapper_factory.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_client_wrapper_factory.ts deleted file mode 100644 index 4e2a12955d702f..00000000000000 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_client_wrapper_factory.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { - SavedObjectsClientWrapperFactory, - SavedObjectsClientWrapperOptions, -} from '@kbn/core/server'; - -import type { SpacesServiceStart } from '../spaces_service/spaces_service'; -import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; - -export function spacesSavedObjectsClientWrapperFactory( - getSpacesService: () => SpacesServiceStart -): SavedObjectsClientWrapperFactory { - return (options: SavedObjectsClientWrapperOptions) => - new SpacesSavedObjectsClient({ - baseClient: options.client, - request: options.request, - getSpacesService, - typeRegistry: options.typeRegistry, - }); -} diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts deleted file mode 100644 index 70a8628246b716..00000000000000 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.test.ts +++ /dev/null @@ -1,865 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import Boom from '@hapi/boom'; - -import type { SavedObject, SavedObjectsType } from '@kbn/core/server'; -import { SavedObjectsErrorHelpers } from '@kbn/core/server'; -import { savedObjectsClientMock, savedObjectsTypeRegistryMock } from '@kbn/core/server/mocks'; - -import { DEFAULT_SPACE_ID } from '../../common/constants'; -import { spacesClientMock } from '../spaces_client/spaces_client.mock'; -import { spacesServiceMock } from '../spaces_service/spaces_service.mock'; -import { SpacesSavedObjectsClient } from './spaces_saved_objects_client'; - -const createMockRequest = () => ({}); - -const createMockClient = () => savedObjectsClientMock.create(); - -const createSpacesService = (spaceId: string) => { - return spacesServiceMock.createStartContract(spaceId); -}; - -const createMockResponse = () => ({ - id: 'logstash-*', - title: 'logstash-*', - type: 'logstash-type', - attributes: {}, - timeFieldName: '@timestamp', - notExpandable: true, - references: [], - score: 0, -}); - -const ERROR_NAMESPACE_SPECIFIED = 'Spaces currently determines the namespaces'; - -[ - { id: DEFAULT_SPACE_ID, expectedNamespace: undefined }, - { id: 'space_1', expectedNamespace: 'space_1' }, -].forEach((currentSpace) => { - describe(`${currentSpace.id} space`, () => { - const createSpacesSavedObjectsClient = () => { - const request = createMockRequest(); - const baseClient = createMockClient(); - const spacesService = createSpacesService(currentSpace.id); - const spacesClient = spacesClientMock.create(); - spacesService.createSpacesClient.mockReturnValue(spacesClient); - const typeRegistry = savedObjectsTypeRegistryMock.create(); - typeRegistry.getAllTypes.mockReturnValue([ - // for test purposes we only need the names of the object types - { name: 'foo' }, - { name: 'bar' }, - { name: 'space' }, - ] as unknown as SavedObjectsType[]); - - const client = new SpacesSavedObjectsClient({ - request, - baseClient, - getSpacesService: () => spacesService, - typeRegistry, - }); - return { client, baseClient, spacesClient, typeRegistry }; - }; - - describe('#get', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect(client.get('foo', '', { namespace: 'bar' })).rejects.toThrow( - ERROR_NAMESPACE_SPECIFIED - ); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = createMockResponse(); - baseClient.get.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const type = Symbol(); - const id = Symbol(); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.get(type, id, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.get).toHaveBeenCalledWith(type, id, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#bulkResolve', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect(client.bulkResolve([], { namespace: 'bar' })).rejects.toThrow( - ERROR_NAMESPACE_SPECIFIED - ); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { resolved_objects: [] }; - baseClient.bulkResolve.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.bulkResolve([], options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.bulkResolve).toHaveBeenCalledWith([], { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#resolve', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect(client.resolve('foo', '', { namespace: 'bar' })).rejects.toThrow( - ERROR_NAMESPACE_SPECIFIED - ); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { - saved_object: createMockResponse(), - outcome: 'exactMatch' as 'exactMatch', // outcome doesn't matter, just including it for type safety - }; - baseClient.resolve.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const type = Symbol(); - const id = Symbol(); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.resolve(type, id, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.resolve).toHaveBeenCalledWith(type, id, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#bulkGet', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - client.bulkGet([{ id: '', type: 'foo' }], { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { saved_objects: [createMockResponse()] }; - baseClient.bulkGet.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const objects = [{ type: 'foo' }]; - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.bulkGet(objects, options); - - expect(actualReturnValue).toEqual(expectedReturnValue); - expect(baseClient.bulkGet).toHaveBeenCalledWith(objects, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - expect(spacesClient.getAll).not.toHaveBeenCalled(); - }); - - test(`replaces object namespaces '*' with available spaces`, async () => { - const { client, baseClient, spacesClient, typeRegistry } = createSpacesSavedObjectsClient(); - spacesClient.getAll.mockResolvedValue([ - { id: 'available-space-a', name: 'a', disabledFeatures: [] }, - { id: 'available-space-b', name: 'b', disabledFeatures: [] }, - ]); - typeRegistry.isNamespaceAgnostic.mockImplementation((type) => type === 'foo'); - typeRegistry.isShareable.mockImplementation((type) => type === 'bar'); - // 'baz' is neither agnostic nor shareable, so it is isolated (namespaceType: 'single' or namespaceType: 'multiple-isolated') - baseClient.bulkGet.mockResolvedValue({ - saved_objects: [ - { type: 'foo', id: '1', key: 'val' }, - { type: 'bar', id: '2', key: 'val' }, - { type: 'baz', id: '3', key: 'val' }, // this should be replaced with a 400 error - { type: 'foo', id: '4', key: 'val' }, - { type: 'bar', id: '5', key: 'val' }, - { type: 'baz', id: '6', key: 'val' }, // this should not be replaced with a 400 error because the user did not search for it in '*' all spaces - ] as unknown as SavedObject[], - }); - - const objects = [ - { type: 'foo', id: '1', namespaces: ['*', 'this-is-ignored'] }, - { type: 'bar', id: '2', namespaces: ['*', 'this-is-ignored'] }, - { type: 'baz', id: '3', namespaces: ['*', 'this-is-ignored'] }, - { type: 'foo', id: '4', namespaces: ['another-space'] }, - { type: 'bar', id: '5', namespaces: ['another-space'] }, - { type: 'baz', id: '6', namespaces: ['another-space'] }, - ]; - const result = await client.bulkGet(objects); - - expect(result.saved_objects).toEqual([ - { type: 'foo', id: '1', key: 'val' }, - { type: 'bar', id: '2', key: 'val' }, - { - type: 'baz', - id: '3', - error: SavedObjectsErrorHelpers.createBadRequestError( - '"namespaces" can only specify a single space when used with space-isolated types' - ).output.payload, - }, - { type: 'foo', id: '4', key: 'val' }, - { type: 'bar', id: '5', key: 'val' }, - { type: 'baz', id: '6', key: 'val' }, - ]); - expect(baseClient.bulkGet).toHaveBeenCalledWith( - [ - { type: 'foo', id: '1', namespaces: ['available-space-a', 'available-space-b'] }, - { type: 'bar', id: '2', namespaces: ['available-space-a', 'available-space-b'] }, - { type: 'baz', id: '3', namespaces: ['available-space-a', 'available-space-b'] }, - // even if another space doesn't exist, it can be specified explicitly - { type: 'foo', id: '4', namespaces: ['another-space'] }, - { type: 'bar', id: '5', namespaces: ['another-space'] }, - { type: 'baz', id: '6', namespaces: ['another-space'] }, - ], - { namespace: currentSpace.expectedNamespace } - ); - expect(spacesClient.getAll).toHaveBeenCalledTimes(1); - }); - - test(`replaces object namespaces '*' with an empty array when the user doesn't have access to any spaces`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - spacesClient.getAll.mockRejectedValue(Boom.forbidden()); - baseClient.bulkGet.mockResolvedValue({ saved_objects: [] }); // doesn't matter for this test - - const objects = [ - { type: 'foo', id: '1', namespaces: ['*'] }, - { type: 'bar', id: '2', namespaces: ['*', 'this-is-ignored'] }, - { type: 'baz', id: '3', namespaces: ['another-space'] }, - ]; - await client.bulkGet(objects); - - expect(baseClient.bulkGet).toHaveBeenCalledWith( - [ - { type: 'foo', id: '1', namespaces: [] }, - { type: 'bar', id: '2', namespaces: [] }, - { type: 'baz', id: '3', namespaces: ['another-space'] }, // even if another space doesn't exist, it can be specified explicitly - ], - { namespace: currentSpace.expectedNamespace } - ); - expect(spacesClient.getAll).toHaveBeenCalledTimes(1); - }); - }); - - describe('#find', () => { - const EMPTY_RESPONSE = { saved_objects: [], total: 0, per_page: 20, page: 1 }; - - test(`returns empty result if user is unauthorized in this space`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - spacesClient.getAll.mockResolvedValue([]); - - const options = Object.freeze({ type: 'foo', namespaces: ['some-ns'] }); - const actualReturnValue = await client.find(options); - - expect(actualReturnValue).toEqual(EMPTY_RESPONSE); - expect(baseClient.find).not.toHaveBeenCalled(); - }); - - test(`returns empty result if user is unauthorized in any space`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - spacesClient.getAll.mockRejectedValue(Boom.forbidden()); - - const options = Object.freeze({ type: 'foo', namespaces: ['some-ns'] }); - const actualReturnValue = await client.find(options); - - expect(actualReturnValue).toEqual(EMPTY_RESPONSE); - expect(baseClient.find).not.toHaveBeenCalled(); - }); - - test(`passes options.type to baseClient if valid singular type specified`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { - saved_objects: [createMockResponse()].map((obj) => ({ ...obj, score: 1 })), - total: 1, - per_page: 0, - page: 0, - }; - baseClient.find.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const options = Object.freeze({ type: 'foo' }); - const actualReturnValue = await client.find(options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.find).toHaveBeenCalledWith({ - type: ['foo'], - namespaces: [currentSpace.expectedNamespace ?? 'default'], - }); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { - saved_objects: [createMockResponse()].map((obj) => ({ ...obj, score: 1 })), - total: 1, - per_page: 0, - page: 0, - }; - baseClient.find.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const options = Object.freeze({ type: ['foo', 'bar'] }); - const actualReturnValue = await client.find(options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.find).toHaveBeenCalledWith({ - type: ['foo', 'bar'], - namespaces: [currentSpace.expectedNamespace ?? 'default'], - }); - }); - - test(`passes options.namespaces along`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { - saved_objects: [createMockResponse()], - total: 1, - per_page: 0, - page: 0, - }; - baseClient.find.mockReturnValue(Promise.resolve(expectedReturnValue)); - - spacesClient.getAll.mockImplementation(() => - Promise.resolve([ - { id: 'ns-1', name: '', disabledFeatures: [] }, - { id: 'ns-2', name: '', disabledFeatures: [] }, - ]) - ); - - const options = Object.freeze({ type: ['foo', 'bar'], namespaces: ['ns-1', 'ns-2'] }); - const actualReturnValue = await client.find(options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.find).toHaveBeenCalledWith({ - type: ['foo', 'bar'], - namespaces: ['ns-1', 'ns-2'], - }); - expect(spacesClient.getAll).toHaveBeenCalledWith({ purpose: 'findSavedObjects' }); - }); - - test(`filters options.namespaces based on authorization`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { - saved_objects: [createMockResponse()], - total: 1, - per_page: 0, - page: 0, - }; - baseClient.find.mockReturnValue(Promise.resolve(expectedReturnValue)); - - spacesClient.getAll.mockImplementation(() => - Promise.resolve([ - { id: 'ns-1', name: '', disabledFeatures: [] }, - { id: 'ns-2', name: '', disabledFeatures: [] }, - ]) - ); - - const options = Object.freeze({ type: ['foo', 'bar'], namespaces: ['ns-1', 'ns-3'] }); - const actualReturnValue = await client.find(options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.find).toHaveBeenCalledWith({ - type: ['foo', 'bar'], - namespaces: ['ns-1'], - }); - expect(spacesClient.getAll).toHaveBeenCalledWith({ purpose: 'findSavedObjects' }); - }); - - test(`translates options.namespace: ['*']`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { - saved_objects: [createMockResponse()], - total: 1, - per_page: 0, - page: 0, - }; - baseClient.find.mockReturnValue(Promise.resolve(expectedReturnValue)); - - spacesClient.getAll.mockImplementation(() => - Promise.resolve([ - { id: 'ns-1', name: '', disabledFeatures: [] }, - { id: 'ns-2', name: '', disabledFeatures: [] }, - ]) - ); - - const options = Object.freeze({ type: ['foo', 'bar'], namespaces: ['*'] }); - const actualReturnValue = await client.find(options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.find).toHaveBeenCalledWith({ - type: ['foo', 'bar'], - namespaces: ['ns-1', 'ns-2'], - }); - expect(spacesClient.getAll).toHaveBeenCalledWith({ purpose: 'findSavedObjects' }); - }); - }); - - describe('#checkConflicts', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - // @ts-expect-error - client.checkConflicts(null, { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { errors: [] }; - baseClient.checkConflicts.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const objects = Symbol(); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.checkConflicts(objects, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.checkConflicts).toHaveBeenCalledWith(objects, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#create', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect(client.create('foo', {}, { namespace: 'bar' })).rejects.toThrow( - ERROR_NAMESPACE_SPECIFIED - ); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = createMockResponse(); - baseClient.create.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const type = Symbol(); - const attributes = Symbol(); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.create(type, attributes, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.create).toHaveBeenCalledWith(type, attributes, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#bulkCreate', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - client.bulkCreate([{ id: '', type: 'foo', attributes: {} }], { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { saved_objects: [createMockResponse()] }; - baseClient.bulkCreate.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const objects = [{ type: 'foo' }]; - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.bulkCreate(objects, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.bulkCreate).toHaveBeenCalledWith(objects, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#update', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - // @ts-expect-error - client.update(null, null, null, { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = createMockResponse(); - baseClient.update.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const type = Symbol(); - const id = Symbol(); - const attributes = Symbol(); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.update(type, id, attributes, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.update).toHaveBeenCalledWith(type, id, attributes, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#bulkUpdate', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - // @ts-expect-error - client.bulkUpdate(null, { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { saved_objects: [createMockResponse()] }; - baseClient.bulkUpdate.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const actualReturnValue = await client.bulkUpdate([ - { id: 'id', type: 'foo', attributes: {}, references: [] }, - ]); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.bulkUpdate).toHaveBeenCalledWith( - [ - { - id: 'id', - type: 'foo', - attributes: {}, - references: [], - }, - ], - { namespace: currentSpace.expectedNamespace } - ); - }); - }); - - describe('#delete', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - // @ts-expect-error - client.delete(null, null, { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = createMockResponse(); - baseClient.delete.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const type = Symbol(); - const id = Symbol(); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.delete(type, id, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.delete).toHaveBeenCalledWith(type, id, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#bulkDelete', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - // @ts-expect-error - client.bulkDelete(null, { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { statuses: [{ id: 'id', type: 'type', success: true }] }; - baseClient.bulkDelete.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const actualReturnValue = await client.bulkDelete([{ id: 'id', type: 'foo' }], { - force: true, - }); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.bulkDelete).toHaveBeenCalledWith( - [ - { - id: 'id', - type: 'foo', - }, - ], - { - namespace: currentSpace.expectedNamespace, - force: true, - } - ); - }); - }); - - describe('#removeReferencesTo', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - // @ts-expect-error - client.removeReferencesTo(null, null, { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { updated: 12 }; - baseClient.removeReferencesTo.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const type = Symbol(); - const id = Symbol(); - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.removeReferencesTo(type, id, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.removeReferencesTo).toHaveBeenCalledWith(type, id, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#openPointInTimeForType', () => { - test(`throws error if if user is unauthorized in this space`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - spacesClient.getAll.mockResolvedValue([]); - - await expect( - client.openPointInTimeForType('foo', { namespaces: ['bar'] }) - ).rejects.toThrowError('Bad Request'); - - expect(baseClient.openPointInTimeForType).not.toHaveBeenCalled(); - }); - - test(`throws error if if user is unauthorized in any space`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - spacesClient.getAll.mockRejectedValue(Boom.forbidden()); - - await expect( - client.openPointInTimeForType('foo', { namespaces: ['bar'] }) - ).rejects.toThrowError('Bad Request'); - - expect(baseClient.openPointInTimeForType).not.toHaveBeenCalled(); - }); - - test(`filters options.namespaces based on authorization`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { id: 'abc123' }; - baseClient.openPointInTimeForType.mockReturnValue(Promise.resolve(expectedReturnValue)); - - spacesClient.getAll.mockImplementation(() => - Promise.resolve([ - { id: 'ns-1', name: '', disabledFeatures: [] }, - { id: 'ns-2', name: '', disabledFeatures: [] }, - ]) - ); - - const options = Object.freeze({ namespaces: ['ns-1', 'ns-3'] }); - const actualReturnValue = await client.openPointInTimeForType(['foo', 'bar'], options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.openPointInTimeForType).toHaveBeenCalledWith(['foo', 'bar'], { - namespaces: ['ns-1'], - }); - expect(spacesClient.getAll).toHaveBeenCalledWith({ purpose: 'findSavedObjects' }); - }); - - test(`translates options.namespaces: ['*']`, async () => { - const { client, baseClient, spacesClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { id: 'abc123' }; - baseClient.openPointInTimeForType.mockReturnValue(Promise.resolve(expectedReturnValue)); - - spacesClient.getAll.mockImplementation(() => - Promise.resolve([ - { id: 'ns-1', name: '', disabledFeatures: [] }, - { id: 'ns-2', name: '', disabledFeatures: [] }, - ]) - ); - - const options = Object.freeze({ namespaces: ['*'] }); - const actualReturnValue = await client.openPointInTimeForType(['foo', 'bar'], options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.openPointInTimeForType).toHaveBeenCalledWith(['foo', 'bar'], { - namespaces: ['ns-1', 'ns-2'], - }); - expect(spacesClient.getAll).toHaveBeenCalledWith({ purpose: 'findSavedObjects' }); - }); - - test(`supplements options with the current namespace if unspecified`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { id: 'abc123' }; - baseClient.openPointInTimeForType.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const options = Object.freeze({ keepAlive: '2m' }); - const actualReturnValue = await client.openPointInTimeForType('foo', options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.openPointInTimeForType).toHaveBeenCalledWith('foo', { - keepAlive: '2m', - namespaces: [currentSpace.expectedNamespace ?? DEFAULT_SPACE_ID], - }); - }); - }); - - describe('#closePointInTime', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect(client.closePointInTime('foo', { namespace: 'bar' })).rejects.toThrow( - ERROR_NAMESPACE_SPECIFIED - ); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { succeeded: true, num_freed: 1 }; - baseClient.closePointInTime.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.closePointInTime('foo', options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.closePointInTime).toHaveBeenCalledWith('foo', { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#createPointInTimeFinder', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - const options = { type: ['a', 'b'], search: 'query', namespace: 'oops' }; - expect(() => client.createPointInTimeFinder(options)).toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - it('redirects request to underlying base client with default dependencies', () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - - const options = { type: ['a', 'b'], search: 'query' }; - client.createPointInTimeFinder(options); - - expect(baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, { - client, - }); - }); - - it('redirects request to underlying base client with custom dependencies', () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - - const options = { type: ['a', 'b'], search: 'query' }; - const dependencies = { - client: { - find: jest.fn(), - openPointInTimeForType: jest.fn(), - closePointInTime: jest.fn(), - }, - }; - client.createPointInTimeFinder(options, dependencies); - - expect(baseClient.createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(baseClient.createPointInTimeFinder).toHaveBeenCalledWith(options, dependencies); - }); - }); - - describe('#collectMultiNamespaceReferences', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect( - client.collectMultiNamespaceReferences([], { namespace: 'bar' }) - ).rejects.toThrow(ERROR_NAMESPACE_SPECIFIED); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { objects: [] }; - baseClient.collectMultiNamespaceReferences.mockReturnValue( - Promise.resolve(expectedReturnValue) - ); - - const objects = [{ type: 'foo', id: 'bar' }]; - const options = Object.freeze({ foo: 'bar' }); - // @ts-expect-error - const actualReturnValue = await client.collectMultiNamespaceReferences(objects, options); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.collectMultiNamespaceReferences).toHaveBeenCalledWith(objects, { - foo: 'bar', - namespace: currentSpace.expectedNamespace, - }); - }); - }); - - describe('#updateObjectsSpaces', () => { - test(`throws error if options.namespace is specified`, async () => { - const { client } = createSpacesSavedObjectsClient(); - - await expect(client.updateObjectsSpaces([], [], [], { namespace: 'bar' })).rejects.toThrow( - ERROR_NAMESPACE_SPECIFIED - ); - }); - - test(`supplements options with the current namespace`, async () => { - const { client, baseClient } = createSpacesSavedObjectsClient(); - const expectedReturnValue = { objects: [] }; - baseClient.updateObjectsSpaces.mockReturnValue(Promise.resolve(expectedReturnValue)); - - const objects = [{ type: 'foo', id: 'bar' }]; - const spacesToAdd = ['space-x']; - const spacesToRemove = ['space-y']; - const options = Object.freeze({ foo: 'bar' }); - const actualReturnValue = await client.updateObjectsSpaces( - objects, - spacesToAdd, - spacesToRemove, - // @ts-expect-error - options - ); - - expect(actualReturnValue).toBe(expectedReturnValue); - expect(baseClient.updateObjectsSpaces).toHaveBeenCalledWith( - objects, - spacesToAdd, - spacesToRemove, - { foo: 'bar', namespace: currentSpace.expectedNamespace } - ); - }); - }); - }); -}); diff --git a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts b/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts deleted file mode 100644 index 52ca1f2604e888..00000000000000 --- a/x-pack/plugins/spaces/server/saved_objects/spaces_saved_objects_client.ts +++ /dev/null @@ -1,397 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import Boom from '@hapi/boom'; - -import type { - ISavedObjectTypeRegistry, - SavedObject, - SavedObjectsBaseOptions, - SavedObjectsBulkCreateObject, - SavedObjectsBulkDeleteObject, - SavedObjectsBulkDeleteOptions, - SavedObjectsBulkGetObject, - SavedObjectsBulkResolveObject, - SavedObjectsBulkUpdateObject, - SavedObjectsCheckConflictsObject, - SavedObjectsClientContract, - SavedObjectsClosePointInTimeOptions, - SavedObjectsCollectMultiNamespaceReferencesObject, - SavedObjectsCollectMultiNamespaceReferencesOptions, - SavedObjectsCollectMultiNamespaceReferencesResponse, - SavedObjectsCreateOptions, - SavedObjectsCreatePointInTimeFinderDependencies, - SavedObjectsCreatePointInTimeFinderOptions, - SavedObjectsFindOptions, - SavedObjectsOpenPointInTimeOptions, - SavedObjectsRemoveReferencesToOptions, - SavedObjectsUpdateObjectsSpacesObject, - SavedObjectsUpdateObjectsSpacesOptions, - SavedObjectsUpdateOptions, -} from '@kbn/core/server'; -import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '@kbn/core/server'; - -import { ALL_SPACES_ID } from '../../common/constants'; -import { spaceIdToNamespace } from '../lib/utils/namespace'; -import type { ISpacesClient } from '../spaces_client'; -import type { SpacesServiceStart } from '../spaces_service/spaces_service'; - -interface Left { - tag: 'Left'; - value: L; -} - -interface Right { - tag: 'Right'; - value: R; -} - -type Either = Left | Right; -const isLeft = (either: Either): either is Left => either.tag === 'Left'; - -interface SpacesSavedObjectsClientOptions { - baseClient: SavedObjectsClientContract; - request: any; - getSpacesService: () => SpacesServiceStart; - typeRegistry: ISavedObjectTypeRegistry; -} - -const coerceToArray = (param: string | string[]) => { - if (Array.isArray(param)) { - return param; - } - - return [param]; -}; - -const throwErrorIfNamespaceSpecified = (options: any) => { - if (options.namespace) { - throw new Error('Spaces currently determines the namespaces'); - } -}; - -export class SpacesSavedObjectsClient implements SavedObjectsClientContract { - private readonly client: SavedObjectsClientContract; - private readonly typeRegistry: ISavedObjectTypeRegistry; - private readonly spaceId: string; - private readonly types: string[]; - private readonly spacesClient: ISpacesClient; - public readonly errors: typeof SavedObjectsErrorHelpers; - - constructor(options: SpacesSavedObjectsClientOptions) { - const { baseClient, request, getSpacesService, typeRegistry } = options; - - const spacesService = getSpacesService(); - - this.client = baseClient; - this.typeRegistry = typeRegistry; - this.spacesClient = spacesService.createSpacesClient(request); - this.spaceId = spacesService.getSpaceId(request); - this.types = typeRegistry.getAllTypes().map((t) => t.name); - this.errors = SavedObjectsErrorHelpers; - } - - async checkConflicts( - objects: SavedObjectsCheckConflictsObject[] = [], - options: SavedObjectsBaseOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.checkConflicts(objects, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async create( - type: string, - attributes: T = {} as T, - options: SavedObjectsCreateOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.create(type, attributes, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async bulkCreate( - objects: Array>, - options: SavedObjectsBaseOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.bulkCreate(objects, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async delete(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.delete(type, id, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async bulkDelete( - objects: SavedObjectsBulkDeleteObject[] = [], - options: SavedObjectsBulkDeleteOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - return await this.client.bulkDelete(objects, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async find(options: SavedObjectsFindOptions) { - let namespaces: string[]; - try { - namespaces = await this.getSearchableSpaces(options.namespaces); - } catch (err) { - if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { - // return empty response, since the user is unauthorized in any space, but we don't return forbidden errors for `find` operations - return SavedObjectsUtils.createEmptyFindResponse(options); - } - throw err; - } - if (namespaces.length === 0) { - // return empty response, since the user is unauthorized in this space (or these spaces), but we don't return forbidden errors for `find` operations - return SavedObjectsUtils.createEmptyFindResponse(options); - } - - return await this.client.find({ - ...options, - type: (options.type ? coerceToArray(options.type) : this.types).filter( - (type) => type !== 'space' - ), - namespaces, - }); - } - - async bulkGet( - objects: SavedObjectsBulkGetObject[] = [], - options: SavedObjectsBaseOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - - let availableSpacesPromise: Promise | undefined; - const getAvailableSpaces = async () => { - if (!availableSpacesPromise) { - availableSpacesPromise = this.getSearchableSpaces([ALL_SPACES_ID]).catch((err) => { - if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { - return []; // the user doesn't have access to any spaces - } else { - throw err; - } - }); - } - return availableSpacesPromise; - }; - - const expectedResults = await Promise.all( - objects.map>>(async (object) => { - const { namespaces, type } = object; - if (namespaces?.includes(ALL_SPACES_ID)) { - // If searching for an isolated object in all spaces, we may need to return a 400 error for consistency with the validation at the - // repository level. This is needed if there is only one space available *and* the user is authorized to access the object in that - // space; in that case, we don't want to unintentionally bypass the repository's validation by deconstructing the '*' identifier - // into all available spaces. - const tag = - !this.typeRegistry.isNamespaceAgnostic(type) && !this.typeRegistry.isShareable(type) - ? 'Left' - : 'Right'; - return { tag, value: { ...object, namespaces: await getAvailableSpaces() } }; - } - return { tag: 'Right', value: object }; - }) - ); - - const objectsToGet = expectedResults.map(({ value }) => value); - const { saved_objects: responseObjects } = objectsToGet.length - ? await this.client.bulkGet(objectsToGet, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }) - : { saved_objects: [] }; - return { - saved_objects: expectedResults.map((expectedResult, i) => { - const actualResult = responseObjects[i]; - if (isLeft(expectedResult)) { - const { type, id } = expectedResult.value; - return { - type, - id, - error: SavedObjectsErrorHelpers.createBadRequestError( - '"namespaces" can only specify a single space when used with space-isolated types' - ).output.payload, - } as unknown as SavedObject; - } - return actualResult; - }), - }; - } - - async get(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.get(type, id, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async bulkResolve( - objects: SavedObjectsBulkResolveObject[], - options: SavedObjectsBaseOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.bulkResolve(objects, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async resolve(type: string, id: string, options: SavedObjectsBaseOptions = {}) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.resolve(type, id, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async update( - type: string, - id: string, - attributes: Partial, - options: SavedObjectsUpdateOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - - return await this.client.update(type, id, attributes, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async bulkUpdate( - objects: Array> = [], - options: SavedObjectsBaseOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - return await this.client.bulkUpdate(objects, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async removeReferencesTo( - type: string, - id: string, - options: SavedObjectsRemoveReferencesToOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - return await this.client.removeReferencesTo(type, id, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async collectMultiNamespaceReferences( - objects: SavedObjectsCollectMultiNamespaceReferencesObject[], - options: SavedObjectsCollectMultiNamespaceReferencesOptions = {} - ): Promise { - throwErrorIfNamespaceSpecified(options); - return await this.client.collectMultiNamespaceReferences(objects, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async updateObjectsSpaces( - objects: SavedObjectsUpdateObjectsSpacesObject[], - spacesToAdd: string[], - spacesToRemove: string[], - options: SavedObjectsUpdateObjectsSpacesOptions = {} - ) { - throwErrorIfNamespaceSpecified(options); - return await this.client.updateObjectsSpaces(objects, spacesToAdd, spacesToRemove, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - async openPointInTimeForType( - type: string | string[], - options: SavedObjectsOpenPointInTimeOptions = {} - ) { - let namespaces: string[]; - try { - namespaces = await this.getSearchableSpaces(options.namespaces); - } catch (err) { - if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { - // throw bad request since the user is unauthorized in any space - throw SavedObjectsErrorHelpers.createBadRequestError(); - } - throw err; - } - if (namespaces.length === 0) { - // throw bad request if no valid spaces were found. - throw SavedObjectsErrorHelpers.createBadRequestError(); - } - - return await this.client.openPointInTimeForType(type, { - ...options, - namespaces, - }); - } - - async closePointInTime(id: string, options: SavedObjectsClosePointInTimeOptions = {}) { - throwErrorIfNamespaceSpecified(options); - return await this.client.closePointInTime(id, { - ...options, - namespace: spaceIdToNamespace(this.spaceId), - }); - } - - createPointInTimeFinder( - findOptions: SavedObjectsCreatePointInTimeFinderOptions, - dependencies?: SavedObjectsCreatePointInTimeFinderDependencies - ) { - throwErrorIfNamespaceSpecified(findOptions); - // We don't need to handle namespaces here, because `createPointInTimeFinder` - // is simply a helper that calls `find`, `openPointInTimeForType`, and - // `closePointInTime` internally, so namespaces will already be handled - // in those methods. - return this.client.createPointInTimeFinder(findOptions, { - client: this, - // Include dependencies last so that subsequent SO client wrappers have their settings applied. - ...dependencies, - }); - } - - private async getSearchableSpaces(namespaces?: string[]): Promise { - if (namespaces) { - const availableSpaces = await this.spacesClient.getAll({ purpose: 'findSavedObjects' }); - if (namespaces.includes(ALL_SPACES_ID)) { - return availableSpaces.map((space) => space.id); - } else { - return namespaces.filter((namespace) => - availableSpaces.some((space) => space.id === namespace) - ); - } - } else { - return [this.spaceId]; - } - } -} diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts index e69265eb3f054a..3f68a76635f00b 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts @@ -65,7 +65,7 @@ export function defineRoutes( const savedObjectsWithAlerts = await savedObjects.getScopedClient(req, { // Exclude the security and spaces wrappers to get around the safeguards those have in place to prevent // us from doing what we want to do - brute force replace the ApiKey - excludedWrappers: ['security', 'spaces'], + excludedExtensions: ['security', 'spaces'], includedHiddenTypes: ['alert'], }); From 06ca3e6ee6fd772662a8440a724a2aed58b88053 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Wed, 12 Oct 2022 12:43:17 -0400 Subject: [PATCH 04/47] Removed unnecessary wrapper tests. --- .../server/saved_objects/index.test.ts | 39 ------------------- x-pack/plugins/spaces/server/plugin.test.ts | 26 +------------ 2 files changed, 1 insertion(+), 64 deletions(-) diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts index b93141a3ad9898..dc2afe6e419570 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.test.ts @@ -12,8 +12,6 @@ import type { } from '@kbn/core/server'; import { coreMock, - httpServerMock, - savedObjectsClientMock, savedObjectsRepositoryMock, savedObjectsTypeRegistryMock, } from '@kbn/core/server/mocks'; @@ -23,7 +21,6 @@ import type { ClientInstanciator } from '.'; import { setupSavedObjects } from '.'; import type { EncryptedSavedObjectsService } from '../crypto'; import { encryptedSavedObjectsServiceMock } from '../crypto/index.mock'; -import { EncryptedSavedObjectsClientWrapper } from './encrypted_saved_objects_client_wrapper'; describe('#setupSavedObjects', () => { let setupContract: ClientInstanciator; @@ -55,42 +52,6 @@ describe('#setupSavedObjects', () => { }); }); - it('properly registers client wrapper factory', () => { - expect(coreSetupMock.savedObjects.addClientWrapper).toHaveBeenCalledTimes(1); - expect(coreSetupMock.savedObjects.addClientWrapper).toHaveBeenCalledWith( - Number.MAX_SAFE_INTEGER, - 'encryptedSavedObjects', - expect.any(Function) - ); - - const [[, , clientFactory]] = coreSetupMock.savedObjects.addClientWrapper.mock.calls; - expect( - clientFactory({ - client: savedObjectsClientMock.create(), - typeRegistry: savedObjectsTypeRegistryMock.create(), - request: httpServerMock.createKibanaRequest(), - }) - ).toBeInstanceOf(EncryptedSavedObjectsClientWrapper); - }); - - it('properly registers client wrapper factory with', () => { - expect(coreSetupMock.savedObjects.addClientWrapper).toHaveBeenCalledTimes(1); - expect(coreSetupMock.savedObjects.addClientWrapper).toHaveBeenCalledWith( - Number.MAX_SAFE_INTEGER, - 'encryptedSavedObjects', - expect.any(Function) - ); - - const [[, , clientFactory]] = coreSetupMock.savedObjects.addClientWrapper.mock.calls; - expect( - clientFactory({ - client: savedObjectsClientMock.create(), - typeRegistry: savedObjectsTypeRegistryMock.create(), - request: httpServerMock.createKibanaRequest(), - }) - ).toBeInstanceOf(EncryptedSavedObjectsClientWrapper); - }); - describe('#setupContract', () => { it('includes hiddenTypes when specified', async () => { await setupContract({ includedHiddenTypes: ['hiddenType'] }); diff --git a/x-pack/plugins/spaces/server/plugin.test.ts b/x-pack/plugins/spaces/server/plugin.test.ts index 024ae1e60100a8..29f1186bd7c9ac 100644 --- a/x-pack/plugins/spaces/server/plugin.test.ts +++ b/x-pack/plugins/spaces/server/plugin.test.ts @@ -39,6 +39,7 @@ describe('Spaces plugin', () => { `); }); + // Joe removed this test, but we're not sure why... it('registers the capabilities provider and switcher', () => { const initializerContext = coreMock.createPluginInitializerContext({}); const core = coreMock.createSetup() as CoreSetup; @@ -67,31 +68,6 @@ describe('Spaces plugin', () => { expect(usageCollection.getCollectorByType('spaces')).toBeDefined(); }); - - it('registers the "space" saved object type and client wrapper', () => { - const initializerContext = coreMock.createPluginInitializerContext({}); - const core = coreMock.createSetup() as CoreSetup; - const features = featuresPluginMock.createSetup(); - const licensing = licensingMock.createSetup(); - - const plugin = new SpacesPlugin(initializerContext); - - plugin.setup(core, { features, licensing }); - - expect(core.savedObjects.registerType).toHaveBeenCalledWith({ - name: 'space', - namespaceType: 'agnostic', - hidden: true, - mappings: expect.any(Object), - migrations: expect.any(Object), - }); - - expect(core.savedObjects.addClientWrapper).toHaveBeenCalledWith( - Number.MIN_SAFE_INTEGER, - 'spaces', - expect.any(Function) - ); - }); }); describe('#start', () => { From 72138ee254a55e4d5aafc46f4507cafa0797749c Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Wed, 12 Oct 2022 13:50:32 -0400 Subject: [PATCH 05/47] Removes wrapper references from secure spaces client. Moves extension mocks to API server mocks package. --- .../src/lib/internal_bulk_resolve.test.ts | 2 +- .../index.ts | 1 + .../src/index.ts | 1 + .../src/saved_objects_extensions.mock.ts} | 2 +- .../server/audit/audit_events.test.ts | 32 ++--- x-pack/plugins/security/server/audit/index.ts | 1 - ...secure_spaces_client_wrapper.test.mocks.ts | 17 --- .../secure_spaces_client_wrapper.test.ts | 125 ++++++++-------- .../spaces/secure_spaces_client_wrapper.ts | 134 +++++++----------- 9 files changed, 134 insertions(+), 181 deletions(-) rename packages/core/saved-objects/{core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts => core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts} (97%) delete mode 100644 x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.ts diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index b22cd68aac70cd..1cdb4e31937877 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -32,7 +32,7 @@ import { ISavedObjectsSecurityExtension, ISavedObjectTypeRegistry, } from '@kbn/core-saved-objects-server'; -import { extensionsMock } from './extensions.test.mock'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; import { authMap, enforceError, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/index.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/index.ts index b558e2d93d6d97..9ba340e600f6c4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/index.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/index.ts @@ -10,4 +10,5 @@ export { savedObjectsClientMock, savedObjectsRepositoryMock, savedObjectsClientProviderMock, + savedObjectsExtensionsMock, } from './src'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/index.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/index.ts index 7dad32d20a57f0..8436953f255586 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/index.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/index.ts @@ -9,3 +9,4 @@ export { savedObjectsClientMock } from './saved_objects_client.mock'; export { savedObjectsRepositoryMock } from './repository.mock'; export { savedObjectsClientProviderMock } from './scoped_client_provider.mock'; +export { savedObjectsExtensionsMock } from './saved_objects_extensions.mock'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts similarity index 97% rename from packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts rename to packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts index 9b411d0b4ef33e..f4308ee6254c7c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/extensions.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts @@ -37,7 +37,7 @@ const create = (): jest.Mocked => ({ spacesExtension: createSpacesExtension(), }); -export const extensionsMock = { +export const savedObjectsExtensionsMock = { create, createEncryptionExtension, createSecurityExtension, diff --git a/x-pack/plugins/security/server/audit/audit_events.test.ts b/x-pack/plugins/security/server/audit/audit_events.test.ts index 1f9ab461e0b003..fb246569fcf816 100644 --- a/x-pack/plugins/security/server/audit/audit_events.test.ts +++ b/x-pack/plugins/security/server/audit/audit_events.test.ts @@ -7,13 +7,13 @@ import { URL } from 'url'; +import { AuditAction } from '@kbn/core-saved-objects-server'; import { httpServerMock } from '@kbn/core/server/mocks'; import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; import { AuthenticationResult } from '../authentication'; import { httpRequestEvent, - SavedObjectAction, savedObjectEvent, sessionCleanupEvent, SpaceAuditAction, @@ -26,7 +26,7 @@ describe('#savedObjectEvent', () => { test('creates event with `unknown` outcome', () => { expect( savedObjectEvent({ - action: SavedObjectAction.CREATE, + action: AuditAction.CREATE, outcome: 'unknown', savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) @@ -59,7 +59,7 @@ describe('#savedObjectEvent', () => { test('creates event with `success` outcome', () => { expect( savedObjectEvent({ - action: SavedObjectAction.CREATE, + action: AuditAction.CREATE, savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) ).toMatchInlineSnapshot(` @@ -91,7 +91,7 @@ describe('#savedObjectEvent', () => { test('creates event with `failure` outcome', () => { expect( savedObjectEvent({ - action: SavedObjectAction.CREATE, + action: AuditAction.CREATE, savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, error: new Error('ERROR_MESSAGE'), }) @@ -127,19 +127,19 @@ describe('#savedObjectEvent', () => { test('does create event for read access of saved objects', () => { expect( savedObjectEvent({ - action: SavedObjectAction.GET, + action: AuditAction.GET, savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) ).not.toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.RESOLVE, + action: AuditAction.RESOLVE, savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) ).not.toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.FIND, + action: AuditAction.FIND, savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) ).not.toBeUndefined(); @@ -148,37 +148,37 @@ describe('#savedObjectEvent', () => { test('does not create event for read access of config or telemetry objects', () => { expect( savedObjectEvent({ - action: SavedObjectAction.GET, + action: AuditAction.GET, savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' }, }) ).toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.GET, + action: AuditAction.GET, savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' }, }) ).toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.RESOLVE, + action: AuditAction.RESOLVE, savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' }, }) ).toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.RESOLVE, + action: AuditAction.RESOLVE, savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' }, }) ).toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.FIND, + action: AuditAction.FIND, savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' }, }) ).toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.FIND, + action: AuditAction.FIND, savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' }, }) ).toBeUndefined(); @@ -187,13 +187,13 @@ describe('#savedObjectEvent', () => { test('does create event for write access of config or telemetry objects', () => { expect( savedObjectEvent({ - action: SavedObjectAction.UPDATE, + action: AuditAction.UPDATE, savedObject: { type: 'config', id: 'SAVED_OBJECT_ID' }, }) ).not.toBeUndefined(); expect( savedObjectEvent({ - action: SavedObjectAction.UPDATE, + action: AuditAction.UPDATE, savedObject: { type: 'telemetry', id: 'SAVED_OBJECT_ID' }, }) ).not.toBeUndefined(); @@ -202,7 +202,7 @@ describe('#savedObjectEvent', () => { test('creates event with `success` outcome for `REMOVE_REFERENCES` action', () => { expect( savedObjectEvent({ - action: SavedObjectAction.REMOVE_REFERENCES, + action: AuditAction.REMOVE_REFERENCES, savedObject: { type: 'dashboard', id: 'SAVED_OBJECT_ID' }, }) ).toMatchInlineSnapshot(` diff --git a/x-pack/plugins/security/server/audit/index.ts b/x-pack/plugins/security/server/audit/index.ts index 0bd8492b796706..c3cb5f890ce0c3 100644 --- a/x-pack/plugins/security/server/audit/index.ts +++ b/x-pack/plugins/security/server/audit/index.ts @@ -16,6 +16,5 @@ export { httpRequestEvent, savedObjectEvent, spaceAuditEvent, - SavedObjectAction, SpaceAuditAction, } from './audit_events'; diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.ts deleted file mode 100644 index 02bd9971f28b8f..00000000000000 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.mocks.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { ensureAuthorized } from '../saved_objects'; - -export const mockEnsureAuthorized = jest.fn() as jest.MockedFunction; - -jest.mock('../saved_objects', () => { - return { - ...jest.requireActual('../saved_objects'), - ensureAuthorized: mockEnsureAuthorized, - }; -}); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index 2b65eff88d36fd..d3b89e2eafbc74 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -5,8 +5,9 @@ * 2.0. */ -import { mockEnsureAuthorized } from './secure_spaces_client_wrapper.test.mocks'; - +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import type { ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; +import { AuditAction } from '@kbn/core-saved-objects-server'; import type { EcsEventOutcome, SavedObjectsFindResponse } from '@kbn/core/server'; import { SavedObjectsErrorHelpers } from '@kbn/core/server'; import { httpServerMock } from '@kbn/core/server/mocks'; @@ -15,7 +16,7 @@ import { spacesClientMock } from '@kbn/spaces-plugin/server/mocks'; import { deepFreeze } from '@kbn/std'; import type { AuditEvent, AuditLogger } from '../audit'; -import { SavedObjectAction, SpaceAuditAction } from '../audit'; +import { SpaceAuditAction } from '../audit'; import { auditLoggerMock } from '../audit/mocks'; import type { AuthorizationServiceSetup, @@ -104,12 +105,17 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { // other errors exist but are not needed for these test cases } as unknown as jest.Mocked; + const securityExtension = securityEnabled + ? (savedObjectsExtensionsMock.create() + .securityExtension as jest.Mocked) + : undefined; const wrapper = new SecureSpacesClientWrapper( baseClient, request, authorization, auditLogger, - errors + errors, + securityExtension ); return { authorization, @@ -118,6 +124,7 @@ const setup = ({ securityEnabled = false }: Opts = {}) => { baseClient, auditLogger, forbiddenError, + securityExtension, }; }; @@ -150,10 +157,6 @@ const expectAuditEvent = ( ); }; -beforeEach(() => { - mockEnsureAuthorized.mockReset(); -}); - describe('SecureSpacesClientWrapper', () => { describe('#getAll', () => { const savedObjects = [ @@ -665,9 +668,9 @@ describe('SecureSpacesClientWrapper', () => { it('deletes the space with all saved objects when authorized', async () => { const username = 'some_user'; - const { wrapper, baseClient, authorization, auditLogger, request } = setup({ - securityEnabled: true, - }); + const { wrapper, baseClient, authorization, auditLogger, request, securityExtension } = setup( + { securityEnabled: true } + ); const checkPrivileges = jest.fn().mockResolvedValue({ username, @@ -694,13 +697,17 @@ describe('SecureSpacesClientWrapper', () => { type: 'space', id: space.id, }); - expectAuditEvent(auditLogger, SavedObjectAction.DELETE, 'unknown', { - type: 'dashboard', - id: '2', + expect(securityExtension!.addAuditEvent).toHaveBeenCalledTimes(2); + expect(securityExtension!.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.DELETE, + outcome: 'unknown', + savedObject: { type: 'dashboard', id: '2' }, }); - expectAuditEvent(auditLogger, SavedObjectAction.UPDATE_OBJECTS_SPACES, 'unknown', { - type: 'dashboard', - id: '3', + expect(securityExtension!.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE_OBJECTS_SPACES, + outcome: 'unknown', + savedObject: { type: 'dashboard', id: '3' }, + deleteFromSpaces: [space.id], }); }); }); @@ -710,39 +717,41 @@ describe('SecureSpacesClientWrapper', () => { const alias2 = { targetSpace: 'space-2', targetType: 'type-2', sourceId: 'id' }; function expectAuditEvents( - auditLogger: AuditLogger, + securityExtension: jest.Mocked, aliases: LegacyUrlAliasTarget[], - action: EcsEventOutcome + { error }: { error: boolean } ) { aliases.forEach((alias) => { - expectAuditEvent(auditLogger, SavedObjectAction.UPDATE, action, { - type: LEGACY_URL_ALIAS_TYPE, - id: getAliasId(alias), + expect(securityExtension!.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.UPDATE, + savedObject: { type: LEGACY_URL_ALIAS_TYPE, id: getAliasId(alias) }, + ...(error ? { error: expect.anything() } : { outcome: 'unknown' }), }); }); } - function expectAuthorizationCheck(targetTypes: string[], targetSpaces: string[]) { - expect(mockEnsureAuthorized).toHaveBeenCalledTimes(1); - expect(mockEnsureAuthorized).toHaveBeenCalledWith( - expect.any(Object), // dependencies - targetTypes, // unique types of the alias targets - ['bulk_update'], // actions - targetSpaces, // unique spaces of the alias targets - { requireFullAuthorization: false } - ); + function expectAuthorizationCheck( + securityExtension: jest.Mocked, + targetTypes: string[], + targetSpaces: string[] + ) { + expect(securityExtension!.checkAuthorization).toHaveBeenCalledTimes(1); + expect(securityExtension!.checkAuthorization).toHaveBeenCalledWith({ + types: new Set(targetTypes), // unique types of the alias targets + spaces: new Set(targetSpaces), // unique spaces of the alias targets + actions: ['bulk_update'], + }); } describe('when security is not enabled', () => { const securityEnabled = false; it('delegates to base client without checking authorization', async () => { - const { wrapper, baseClient, auditLogger } = setup({ securityEnabled }); + const { wrapper, baseClient, securityExtension } = setup({ securityEnabled }); const aliases = [alias1]; await wrapper.disableLegacyUrlAliases(aliases); - expect(mockEnsureAuthorized).not.toHaveBeenCalled(); - expectAuditEvents(auditLogger, aliases, 'unknown'); + expect(securityExtension).toBeUndefined(); expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledTimes(1); expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledWith(aliases); }); @@ -751,49 +760,41 @@ describe('SecureSpacesClientWrapper', () => { describe('when security is enabled', () => { const securityEnabled = true; - it('re-throws the error if the authorization check fails', async () => { - const error = new Error('Oh no!'); - mockEnsureAuthorized.mockRejectedValue(error); - const { wrapper, baseClient, auditLogger } = setup({ securityEnabled }); - const aliases = [alias1, alias2]; - await expect(() => wrapper.disableLegacyUrlAliases(aliases)).rejects.toThrow(error); - - expectAuthorizationCheck(['type-1', 'type-2'], ['space-1', 'space-2']); - expectAuditEvents(auditLogger, aliases, 'failure'); - expect(baseClient.disableLegacyUrlAliases).not.toHaveBeenCalled(); - }); - it('throws a forbidden error when unauthorized', async () => { - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('type-1', { bulk_update: { authorizedSpaces: ['space-1'] } }) - .set('type-2', { bulk_update: { authorizedSpaces: ['space-1'] } }), // the user is not authorized to bulkUpdate type-2 in space-2, so this will throw a forbidden error + const { wrapper, baseClient, forbiddenError, securityExtension } = setup({ + securityEnabled, + }); + securityExtension!.checkAuthorization.mockResolvedValue({ + // These values don't actually matter, the call to enforceAuthorization matters + status: 'unauthorized', + typeMap: new Map(), + }); + securityExtension!.enforceAuthorization.mockImplementation(() => { + throw new Error('Oh no!'); }); - const { wrapper, baseClient, auditLogger, forbiddenError } = setup({ securityEnabled }); const aliases = [alias1, alias2]; await expect(() => wrapper.disableLegacyUrlAliases(aliases)).rejects.toThrow( forbiddenError ); - expectAuthorizationCheck(['type-1', 'type-2'], ['space-1', 'space-2']); - expectAuditEvents(auditLogger, aliases, 'failure'); + expectAuthorizationCheck(securityExtension!, ['type-1', 'type-2'], ['space-1', 'space-2']); + expectAuditEvents(securityExtension!, aliases, { error: true }); expect(baseClient.disableLegacyUrlAliases).not.toHaveBeenCalled(); }); it('updates the legacy URL aliases when authorized', async () => { - mockEnsureAuthorized.mockResolvedValue({ - status: 'partially_authorized', - typeActionMap: new Map() - .set('type-1', { bulk_update: { authorizedSpaces: ['space-1'] } }) - .set('type-2', { bulk_update: { authorizedSpaces: ['space-2'] } }), + const { wrapper, baseClient, securityExtension } = setup({ securityEnabled }); + securityExtension!.checkAuthorization.mockResolvedValue({ + // These values don't actually matter, the call to enforceAuthorization matters + status: 'fully_authorized', + typeMap: new Map(), }); - const { wrapper, baseClient, auditLogger } = setup({ securityEnabled }); + // enforceAuthorization does *not* throw an error by default const aliases = [alias1, alias2]; await wrapper.disableLegacyUrlAliases(aliases); - expectAuthorizationCheck(['type-1', 'type-2'], ['space-1', 'space-2']); - expectAuditEvents(auditLogger, aliases, 'unknown'); + expectAuthorizationCheck(securityExtension!, ['type-1', 'type-2'], ['space-1', 'space-2']); + expectAuditEvents(securityExtension!, aliases, { error: false }); expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledTimes(1); expect(baseClient.disableLegacyUrlAliases).toHaveBeenCalledWith(aliases); }); diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index c33686a0f8cfa6..1d92a31736660a 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -7,7 +7,9 @@ import Boom from '@hapi/boom'; -import type { KibanaRequest, SavedObjectsErrorHelpers } from '@kbn/core/server'; +import type { ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; +import { AuditAction } from '@kbn/core-saved-objects-server'; +import type { KibanaRequest, SavedObjectsClient } from '@kbn/core/server'; import type { GetAllSpacesOptions, GetAllSpacesPurpose, @@ -19,11 +21,9 @@ import type { import { ALL_SPACES_ID } from '../../common/constants'; import type { AuditLogger } from '../audit'; -import { SavedObjectAction, savedObjectEvent, SpaceAuditAction, spaceAuditEvent } from '../audit'; +import { SpaceAuditAction, spaceAuditEvent } from '../audit'; import type { AuthorizationServiceSetup } from '../authorization'; import type { SecurityPluginSetup } from '../plugin'; -import type { EnsureAuthorizedDependencies, EnsureAuthorizedOptions } from '../saved_objects'; -import { ensureAuthorized, isAuthorizedForObjectInAllSpaces } from '../saved_objects'; const PURPOSE_PRIVILEGE_MAP: Record< GetAllSpacesPurpose, @@ -52,7 +52,8 @@ export class SecureSpacesClientWrapper implements ISpacesClient { private readonly request: KibanaRequest, private readonly authorization: AuthorizationServiceSetup, private readonly auditLogger: AuditLogger, - private readonly errors: typeof SavedObjectsErrorHelpers + private readonly errors: SavedObjectsClient['errors'], + private readonly securityExtension: ISavedObjectsSecurityExtension | undefined ) { this.useRbac = this.authorization.mode.useRbacForRequest(this.request); } @@ -271,7 +272,9 @@ export class SecureSpacesClientWrapper implements ISpacesClient { } // Fetch saved objects to be removed for audit logging - if (this.auditLogger.enabled) { + // If RBAC is enabled, the securityExtension should definitely be defined, but we check just in case + const securityExtension = this.securityExtension; + if (this.auditLogger.enabled && securityExtension !== undefined) { const finder = this.spacesClient.createSavedObjectFinder(id); try { for await (const response of finder.find()) { @@ -282,16 +285,12 @@ export class SecureSpacesClientWrapper implements ISpacesClient { // This object exists in All Spaces and its `namespaces` field isn't going to change; there's nothing to audit return; } - this.auditLogger.log( - savedObjectEvent({ - action: isOnlySpace - ? SavedObjectAction.DELETE - : SavedObjectAction.UPDATE_OBJECTS_SPACES, - outcome: 'unknown', - savedObject: { type: savedObject.type, id: savedObject.id }, - deleteFromSpaces: [id], - }) - ); + securityExtension.addAuditEvent({ + action: isOnlySpace ? AuditAction.DELETE : AuditAction.UPDATE_OBJECTS_SPACES, + outcome: 'unknown', + savedObject: { type: savedObject.type, id: savedObject.id }, + ...(!isOnlySpace && { deleteFromSpaces: [id] }), + }); }); } } finally { @@ -311,83 +310,52 @@ export class SecureSpacesClientWrapper implements ISpacesClient { } public async disableLegacyUrlAliases(aliases: LegacyUrlAliasTarget[]) { - if (this.useRbac) { - try { - const [uniqueSpaces, uniqueTypes, typesAndSpacesMap] = aliases.reduce( - ([spaces, types, typesAndSpaces], { targetSpace, targetType }) => { - const spacesForType = typesAndSpaces.get(targetType) ?? new Set(); - return [ - spaces.add(targetSpace), - types.add(targetType), - typesAndSpaces.set(targetType, spacesForType.add(targetSpace)), - ]; - }, - [new Set(), new Set(), new Map>()] - ); + if (this.securityExtension) { + const [uniqueSpaces, typesAndSpaces] = aliases.reduce( + ([spaces, typesAndSpacesMap], { targetSpace, targetType }) => { + const spacesForType = typesAndSpacesMap.get(targetType) ?? new Set(); + return [ + spaces.add(targetSpace), + typesAndSpacesMap.set(targetType, spacesForType.add(targetSpace)), + ]; + }, + [new Set(), new Map>()] + ); - const action = 'bulk_update'; - const { typeActionMap } = await this.ensureAuthorizedForSavedObjects( - Array.from(uniqueTypes), - [action], - Array.from(uniqueSpaces), - { requireFullAuthorization: false } + const { typeMap } = await this.securityExtension.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: uniqueSpaces, + actions: ['bulk_update'], + }); + let error: Error | undefined; + try { + await this.securityExtension.enforceAuthorization({ + typesAndSpaces, + action: 'bulk_update', + typeMap, + }); + } catch (err) { + error = this.errors.decorateForbiddenError( + new Error(`Unable to disable aliases: ${err.message}`) ); - const unauthorizedTypes = new Set(); - for (const type of uniqueTypes) { - const spaces = Array.from(typesAndSpacesMap.get(type)!); - if (!isAuthorizedForObjectInAllSpaces(type, action, typeActionMap, spaces)) { - unauthorizedTypes.add(type); - } - } - if (unauthorizedTypes.size > 0) { - const targetTypes = Array.from(unauthorizedTypes).sort().join(','); - const msg = `Unable to disable aliases for ${targetTypes}`; - throw this.errors.decorateForbiddenError(new Error(msg)); - } - } catch (error) { - aliases.forEach((alias) => { - const id = getAliasId(alias); - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE, - savedObject: { type: LEGACY_URL_ALIAS_TYPE, id }, - error, - }) - ); + } + for (const alias of aliases) { + const id = getAliasId(alias); + this.securityExtension.addAuditEvent({ + action: AuditAction.UPDATE, + savedObject: { type: LEGACY_URL_ALIAS_TYPE, id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the update operation has not occurred yet }); + } + if (error) { throw error; } } - aliases.forEach((alias) => { - const id = getAliasId(alias); - this.auditLogger.log( - savedObjectEvent({ - action: SavedObjectAction.UPDATE, - outcome: 'unknown', - savedObject: { type: LEGACY_URL_ALIAS_TYPE, id }, - }) - ); - }); - return this.spacesClient.disableLegacyUrlAliases(aliases); } - private async ensureAuthorizedForSavedObjects( - types: string[], - actions: T[], - namespaces: string[], - options?: EnsureAuthorizedOptions - ) { - const ensureAuthorizedDependencies: EnsureAuthorizedDependencies = { - actions: this.authorization.actions, - errors: this.errors, - checkSavedObjectsPrivilegesAsCurrentUser: - this.authorization.checkSavedObjectsPrivilegesWithRequest(this.request), - }; - return ensureAuthorized(ensureAuthorizedDependencies, types, actions, namespaces, options); - } - private async ensureAuthorizedGlobally(action: string, forbiddenMessage: string) { const checkPrivileges = this.authorization.checkPrivilegesWithRequest(this.request); const { hasAllRequested } = await checkPrivileges.globally({ kibana: action }); From 1bcbbdc17e279955c0f5275eb70fc44c320dbf27 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Wed, 12 Oct 2022 14:13:55 -0400 Subject: [PATCH 06/47] Fix to setup spaces client - now references security extension --- .../server/spaces/setup_spaces_client.ts | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/security/server/spaces/setup_spaces_client.ts b/x-pack/plugins/security/server/spaces/setup_spaces_client.ts index a13c7f6434365d..b2871c8bc7ef17 100644 --- a/x-pack/plugins/security/server/spaces/setup_spaces_client.ts +++ b/x-pack/plugins/security/server/spaces/setup_spaces_client.ts @@ -10,6 +10,7 @@ import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server'; import type { AuditServiceSetup } from '../audit'; import type { AuthorizationServiceSetup } from '../authorization'; +import { SavedObjectsSecurityExtension } from '../saved_objects'; import { SecureSpacesClientWrapper } from './secure_spaces_client_wrapper'; interface Deps { @@ -31,14 +32,22 @@ export const setupSpacesClient = ({ audit, authz, spaces }: Deps) => { return savedObjectsStart.createScopedRepository(request, ['space']); }); - spacesClient.registerClientWrapper( - (request, baseClient) => - new SecureSpacesClientWrapper( - baseClient, - request, - authz, - audit.asScoped(request), - SavedObjectsClient.errors - ) - ); + spacesClient.registerClientWrapper((request, baseClient) => { + const securityExtension = authz.mode.useRbacForRequest(request) + ? new SavedObjectsSecurityExtension({ + actions: authz.actions, + auditLogger: audit.asScoped(request), + checkPrivileges: authz.checkSavedObjectsPrivilegesWithRequest(request), + errors: SavedObjectsClient.errors, + }) + : undefined; + return new SecureSpacesClientWrapper( + baseClient, + request, + authz, + audit.asScoped(request), + SavedObjectsClient.errors, + securityExtension + ); + }); }; From 1f277e66e63e4d5224b95ed2c15d30c2bcfd8114 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Wed, 12 Oct 2022 16:13:58 -0400 Subject: [PATCH 07/47] Resolves SO extension mock references. --- .../src/lib/collect_multi_namespace_references.test.ts | 5 +++-- .../src/lib/internal_bulk_resolve.test.ts | 6 +++--- .../src/lib/repository.encryption_extension.test.ts | 4 ++-- .../src/lib/repository.security_extension.test.ts | 4 ++-- .../src/lib/repository.spaces_extension.test.ts | 8 ++++---- .../src/lib/scoped_client_provider.test.ts | 8 ++++---- .../src/lib/update_objects_spaces.test.ts | 8 ++++---- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 5c1b7849b019d4..40eec116ff3657 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -27,7 +27,7 @@ import { import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; -import { extensionsMock } from './extensions.test.mock'; + import { authMap, enforceError, @@ -39,6 +39,7 @@ import { setupEnforceSuccess, setupRedactPassthrough, } from './repository.common.test'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; const SPACES = ['default', 'another-space']; const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 }; @@ -465,7 +466,7 @@ describe('collectMultiNamespaceReferences', () => { }); describe('with security enabled', () => { - const mockSecurityExt = extensionsMock.createSecurityExtension(); + const mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); const obj1 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-1' }; const obj2 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-2' }; const obj3 = { type: MULTI_NAMESPACE_OBJ_TYPE_1, id: 'id-3' }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index 1cdb4e31937877..196025bcf3fe24 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -32,7 +32,6 @@ import { ISavedObjectsSecurityExtension, ISavedObjectTypeRegistry, } from '@kbn/core-saved-objects-server'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; import { authMap, enforceError, @@ -44,6 +43,7 @@ import { setupEnforceSuccess, setupRedactPassthrough, } from './repository.common.test'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 }; const OBJ_TYPE = 'obj-type'; @@ -398,7 +398,7 @@ describe('internalBulkResolve', () => { { type: OBJ_TYPE, id: '11' }, // non encryptable type { type: ENCRYPTED_TYPE, id: '12' }, // encryptable type ]; - const mockEncryptionExt = extensionsMock.createEncryptionExtension(); + const mockEncryptionExt = savedObjectsExtensionsMock.createEncryptionExtension(); const params = setup(objects, { namespace }, { encryptionExt: mockEncryptionExt }); mockBulkResults( // No alias matches @@ -448,7 +448,7 @@ describe('internalBulkResolve', () => { } as SavedObject; }); - mockSecurityExt = extensionsMock.createSecurityExtension(); + mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); params = setup(objects, { namespace }, { securityExt: mockSecurityExt }); mockBulkResults( diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts index d37bf6ff8ed605..4d7fd552af6d57 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts @@ -44,7 +44,7 @@ import { TypeIdTuple, updateSuccess, } from './repository.common.test'; -import { extensionsMock } from './extensions.test.mock'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -117,7 +117,7 @@ describe('SavedObjectsRepository Encryption Extension', () => { serializer = createSpySerializer(registry); // create a mock saved objects encryption extension - mockEncryptionExt = extensionsMock.createEncryptionExtension(); + mockEncryptionExt = savedObjectsExtensionsMock.createEncryptionExtension(); mockGetCurrentTime.mockReturnValue(mockTimestamp); mockGetSearchDsl.mockClear(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index 4c8cfc2ece4056..b4e36ab0526464 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -27,7 +27,6 @@ import { SavedObjectsRawDocSource, } from '@kbn/core-saved-objects-server'; import { kibanaMigratorMock } from '../mocks'; -import { extensionsMock } from './extensions.test.mock'; import { createRegistry, createDocumentMigrator, @@ -64,6 +63,7 @@ import { bulkUpdateSuccess, expectUpdateResult, } from './repository.common.test'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -116,7 +116,7 @@ describe('SavedObjectsRepository Security Extension', () => { serializer = createSpySerializer(registry); // create a mock saved objects encryption extension - mockSecurityExt = extensionsMock.createSecurityExtension(); + mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); mockGetCurrentTime.mockReturnValue(mockTimestamp); mockGetSearchDsl.mockClear(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts index b44c0db9410461..58e4329fdad7af 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts @@ -33,7 +33,6 @@ import { } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { kibanaMigratorMock } from '../mocks'; -import { extensionsMock } from './extensions.test.mock'; import { createRegistry, createDocumentMigrator, @@ -56,6 +55,7 @@ import { setupCheckUnauthorized, generateIndexPatternSearchResults, } from './repository.common.test'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -121,7 +121,7 @@ describe('SavedObjectsRepository Spaces Extension', () => { serializer = createSpySerializer(registry); // create a mock saved objects spaces extension - mockSpacesExt = extensionsMock.createSpacesExtension(); + mockSpacesExt = savedObjectsExtensionsMock.createSpacesExtension(); mockGetCurrentTime.mockReturnValue(mockTimestamp); mockGetSearchDsl.mockClear(); @@ -812,8 +812,8 @@ describe('SavedObjectsRepository Spaces Extension', () => { // create a mock serializer "shim" so we can track function calls, but use the real serializer's implementation serializer = createSpySerializer(registry); // create a mock extensions - mockSpacesExt = extensionsMock.createSpacesExtension(); - mockSecurityExt = extensionsMock.createSecurityExtension(); + mockSpacesExt = savedObjectsExtensionsMock.createSpacesExtension(); + mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); mockGetCurrentTime.mockReturnValue(mockTimestamp); mockGetSearchDsl.mockClear(); repository = instantiateRepository(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts index eb72af9914d339..871b38fd89af80 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts @@ -19,8 +19,8 @@ import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID, } from '@kbn/core-saved-objects-server'; -import { extensionsMock } from './extensions.test.mock'; import { KibanaRequest } from '@kbn/core-http-server'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; /** * @internal only used for unit tests @@ -110,19 +110,19 @@ describe(`allows extensions to be excluded`, () => { const typeRegistry = typeRegistryMock.create(); const defaultClientFactoryMock = jest.fn().mockReturnValue(defaultClient); - const mockEncryptionExt = extensionsMock.createEncryptionExtension(); + const mockEncryptionExt = savedObjectsExtensionsMock.createEncryptionExtension(); const encryptionExtFactory: SavedObjectsEncryptionExtensionFactory = (params: { typeRegistry: ISavedObjectTypeRegistry; request: KibanaRequest; }) => mockEncryptionExt; - const mockSpacesExt = extensionsMock.createSpacesExtension(); + const mockSpacesExt = savedObjectsExtensionsMock.createSpacesExtension(); const spacesExtFactory: SavedObjectsSpacesExtensionFactory = (params: { typeRegistry: ISavedObjectTypeRegistry; request: KibanaRequest; }) => mockSpacesExt; - const mockSecurityExt = extensionsMock.createSecurityExtension(); + const mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); const securityExtFactory: SavedObjectsSecurityExtensionFactory = (params: { typeRegistry: ISavedObjectTypeRegistry; request: KibanaRequest; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index e1cc0d0fbbf20a..77974b9f71b881 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -26,7 +26,6 @@ import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import type { UpdateObjectsSpacesParams } from './update_objects_spaces'; import { updateObjectsSpaces } from './update_objects_spaces'; import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; -import { extensionsMock } from './extensions.test.mock'; import { authMap, checkAuthError, @@ -39,6 +38,7 @@ import { setupEnforceSuccess, setupRedactPassthrough, } from './repository.common.test'; +import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; type SetupParams = Partial< Pick @@ -659,7 +659,7 @@ describe('#updateObjectsSpaces', () => { const obj1 = { type: SHAREABLE_OBJ_TYPE, id: 'id-1' }; const objects = [obj1]; const spacesToAdd = ['foo-space']; - mockSecurityExt = extensionsMock.createSecurityExtension(); + mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); params = setup({ objects, spacesToAdd }, mockSecurityExt); mockMgetResults({ found: true, namespaces: [EXISTING_SPACE] }); // result for obj1 mockBulkResults({ error: false }); // result for obj1 @@ -752,7 +752,7 @@ describe('#updateObjectsSpaces', () => { const spacesToRemove = [EXISTING_SPACE]; beforeEach(() => { - mockSecurityExt = extensionsMock.createSecurityExtension(); + mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); params = setup({ objects, spacesToAdd, spacesToRemove }, mockSecurityExt); mockMgetResults( { found: true, namespaces: [ALL_NAMESPACES_STRING, otherSpace] }, // result for obj1 -- will not be changed @@ -835,7 +835,7 @@ describe('#updateObjectsSpaces', () => { const objects = [obj1, obj2, obj3, obj4]; const setupForAllSpaces = (spacesToAdd: string[], spacesToRemove: string[]) => { - mockSecurityExt = extensionsMock.createSecurityExtension(); + mockSecurityExt = savedObjectsExtensionsMock.createSecurityExtension(); params = setup({ objects, spacesToAdd, spacesToRemove }, mockSecurityExt); mockMgetResults( { found: true, namespaces: [ALL_NAMESPACES_STRING, otherSpace] }, // result for obj1 -- will not be changed From 4fff61811686a6a3dd97c85a04144ed3baee8932 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Wed, 12 Oct 2022 16:37:45 -0400 Subject: [PATCH 08/47] Fixes several jest tests to include expected calling parameters with internal options. --- .../telemetry_application_usage_collector.test.ts | 6 ++++-- .../saved_objects_management/server/lib/find_all.test.ts | 3 ++- .../server/services/tags/tags_client.test.ts | 3 ++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts index deea86bbdd18cb..3550d93b1edf97 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/application_usage/telemetry_application_usage_collector.test.ts @@ -84,12 +84,14 @@ describe('telemetry_application_usage', () => { expect(savedObjectClient.find).toHaveBeenCalledWith( expect.objectContaining({ type: SAVED_OBJECTS_TOTAL_TYPE, - }) + }), + undefined // internalOptions ); expect(savedObjectClient.find).toHaveBeenCalledWith( expect.objectContaining({ type: SAVED_OBJECTS_DAILY_TYPE, - }) + }), + undefined // internalOptions ); }); diff --git a/src/plugins/saved_objects_management/server/lib/find_all.test.ts b/src/plugins/saved_objects_management/server/lib/find_all.test.ts index 59c2efd36a8f47..13135ce41b06e4 100644 --- a/src/plugins/saved_objects_management/server/lib/find_all.test.ts +++ b/src/plugins/saved_objects_management/server/lib/find_all.test.ts @@ -62,7 +62,8 @@ describe('findAll', () => { expect(savedObjectsClient.find).toHaveBeenCalledWith( expect.objectContaining({ ...query, - }) + }), + undefined // internalOptions ); expect(results).toEqual([createObj(1), createObj(2)]); diff --git a/x-pack/plugins/saved_objects_tagging/server/services/tags/tags_client.test.ts b/x-pack/plugins/saved_objects_tagging/server/services/tags/tags_client.test.ts index 7743b109dbd61e..949e460030cf6d 100644 --- a/x-pack/plugins/saved_objects_tagging/server/services/tags/tags_client.test.ts +++ b/x-pack/plugins/saved_objects_tagging/server/services/tags/tags_client.test.ts @@ -218,7 +218,8 @@ describe('TagsClient', () => { expect.objectContaining({ type: 'tag', perPage: 1000, - }) + }), + undefined // internalOptions ); }); From c7c4d55a8cc83d1b7df2fa1676e2502a42a1a794 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Thu, 13 Oct 2022 13:02:17 -0400 Subject: [PATCH 09/47] Fixes several unit tests, cleans up unused code and comments. --- ...collect_multi_namespace_references.test.ts | 2 +- .../src/lib/repository.common.test.ts | 2 +- .../lib/repository.security_extension.test.ts | 77 ++++++++++++++----- .../lib/repository.spaces_extension.test.ts | 6 +- .../src/lib/repository.ts | 2 +- .../lib/repository_create_repository.test.ts | 2 +- .../src/saved_objects_repository.ts | 2 +- .../src/export/saved_objects_exporter.test.ts | 8 +- .../src/contracts.ts | 8 +- x-pack/test/functional/services/index.ts | 1 - 10 files changed, 75 insertions(+), 35 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 40eec116ff3657..874b381caea5c5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -494,7 +494,7 @@ describe('collectMultiNamespaceReferences', () => { }); describe(`errors`, () => { - test(`propogates decorated error when not authorized`, async () => { + test(`propagates decorated error when not authorized`, async () => { setupCheckUnauthorized(mockSecurityExt); // Unlike other functions, it doesn't validate the level of authorization first, so we need to // carry on and mock the enforce function as well to create an unauthorized condition diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts index d2f71da3763806..2c6930fc0d97af 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts @@ -216,7 +216,7 @@ export const mappings: SavedObjectsTypeMappingDefinition = { }; export const authRecord: Record = { - get: { authorizedSpaces: ['bar'] }, + find: { authorizedSpaces: ['bar'] }, }; export const authMap = Object.freeze(new Map([['foo', authRecord]])); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index b4e36ab0526464..a9533a302a8bfa 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -130,7 +130,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); describe('#get', () => { - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect( getSuccess(client, repository, registry, type, id, { namespace }) @@ -139,7 +139,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -265,7 +265,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); describe('#update', () => { - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect( updateSuccess(client, repository, registry, type, id, attributes, { namespace }) @@ -274,7 +274,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -420,7 +420,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); describe('#create', () => { - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect(repository.create(type, attributes, { namespace })).rejects.toThrow( checkAuthError @@ -429,7 +429,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -602,7 +602,7 @@ describe('SavedObjectsRepository Security Extension', () => { mockDeleteLegacyUrlAliases.mockClear(); }); - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect( deleteSuccess(client, repository, registry, type, id, { namespace }) @@ -611,7 +611,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -718,7 +718,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); describe('#removeReferencesTo', () => { - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect( removeReferencesToSuccess(client, repository, type, id, { namespace }) @@ -727,7 +727,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -829,7 +829,7 @@ describe('SavedObjectsRepository Security Extension', () => { const obj1 = { type, id: 'one' }; const obj2 = { type, id: 'two' }; - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect( checkConflictsSuccess(client, repository, registry, [obj1, obj2], { namespace }) @@ -838,7 +838,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -930,7 +930,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); describe('#openPointInTimeForType', () => { - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect(repository.openPointInTimeForType(type)).rejects.toThrow(checkAuthError); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); @@ -1022,7 +1022,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); describe('#find', () => { - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect(findSuccess(client, repository, { type })).rejects.toThrow(checkAuthError); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); @@ -1043,6 +1043,22 @@ describe('SavedObjectsRepository Security Extension', () => { ); }); + // test.only(`calls es search onlywith authorized spaces when partially authorized`, async () => { + // setupCheckPartiallyAuthorized(mockSecurityExt); + + // // const result = await repository.find({ type, namespaces: [namespace, 'ns-1'] }); + // await findSuccess(client, repository, { type, namespaces: [namespace, 'ns-1'] }, namespace); + // expect(client.search).toBeCalledWith(expect.objectContaining({})); + + // // expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + // // expect(result).toEqual( + // // expect.objectContaining({ + // // saved_objects: [], + // // total: 0, + // // }) + // // ); + // }); + test(`returns result of es find when fully authorized`, async () => { setupCheckAuthorized(mockSecurityExt); setupRedactPassthrough(mockSecurityExt); @@ -1073,6 +1089,27 @@ describe('SavedObjectsRepository Security Extension', () => { }); }); + test(`uses the authorization map when partially authorized`, async () => { + setupCheckPartiallyAuthorized(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + await findSuccess( + client, + repository, + { type: [type, NAMESPACE_AGNOSTIC_TYPE], namespaces: [namespace, 'ns-1'] }, // include multiple types and spaces + namespace + ); + + // make sure the authorized map gets passed to the es client call + expect(mockGetSearchDsl).toHaveBeenCalledWith( + expect.objectContaining({}), + expect.objectContaining({}), + expect.objectContaining({ + typeToNamespacesMap: authMap, + }) + ); + }); + test(`returns result of es find when partially authorized`, async () => { setupCheckPartiallyAuthorized(mockSecurityExt); setupRedactPassthrough(mockSecurityExt); @@ -1211,7 +1248,7 @@ describe('SavedObjectsRepository Security Extension', () => { namespaces: [namespace], }; - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect( bulkGetSuccess(client, repository, registry, [obj1, obj2], { namespace }) @@ -1220,7 +1257,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -1425,7 +1462,7 @@ describe('SavedObjectsRepository Security Extension', () => { references: [{ name: 'ref_0', type: 'test', id: '2' }], }; - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect(bulkCreateSuccess(client, repository, [obj1, obj2])).rejects.toThrow( checkAuthError @@ -1434,7 +1471,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); @@ -1636,7 +1673,7 @@ describe('SavedObjectsRepository Security Extension', () => { attributes: { title: 'Test Two' }, }; - test(`propogates decorated error when checkAuthorization rejects promise`, async () => { + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); await expect(bulkUpdateSuccess(client, repository, registry, [obj1, obj2])).rejects.toThrow( checkAuthError @@ -1645,7 +1682,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); }); - test(`propogates decorated error when unauthorized`, async () => { + test(`propagates decorated error when unauthorized`, async () => { setupCheckUnauthorized(mockSecurityExt); setupEnforceFailure(mockSecurityExt); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts index 58e4329fdad7af..517de8b29cc99b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts @@ -193,7 +193,7 @@ describe('SavedObjectsRepository Spaces Extension', () => { describe('#update', () => { test(`throws error if options.namespace is specified`, async () => { - // Just makes sure the error propogstes from the extension through the repo call + // Just makes sure the error propagates from the extension through the repo call await expect( repository.update('foo', 'some-id', { attr: 'value' }, { namespace: 'bar' }) ).rejects.toThrowError( @@ -465,7 +465,7 @@ describe('SavedObjectsRepository Spaces Extension', () => { }); describe('#openPointInTimeForType', () => { - test(`propogates options.namespaces: ['*']`, async () => { + test(`propagates options.namespaces: ['*']`, async () => { await repository.openPointInTimeForType(CUSTOM_INDEX_TYPE, { namespaces: ['*'] }); expect(mockSpacesExt.getSearchableNamespaces).toBeCalledTimes(1); expect(mockSpacesExt.getSearchableNamespaces).toBeCalledWith(['*']); @@ -783,7 +783,7 @@ describe('SavedObjectsRepository Spaces Extension', () => { ); }); - test(`propogates options.namespaces: ['*']`, async () => { + test(`propagates options.namespaces: ['*']`, async () => { const type = 'index-pattern'; await findSuccess(client, repository, { type, namespaces: ['*'] }); expect(mockSpacesExt.getSearchableNamespaces).toBeCalledTimes(1); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index c2fc7e69bda7cc..e640e1e550a368 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -150,7 +150,7 @@ export interface SavedObjectsRepositoryOptions { migrator: IKibanaMigrator; allowedTypes: string[]; logger: Logger; - extensions: SavedObjectsExtensions | undefined; + extensions?: SavedObjectsExtensions; } export const DEFAULT_REFRESH_SETTING = 'wait_for'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts index 16138f79a4bda9..af4216bf4ecb6f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_create_repository.test.ts @@ -90,7 +90,7 @@ describe('SavedObjectsRepository#createRepository', () => { callAdminCluster, logger, [], - { encryptionExtension: undefined, securityExtension: undefined, spacesExtension: undefined }, + undefined, SavedObjectsRepository ); expect(repository).toBeDefined(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index a1f643bd3edc48..48a362305a6b75 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -53,7 +53,7 @@ import type { * @internal */ export interface SavedObjectsFindInternalOptions { - /** This is used for calls internal to the SO doamain that need to use a PIT finder but want to prevent extensions from functioning. + /** This is used for calls internal to the SO domain that need to use a PIT finder but want to prevent extensions from functioning. * We use the SOR's PointInTimeFinder internally when searching for aliases and shared origins for saved objects, but we * need to disable the extensions for that to function correctly. * Before, when we had SOC wrappers, the SOR's PointInTimeFinder did not have any of the wrapper functionality applied. diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts index fed06cbf2f7409..2674b5a62e14a8 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts @@ -127,6 +127,7 @@ describe('getSortedObjectsForExport()', () => { "search", ], }, + undefined, ], ], "results": Array [ @@ -242,7 +243,8 @@ describe('getSortedObjectsForExport()', () => { sortField: 'updated_at', sortOrder: 'desc', type: ['index-pattern'], - }) + }), + undefined // PointInTimeFinder adds `internalOptions`, which is undefined in this case ); }); }); @@ -480,6 +482,7 @@ describe('getSortedObjectsForExport()', () => { "search", ], }, + undefined, ], ], "results": Array [ @@ -639,6 +642,7 @@ describe('getSortedObjectsForExport()', () => { "search", ], }, + undefined, ], ], "results": Array [ @@ -735,6 +739,7 @@ describe('getSortedObjectsForExport()', () => { "search", ], }, + undefined, ], ], "results": Array [ @@ -836,6 +841,7 @@ describe('getSortedObjectsForExport()', () => { "search", ], }, + undefined, ], ], "results": Array [ diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index 0f2c3d5447c0dc..a181865271f1f6 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -28,12 +28,11 @@ import { SavedObjectsExtensions } from './extensions'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to * use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods - * for registering Saved Object types, creating and registering Saved Object client wrappers and factories. + * or registering Saved Object types, and creating and registering Saved Object client factories. * * @remarks * When plugins access the Saved Objects client, a new client is created using - * the factory provided to `setClientFactory` and wrapped by all wrappers - * registered through `addClientWrapper`. + * the factory provided to `setClientFactory`. * * @example * ```ts @@ -152,8 +151,7 @@ export interface SavedObjectsServiceStart { /** * Creates a {@link SavedObjectsClientContract | Saved Objects client} that * uses the credentials from the passed in request to authenticate with - * Elasticsearch. If other plugins have registered Saved Objects client - * wrappers, these will be applied to extend the functionality of the client. + * Elasticsearch. * * A client that is already scoped to the incoming request is also exposed * from the route handler context see {@link RequestHandlerContext}. diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 444d7f3e39d751..8953d854e4671d 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -68,7 +68,6 @@ import { } from './dashboard'; import { SearchSessionsService } from './search_sessions'; import { ObservabilityProvider } from './observability'; -// import { CompareImagesProvider } from './compare_images'; import { CasesServiceProvider } from './cases'; import { AiopsProvider } from './aiops'; From a8eefce8a8802854ee83dc1a7acc9af39c6610f8 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Thu, 13 Oct 2022 14:01:03 -0400 Subject: [PATCH 10/47] Merges changes in api integration tests --- .../common/suites/bulk_resolve.ts | 1 + .../common/suites/resolve.ts | 2 +- .../security_and_spaces/apis/bulk_create.ts | 79 ++++++++--------- .../security_and_spaces/apis/bulk_get.ts | 85 ++++++++++++------- .../security_and_spaces/apis/bulk_resolve.ts | 21 +++-- .../security_and_spaces/apis/bulk_update.ts | 42 ++++----- .../security_and_spaces/apis/create.ts | 40 +++++---- .../security_and_spaces/apis/delete.ts | 14 ++- .../security_and_spaces/apis/find.ts | 61 ++++++------- .../security_and_spaces/apis/get.ts | 13 ++- .../security_and_spaces/apis/resolve.ts | 13 ++- .../security_and_spaces/apis/update.ts | 14 ++- .../suites/disable_legacy_url_aliases.ts | 2 +- 13 files changed, 195 insertions(+), 192 deletions(-) diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_resolve.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_resolve.ts index 7073fd99fdd478..a203865e294aad 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_resolve.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_resolve.ts @@ -135,5 +135,6 @@ export function bulkResolveTestSuiteFactory(esArchiver: any, supertest: SuperTes return { addTests, createTestDefinitions, + expectSavedObjectForbidden, }; } diff --git a/x-pack/test/saved_object_api_integration/common/suites/resolve.ts b/x-pack/test/saved_object_api_integration/common/suites/resolve.ts index 9be10e92438a9d..ebb71a860d744c 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/resolve.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/resolve.ts @@ -71,7 +71,7 @@ export const TEST_CASES = Object.freeze({ }); export function resolveTestSuiteFactory(esArchiver: any, supertest: SuperTest) { - const expectSavedObjectForbidden = expectResponses.forbiddenTypes('get'); + const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_get'); const expectResponseBody = (testCase: ResolveTestCase): ExpectResponseBody => async (response: Record) => { diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts index 2d8ca1c303bd41..f1f220e7395738 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_create.ts @@ -85,6 +85,19 @@ const createTestCases = (overwrite: boolean, spaceId: string) => { expectedNamespaces, }, ]; + const badRequests = [ + { ...CASES.HIDDEN, ...fail400() }, + { + ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, + initialNamespaces: ['x', 'y'], + ...fail400(), // cannot be created in multiple spaces -- second try below succeeds + }, + { + ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, + initialNamespaces: [ALL_SPACES_ID], + ...fail400(), // cannot be created in multiple spaces -- second try below succeeds + }, + ]; const crossNamespace = [ { ...CASES.ALIAS_CONFLICT_OBJ, @@ -93,24 +106,13 @@ const createTestCases = (overwrite: boolean, spaceId: string) => { fail409Param: 'aliasConflictAllSpaces', // second try fails because an alias exists in space_x, the default space, and space_1 (but not space_y because that alias is disabled) // note that if an object was successfully created with this type/ID in the first try, that won't change this outcome, because an alias conflict supersedes all other types of conflicts }, - { - ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, - initialNamespaces: ['x', 'y'], - ...fail400(), // cannot be created in multiple spaces - }, CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid - { - ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, - initialNamespaces: [ALL_SPACES_ID], - ...fail400(), // cannot be created in multiple spaces - }, CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE, CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES, ]; - const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; - const allTypes = [...normalTypes, ...crossNamespace, ...hiddenType]; - return { normalTypes, crossNamespace, hiddenType, allTypes }; + const allTypes = [...normalTypes, ...badRequests, ...crossNamespace]; + return { normalTypes, badRequests, crossNamespace, allTypes }; }; export default function ({ getService }: FtrProviderContext) { @@ -120,54 +122,48 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkCreateTestSuiteFactory(esArchiver, supertest); const createTests = (overwrite: boolean, spaceId: string, user: TestUser) => { - const { normalTypes, crossNamespace, hiddenType, allTypes } = createTestCases( + const { normalTypes, badRequests, crossNamespace, allTypes } = createTestCases( overwrite, spaceId ); // use singleRequest to reduce execution time and/or test combined cases - const authorizedCommon = [ - createTestDefinitions(normalTypes, false, overwrite, { - spaceId, - user, - singleRequest: true, - }), - createTestDefinitions(hiddenType, true, overwrite, { spaceId, user }), - ].flat(); + const singleRequest = true; return { - unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId, user }), - authorizedAtSpace: [ - authorizedCommon, + unauthorized: [ + createTestDefinitions(normalTypes, true, overwrite, { spaceId, user }), + createTestDefinitions(badRequests, false, overwrite, { spaceId, user, singleRequest }), // validation for hidden type and initialNamespaces returns 400 Bad Request before authZ check createTestDefinitions(crossNamespace, true, overwrite, { spaceId, user }), createTestDefinitions(allTypes, true, overwrite, { spaceId, user, - singleRequest: true, + singleRequest, + responseBodyOverride: expectSavedObjectForbidden([ + 'dashboard,globaltype,isolatedtype,resolvetype,sharecapabletype,sharedtype', // 'hiddentype' is not included in the 403 message, it was filtered out before the authZ check + ]), }), ].flat(), - authorizedEverywhere: [ - authorizedCommon, - createTestDefinitions(crossNamespace, false, overwrite, { - spaceId, - user, - singleRequest: true, - }), + authorizedAtSpace: [ + createTestDefinitions(normalTypes, false, overwrite, { spaceId, user, singleRequest }), + createTestDefinitions(badRequests, false, overwrite, { spaceId, user, singleRequest }), // validation for hidden type and initialNamespaces returns 400 Bad Request before authZ check + createTestDefinitions(crossNamespace, true, overwrite, { spaceId, user }), createTestDefinitions(allTypes, true, overwrite, { spaceId, user, - singleRequest: true, - responseBodyOverride: expectSavedObjectForbidden(['hiddentype']), + singleRequest, + responseBodyOverride: expectSavedObjectForbidden([ + 'dashboard,globaltype,isolatedtype,resolvetype,sharecapabletype,sharedtype', // 'hiddentype' is not included in the 403 message, it was filtered out before the authZ check + ]), }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite, { + authorizedEverywhere: createTestDefinitions(allTypes, false, overwrite, { spaceId, user, - singleRequest: true, + singleRequest, }), }; }; - // Failing: See https://github.com/elastic/kibana/issues/122827 - describe.skip('_bulk_create', () => { + describe('_bulk_create', () => { getTestScenarios([false, true]).securityAndSpaces.forEach( ({ spaceId, users, modifier: overwrite }) => { const suffix = ` within the ${spaceId} space${overwrite ? ' with overwrite enabled' : ''}`; @@ -190,13 +186,10 @@ export default function ({ getService }: FtrProviderContext) { const { authorizedAtSpace } = createTests(overwrite!, spaceId, users.allAtSpace); _addTests(users.allAtSpace, authorizedAtSpace); - [users.dualAll, users.allGlobally].forEach((user) => { + [users.dualAll, users.allGlobally, users.superuser].forEach((user) => { const { authorizedEverywhere } = createTests(overwrite!, spaceId, user); _addTests(user, authorizedEverywhere); }); - - const { superuser } = createTests(overwrite!, spaceId, users.superuser); - _addTests(users.superuser, superuser); } ); }); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts index ed251440d361a0..eaa78dd8a25747 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts @@ -45,38 +45,42 @@ const createTestCases = (spaceId: string) => { CASES.NAMESPACE_AGNOSTIC, { ...CASES.DOES_NOT_EXIST, ...fail404() }, ]; - const crossNamespace = [ + const badRequests = [ + { ...CASES.HIDDEN, ...fail400() }, { ...CASES.SINGLE_NAMESPACE_SPACE_2, namespaces: ['x', 'y'], - ...fail400(), // cannot be searched for in multiple spaces + ...fail400(), // cannot be searched for in multiple spaces -- second try below succeeds }, - { ...CASES.SINGLE_NAMESPACE_SPACE_2, namespaces: [SPACE_2_ID] }, // second try searches for it in a single other space, which is valid { ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, namespaces: [ALL_SPACES_ID], - ...fail400(), // cannot be searched for in multiple spaces + ...fail400(), // cannot be searched for in multiple spaces -- second try below succeeds }, + ]; + const crossNamespace = [ + { ...CASES.SINGLE_NAMESPACE_SPACE_2, namespaces: [SPACE_2_ID] }, // second try searches for it in a single other space, which is valid { ...CASES.MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1, namespaces: [SPACE_1_ID] }, // second try searches for it in a single other space, which is valid { ...CASES.MULTI_NAMESPACE_DEFAULT_AND_SPACE_1, namespaces: [SPACE_2_ID], ...fail404() }, { ...CASES.MULTI_NAMESPACE_ALL_SPACES, namespaces: [SPACE_2_ID, 'x'] }, // unknown space is allowed / ignored { ...CASES.MULTI_NAMESPACE_ALL_SPACES, namespaces: [ALL_SPACES_ID] }, // this is different than the same test case in the spaces_only suite, since MULTI_NAMESPACE_ONLY_SPACE_1 *may* return a 404 error to a partially authorized user ]; - const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; - const allTypes = [...normalTypes, ...crossNamespace, ...hiddenType]; - return { normalTypes, crossNamespace, hiddenType, allTypes }; + const allTypes = [...normalTypes, ...badRequests, ...crossNamespace]; + return { normalTypes, badRequests, crossNamespace, allTypes }; }; -export default function (context: FtrProviderContext) { - const { addTests, createTestDefinitions, expectSavedObjectForbidden } = - bulkGetTestSuiteFactory(context); +export default function ({ getService }: FtrProviderContext) { + const supertest = getService('supertestWithoutAuth'); + const esArchiver = getService('esArchiver'); + + const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkGetTestSuiteFactory( + esArchiver, + supertest + ); const createTests = (spaceId: string) => { - const { normalTypes, crossNamespace, hiddenType, allTypes } = createTestCases(spaceId); + const { normalTypes, badRequests, crossNamespace, allTypes } = createTestCases(spaceId); // use singleRequest to reduce execution time and/or test combined cases - const authorizedCommon = [ - createTestDefinitions(normalTypes, false, { singleRequest: true }), - createTestDefinitions(hiddenType, true), - ].flat(); + const singleRequest = true; const crossNamespaceAuthorizedAtSpace = crossNamespace.reduce<{ authorized: BulkGetTestCase[]; unauthorized: BulkGetTestCase[]; @@ -92,32 +96,43 @@ export default function (context: FtrProviderContext) { ); return { - unauthorized: createTestDefinitions(allTypes, true), - authorizedAtSpace: [ - authorizedCommon, - createTestDefinitions(crossNamespaceAuthorizedAtSpace.authorized, false, { - singleRequest: true, + unauthorized: [ + createTestDefinitions(normalTypes, true), + createTestDefinitions(badRequests, false, { singleRequest }), // validation for hidden type and initialNamespaces returns 400 Bad Request before authZ check + createTestDefinitions(crossNamespace, true), + createTestDefinitions(allTypes, true, { + singleRequest, + responseBodyOverride: expectSavedObjectForbidden([ + 'dashboard,globaltype,isolatedtype,sharecapabletype,sharedtype', // 'hiddentype' is not included in the 403 message, it was filtered out before the authZ check + ]), }), - createTestDefinitions(crossNamespaceAuthorizedAtSpace.unauthorized, true), - createTestDefinitions(allTypes, true, { singleRequest: true }), ].flat(), - authorizedEverywhere: [ - authorizedCommon, - createTestDefinitions(crossNamespace, false, { singleRequest: true }), + authorizedAtSpace: [ + createTestDefinitions(normalTypes, false, { singleRequest }), + createTestDefinitions(badRequests, false, { singleRequest }), // validation for hidden type and initialNamespaces returns 400 Bad Request before authZ check + createTestDefinitions(crossNamespaceAuthorizedAtSpace.authorized, false, { singleRequest }), + createTestDefinitions(crossNamespaceAuthorizedAtSpace.unauthorized, true), createTestDefinitions(allTypes, true, { - singleRequest: true, - responseBodyOverride: expectSavedObjectForbidden(['hiddentype']), + singleRequest, + responseBodyOverride: expectSavedObjectForbidden( + spaceId === DEFAULT_SPACE_ID + ? // While the Default space is active, we always attempt to get 'sharecapabletype' (MULTI_NAMESPACE_ISOLATED_ONLY_SPACE_1) + // with a cross-namespace check in Space 1, and fail the authZ check for 'sharecapabletype' in Space 1 because these users + // are only authorized for the Default space! + // 'hiddentype' is not included in either 403 message, it was filtered out before the authZ check + ['isolatedtype,sharecapabletype,sharedtype'] + : ['isolatedtype,sharedtype'] + ), }), ].flat(), - superuser: createTestDefinitions(allTypes, false, { singleRequest: true }), + authorizedEverywhere: createTestDefinitions(allTypes, false, { singleRequest }), }; }; describe('_bulk_get', () => { getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { const suffix = ` within the ${spaceId} space`; - const { unauthorized, authorizedAtSpace, authorizedEverywhere, superuser } = - createTests(spaceId); + const { unauthorized, authorizedAtSpace, authorizedEverywhere } = createTests(spaceId); const _addTests = (user: TestUser, tests: BulkGetTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -130,11 +145,15 @@ export default function (context: FtrProviderContext) { _addTests(user, authorizedAtSpace); }); - [users.dualAll, users.dualRead, users.allGlobally, users.readGlobally].forEach((user) => { + [ + users.dualAll, + users.dualRead, + users.allGlobally, + users.readGlobally, + users.superuser, + ].forEach((user) => { _addTests(user, authorizedEverywhere); }); - - _addTests(users.superuser, superuser); }); }); } diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_resolve.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_resolve.ts index ef4c54110a20a6..5f6606ac07c59d 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_resolve.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_resolve.ts @@ -44,24 +44,29 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const esArchiver = getService('esArchiver'); - const { addTests, createTestDefinitions } = bulkResolveTestSuiteFactory(esArchiver, supertest); + const { addTests, createTestDefinitions, expectSavedObjectForbidden } = + bulkResolveTestSuiteFactory(esArchiver, supertest); const createTests = (spaceId: string) => { const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); // use singleRequest to reduce execution time and/or test combined cases + const singleRequest = true; return { - unauthorized: createTestDefinitions(allTypes, true), - authorized: [ - createTestDefinitions(normalTypes, false), - createTestDefinitions(hiddenType, true), + unauthorized: [ + createTestDefinitions(normalTypes, true), + createTestDefinitions(hiddenType, false, { singleRequest }), // validation for hidden type returns 400 Bad Request before authZ check + createTestDefinitions(allTypes, true, { + singleRequest, + responseBodyOverride: expectSavedObjectForbidden(['resolvetype']), // 'hiddentype' is not included in the 403 message, it was filtered out before the authZ check + }), ].flat(), - superuser: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allTypes, false, { singleRequest }), }; }; describe('_bulk_resolve', () => { getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { const suffix = ` within the ${spaceId} space`; - const { unauthorized, authorized, superuser } = createTests(spaceId); + const { unauthorized, authorized } = createTests(spaceId); const _addTests = (user: TestUser, tests: BulkResolveTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -76,10 +81,10 @@ export default function ({ getService }: FtrProviderContext) { users.readGlobally, users.allAtSpace, users.readAtSpace, + users.superuser, ].forEach((user) => { _addTests(user, authorized); }); - _addTests(users.superuser, superuser); }); }); } diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_update.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_update.ts index 2d6b1160d9864b..33a02fd783e75f 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_update.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_update.ts @@ -45,7 +45,7 @@ const createTestCases = (spaceId: string) => { { ...CASES.DOES_NOT_EXIST, ...fail404() }, ]; const hiddenType = [{ ...CASES.HIDDEN, ...fail404() }]; - const allTypes = normalTypes.concat(hiddenType); + const normalAndHidden = normalTypes.concat(hiddenType); // an "object namespace" string can be specified for individual objects (to bulkUpdate across namespaces) const withObjectNamespaces = [ { ...CASES.SINGLE_NAMESPACE_DEFAULT_SPACE, namespace: DEFAULT_SPACE_ID }, @@ -58,7 +58,7 @@ const createTestCases = (spaceId: string) => { CASES.NAMESPACE_AGNOSTIC, // any namespace would work and would make no difference { ...CASES.DOES_NOT_EXIST, ...fail404() }, ]; - return { normalTypes, hiddenType, allTypes, withObjectNamespaces }; + return { normalTypes, hiddenType, normalAndHidden, withObjectNamespaces }; }; export default function ({ getService }: FtrProviderContext) { @@ -68,32 +68,26 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkUpdateTestSuiteFactory(esArchiver, supertest); const createTests = (spaceId: string) => { - const { normalTypes, hiddenType, allTypes, withObjectNamespaces } = createTestCases(spaceId); + const { normalTypes, hiddenType, normalAndHidden, withObjectNamespaces } = + createTestCases(spaceId); // use singleRequest to reduce execution time and/or test combined cases - const authorizedCommon = [ - createTestDefinitions(normalTypes, false, { singleRequest: true }), - createTestDefinitions(hiddenType, true), - createTestDefinitions(allTypes, true, { - singleRequest: true, - responseBodyOverride: expectSavedObjectForbidden(['hiddentype']), - }), - ].flat(); + const singleRequest = true; return { unauthorized: [ - createTestDefinitions(allTypes, true), - createTestDefinitions(withObjectNamespaces, true, { singleRequest: true }), + createTestDefinitions(normalTypes, true), + createTestDefinitions(hiddenType, false, { singleRequest }), // validation for hidden type returns 404 Not Found before authZ check + createTestDefinitions(withObjectNamespaces, true, { singleRequest }), ].flat(), authorizedAtSpace: [ - authorizedCommon, - createTestDefinitions(withObjectNamespaces, true, { singleRequest: true }), + createTestDefinitions(normalAndHidden, false, { singleRequest }), // validation for hidden type returns 404 Not Found before authZ check + createTestDefinitions(withObjectNamespaces, true, { + singleRequest, + responseBodyOverride: expectSavedObjectForbidden(['isolatedtype,sharedtype']), // 'dashboard' and 'globaltype' are not in the error message because this user *is* authorized to update those object types in that space + }), ].flat(), authorizedAllSpaces: [ - authorizedCommon, - createTestDefinitions(withObjectNamespaces, false, { singleRequest: true }), - ].flat(), - superuser: [ - createTestDefinitions(allTypes, false, { singleRequest: true }), - createTestDefinitions(withObjectNamespaces, false, { singleRequest: true }), + createTestDefinitions(normalAndHidden, false, { singleRequest }), // validation for hidden type returns 404 Not Found before authZ check + createTestDefinitions(withObjectNamespaces, false, { singleRequest }), ].flat(), }; }; @@ -101,8 +95,7 @@ export default function ({ getService }: FtrProviderContext) { describe('_bulk_update', () => { getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { const suffix = ` within the ${spaceId} space`; - const { unauthorized, authorizedAtSpace, authorizedAllSpaces, superuser } = - createTests(spaceId); + const { unauthorized, authorizedAtSpace, authorizedAllSpaces } = createTests(spaceId); const _addTests = (user: TestUser, tests: BulkUpdateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -120,10 +113,9 @@ export default function ({ getService }: FtrProviderContext) { [users.allAtSpace].forEach((user) => { _addTests(user, authorizedAtSpace); }); - [users.dualAll, users.allGlobally].forEach((user) => { + [users.dualAll, users.allGlobally, users.superuser].forEach((user) => { _addTests(user, authorizedAllSpaces); }); - _addTests(users.superuser, superuser); }); }); } diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts index cdef08e8204169..cf9b118d4776d7 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/create.ts @@ -64,26 +64,28 @@ const createTestCases = (overwrite: boolean, spaceId: string) => { // We test the alias conflict preflight check error case twice; once by checking the alias with "find" and once by using "bulk-get". { ...CASES.ALIAS_CONFLICT_OBJ, ...fail409(spaceId !== SPACE_2_ID), expectedNamespaces }, // first try fails if this is the default space or space_1, because an alias exists in those spaces ]; - const crossNamespace = [ - { ...CASES.ALIAS_CONFLICT_OBJ, initialNamespaces: ['*'], ...fail409() }, // second try fails because an alias exists in space_x, the default space, and space_1 (but not space_y because that alias is disabled) + const badRequests = [ + { ...CASES.HIDDEN, ...fail400() }, { ...CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, initialNamespaces: ['x', 'y'], - ...fail400(), // cannot be created in multiple spaces + ...fail400(), // cannot be created in multiple spaces -- second try below succeeds }, - CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid { ...CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, initialNamespaces: [ALL_SPACES_ID], - ...fail400(), // cannot be created in multiple spaces + ...fail400(), // cannot be created in multiple spaces -- second try below succeeds }, + ]; + const crossNamespace = [ + { ...CASES.ALIAS_CONFLICT_OBJ, initialNamespaces: ['*'], ...fail409() }, // second try fails because an alias exists in space_x, the default space, and space_1 (but not space_y because that alias is disabled) + CASES.INITIAL_NS_SINGLE_NAMESPACE_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid CASES.INITIAL_NS_MULTI_NAMESPACE_ISOLATED_OBJ_OTHER_SPACE, // second try creates it in a single other space, which is valid CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_EACH_SPACE, CASES.INITIAL_NS_MULTI_NAMESPACE_OBJ_ALL_SPACES, ]; - const hiddenType = [{ ...CASES.HIDDEN, ...fail400() }]; - const allTypes = normalTypes.concat(crossNamespace, hiddenType); - return { normalTypes, crossNamespace, hiddenType, allTypes }; + const allTypes = [...normalTypes, ...badRequests, ...crossNamespace]; + return { normalTypes, badRequests, crossNamespace, allTypes }; }; export default function ({ getService }: FtrProviderContext) { @@ -92,23 +94,22 @@ export default function ({ getService }: FtrProviderContext) { const { addTests, createTestDefinitions } = createTestSuiteFactory(esArchiver, supertest); const createTests = (overwrite: boolean, spaceId: string, user: TestUser) => { - const { normalTypes, crossNamespace, hiddenType, allTypes } = createTestCases( + const { normalTypes, badRequests, crossNamespace, allTypes } = createTestCases( overwrite, spaceId ); return { - unauthorized: createTestDefinitions(allTypes, true, overwrite, { spaceId, user }), - authorizedAtSpace: [ - createTestDefinitions(normalTypes, false, overwrite, { spaceId, user }), + unauthorized: [ + createTestDefinitions(normalTypes, true, overwrite, { spaceId, user }), + createTestDefinitions(badRequests, false, overwrite, { spaceId, user }), // validation for hidden type and initialNamespaces returns 400 Bad Request before authZ check createTestDefinitions(crossNamespace, true, overwrite, { spaceId, user }), - createTestDefinitions(hiddenType, true, overwrite, { spaceId, user }), ].flat(), - authorizedEverywhere: [ + authorizedAtSpace: [ createTestDefinitions(normalTypes, false, overwrite, { spaceId, user }), - createTestDefinitions(crossNamespace, false, overwrite, { spaceId, user }), - createTestDefinitions(hiddenType, true, overwrite, { spaceId, user }), + createTestDefinitions(badRequests, false, overwrite, { spaceId, user }), // validation for hidden type and initialNamespaces returns 400 Bad Request before authZ check + createTestDefinitions(crossNamespace, true, overwrite, { spaceId, user }), ].flat(), - superuser: createTestDefinitions(allTypes, false, overwrite, { spaceId, user }), + authorizedEverywhere: createTestDefinitions(allTypes, false, overwrite, { spaceId, user }), }; }; @@ -135,13 +136,10 @@ export default function ({ getService }: FtrProviderContext) { const { authorizedAtSpace } = createTests(overwrite!, spaceId, users.allAtSpace); _addTests(users.allAtSpace, authorizedAtSpace); - [users.dualAll, users.allGlobally].forEach((user) => { + [users.dualAll, users.allGlobally, users.superuser].forEach((user) => { const { authorizedEverywhere } = createTests(overwrite!, spaceId, user); _addTests(user, authorizedEverywhere); }); - - const { superuser } = createTests(overwrite!, spaceId, users.superuser); - _addTests(users.superuser, superuser); } ); }); diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts index 8970070645f4df..37fe392db06cef 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/delete.ts @@ -69,19 +69,18 @@ export default function ({ getService }: FtrProviderContext) { const createTests = (spaceId: string) => { const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); return { - unauthorized: createTestDefinitions(allTypes, true, { spaceId }), - authorized: [ - createTestDefinitions(normalTypes, false, { spaceId }), - createTestDefinitions(hiddenType, true, { spaceId }), + unauthorized: [ + createTestDefinitions(normalTypes, true, { spaceId }), + createTestDefinitions(hiddenType, false, { spaceId }), // validation for hidden type returns 404 Not Found before authZ check ].flat(), - superuser: createTestDefinitions(allTypes, false, { spaceId }), + authorized: createTestDefinitions(allTypes, false, { spaceId }), }; }; describe('_delete', () => { getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { const suffix = ` within the ${spaceId} space`; - const { unauthorized, authorized, superuser } = createTests(spaceId); + const { unauthorized, authorized } = createTests(spaceId); const _addTests = (user: TestUser, tests: DeleteTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -96,10 +95,9 @@ export default function ({ getService }: FtrProviderContext) { ].forEach((user) => { _addTests(user, unauthorized); }); - [users.dualAll, users.allGlobally, users.allAtSpace].forEach((user) => { + [users.dualAll, users.allGlobally, users.allAtSpace, users.superuser].forEach((user) => { _addTests(user, authorized); }); - _addTests(users.superuser, superuser); }); }); } diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts index 6d9c38ecca5962..b0a04c2cc370bf 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts @@ -6,7 +6,6 @@ */ import { SPACES } from '../../common/lib/spaces'; -import { AUTHENTICATION } from '../../common/lib/authentication'; import { getTestScenarios, isUserAuthorizedAtSpace, @@ -33,16 +32,19 @@ const createTestCases = (currentSpace: string, crossSpaceSearch?: string[]) => { cases.pageBeyondTotal, cases.unknownSearchField, cases.filterWithNamespaceAgnosticType, - cases.filterWithDisallowedType, ]; + const badRequestTypes = [cases.filterWithDisallowedType]; const hiddenAndUnknownTypes = [ cases.hiddenType, cases.unknownType, cases.filterWithHiddenType, cases.filterWithUnknownType, ]; - const allTypes = normalTypes.concat(hiddenAndUnknownTypes); - return { normalTypes, hiddenAndUnknownTypes, allTypes }; + return { + normalTypes, + badRequestTypes, + hiddenAndUnknownTypes, +}; }; export default function ({ getService }: FtrProviderContext) { @@ -57,16 +59,6 @@ export default function ({ getService }: FtrProviderContext) { const explicitCrossSpace = createTestCases(spaceId, EACH_SPACE); const wildcardCrossSpace = createTestCases(spaceId, ['*']); - if (user.username === AUTHENTICATION.SUPERUSER.username) { - return { - currentSpace: createTestDefinitions(currentSpaceCases.allTypes, false, { user }), - crossSpace: [ - createTestDefinitions(explicitCrossSpace.allTypes, false, { user }), - createTestDefinitions(wildcardCrossSpace.allTypes, false, { user }), - ].flat(), - }; - } - const isAuthorizedExplicitCrossSpaces = EACH_SPACE.some( (s) => s !== spaceId && isUserAuthorizedAtSpace(user, s) ); @@ -84,7 +76,7 @@ export default function ({ getService }: FtrProviderContext) { ), ].flat() : createTestDefinitions( - explicitCrossSpace.allTypes, + [explicitCrossSpace.normalTypes, explicitCrossSpace.hiddenAndUnknownTypes].flat(), { statusCode: 200, reason: 'unauthorized' }, { user } ); @@ -98,27 +90,36 @@ export default function ({ getService }: FtrProviderContext) { ), ].flat() : createTestDefinitions( - wildcardCrossSpace.allTypes, + [wildcardCrossSpace.normalTypes, wildcardCrossSpace.hiddenAndUnknownTypes].flat(), { statusCode: 200, reason: 'unauthorized' }, { user } ); - return { - currentSpace: isUserAuthorizedAtSpace(user, spaceId) + const currentSpaceDefinitions = isUserAuthorizedAtSpace(user, spaceId) ? [ - createTestDefinitions(currentSpaceCases.normalTypes, false, { - user, - }), - createTestDefinitions(currentSpaceCases.hiddenAndUnknownTypes, { - statusCode: 200, - reason: 'unauthorized', - }), + createTestDefinitions(currentSpaceCases.normalTypes, false, { user }), + createTestDefinitions( + currentSpaceCases.hiddenAndUnknownTypes, + { statusCode: 200, reason: 'unauthorized' }, + { user } + ), ].flat() - : createTestDefinitions(currentSpaceCases.allTypes, { - statusCode: 200, - reason: 'unauthorized', - }), - crossSpace: [...explicitCrossSpaceDefinitions, ...wildcardCrossSpaceDefinitions], + : createTestDefinitions( + [currentSpaceCases.normalTypes, currentSpaceCases.hiddenAndUnknownTypes].flat(), + { statusCode: 200, reason: 'unauthorized' }, + { user } + ); + return { + currentSpace: [ + currentSpaceDefinitions, + createTestDefinitions(currentSpaceCases.badRequestTypes, false, { user }), // validation for filter returns 400 Bad Request before authZ check + ].flat(), + crossSpace: [ + explicitCrossSpaceDefinitions, + wildcardCrossSpaceDefinitions, + createTestDefinitions(explicitCrossSpace.badRequestTypes, false, { user }), // validation for filter returns 400 Bad Request before authZ check + createTestDefinitions(wildcardCrossSpace.badRequestTypes, false, { user }), // validation for filter returns 400 Bad Request before authZ check + ].flat(), }; }; diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/get.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/get.ts index e61d5c10c2dbb3..a6f976ae69599d 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/get.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/get.ts @@ -58,19 +58,18 @@ export default function ({ getService }: FtrProviderContext) { const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); // use singleRequest to reduce execution time and/or test combined cases return { - unauthorized: createTestDefinitions(allTypes, true), - authorized: [ - createTestDefinitions(normalTypes, false), - createTestDefinitions(hiddenType, true), + unauthorized: [ + createTestDefinitions(normalTypes, true), + createTestDefinitions(hiddenType, false), // validation for hidden type returns 404 Not Found before authZ check ].flat(), - superuser: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allTypes, false), }; }; describe('_get', () => { getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { const suffix = ` within the ${spaceId} space`; - const { unauthorized, authorized, superuser } = createTests(spaceId); + const { unauthorized, authorized } = createTests(spaceId); const _addTests = (user: TestUser, tests: GetTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -85,10 +84,10 @@ export default function ({ getService }: FtrProviderContext) { users.readGlobally, users.allAtSpace, users.readAtSpace, + users.superuser, ].forEach((user) => { _addTests(user, authorized); }); - _addTests(users.superuser, superuser); }); }); } diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.ts index eecc2e39f608de..46ac9a7342e9ae 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/resolve.ts @@ -49,19 +49,18 @@ export default function ({ getService }: FtrProviderContext) { const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); // use singleRequest to reduce execution time and/or test combined cases return { - unauthorized: createTestDefinitions(allTypes, true), - authorized: [ - createTestDefinitions(normalTypes, false), - createTestDefinitions(hiddenType, true), + unauthorized: [ + createTestDefinitions(normalTypes, true), + createTestDefinitions(hiddenType, false), // validation for hidden type returns 400 Bad Request before authZ check ].flat(), - superuser: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allTypes, false), }; }; describe('_resolve', () => { getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { const suffix = ` within the ${spaceId} space`; - const { unauthorized, authorized, superuser } = createTests(spaceId); + const { unauthorized, authorized } = createTests(spaceId); const _addTests = (user: TestUser, tests: ResolveTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -76,10 +75,10 @@ export default function ({ getService }: FtrProviderContext) { users.readGlobally, users.allAtSpace, users.readAtSpace, + users.superuser, ].forEach((user) => { _addTests(user, authorized); }); - _addTests(users.superuser, superuser); }); }); } diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/update.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/update.ts index 1f5cbe892a5f55..cf71994e6eb681 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/update.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/update.ts @@ -59,19 +59,18 @@ export default function ({ getService }: FtrProviderContext) { const createTests = (spaceId: string) => { const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); return { - unauthorized: createTestDefinitions(allTypes, true), - authorized: [ - createTestDefinitions(normalTypes, false), - createTestDefinitions(hiddenType, true), + unauthorized: [ + createTestDefinitions(normalTypes, true), + createTestDefinitions(hiddenType, false), // validation for hidden type returns 404 Not Found before authZ check ].flat(), - superuser: createTestDefinitions(allTypes, false), + authorized: createTestDefinitions(allTypes, false), }; }; describe('_update', () => { getTestScenarios().securityAndSpaces.forEach(({ spaceId, users }) => { const suffix = ` within the ${spaceId} space`; - const { unauthorized, authorized, superuser } = createTests(spaceId); + const { unauthorized, authorized } = createTests(spaceId); const _addTests = (user: TestUser, tests: UpdateTestDefinition[]) => { addTests(`${user.description}${suffix}`, { user, spaceId, tests }); }; @@ -86,10 +85,9 @@ export default function ({ getService }: FtrProviderContext) { ].forEach((user) => { _addTests(user, unauthorized); }); - [users.dualAll, users.allGlobally, users.allAtSpace].forEach((user) => { + [users.dualAll, users.allGlobally, users.allAtSpace, users.superuser].forEach((user) => { _addTests(user, authorized); }); - _addTests(users.superuser, superuser); }); }); } diff --git a/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts index f95267a02ab3f2..4719d0e5164a65 100644 --- a/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts +++ b/x-pack/test/spaces_api_integration/common/suites/disable_legacy_url_aliases.ts @@ -57,7 +57,7 @@ export function disableLegacyUrlAliasesTestSuiteFactory( expect(response.body).to.eql({ statusCode: 403, error: 'Forbidden', - message: `Unable to disable aliases for ${targetType}`, + message: `Unable to disable aliases: Unable to bulk_update ${targetType}`, }); } const esResponse = await es.get( From 9935fef1335fadded16840d3f526bc3552d060bb Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Thu, 13 Oct 2022 14:55:58 -0400 Subject: [PATCH 11/47] Fixes accidental overwrite of esArchiver replacement. --- .../security_and_spaces/apis/bulk_get.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts index eaa78dd8a25747..d8bc344fab1097 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_get.ts @@ -69,14 +69,9 @@ const createTestCases = (spaceId: string) => { return { normalTypes, badRequests, crossNamespace, allTypes }; }; -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertestWithoutAuth'); - const esArchiver = getService('esArchiver'); - - const { addTests, createTestDefinitions, expectSavedObjectForbidden } = bulkGetTestSuiteFactory( - esArchiver, - supertest - ); +export default function (context: FtrProviderContext) { + const { addTests, createTestDefinitions, expectSavedObjectForbidden } = + bulkGetTestSuiteFactory(context); const createTests = (spaceId: string) => { const { normalTypes, badRequests, crossNamespace, allTypes } = createTestCases(spaceId); // use singleRequest to reduce execution time and/or test combined cases From 3398a57aeb1094bb09598105702b8a46ce74517d Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 13 Oct 2022 19:28:17 +0000 Subject: [PATCH 12/47] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../security_and_spaces/apis/find.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts index b0a04c2cc370bf..6ec1684c8aadec 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/find.ts @@ -44,7 +44,7 @@ const createTestCases = (currentSpace: string, crossSpaceSearch?: string[]) => { normalTypes, badRequestTypes, hiddenAndUnknownTypes, -}; + }; }; export default function ({ getService }: FtrProviderContext) { @@ -96,14 +96,14 @@ export default function ({ getService }: FtrProviderContext) { ); const currentSpaceDefinitions = isUserAuthorizedAtSpace(user, spaceId) - ? [ + ? [ createTestDefinitions(currentSpaceCases.normalTypes, false, { user }), createTestDefinitions( currentSpaceCases.hiddenAndUnknownTypes, { statusCode: 200, reason: 'unauthorized' }, { user } ), - ].flat() + ].flat() : createTestDefinitions( [currentSpaceCases.normalTypes, currentSpaceCases.hiddenAndUnknownTypes].flat(), { statusCode: 200, reason: 'unauthorized' }, From dfaf90bee75058d86fc08b0a7a0b6439040ac826 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Fri, 14 Oct 2022 14:28:06 -0400 Subject: [PATCH 13/47] Fixes repo called to find_legacy_url_aliases and find_shared_origin_objects by adding the new internalOptions parameter. --- .../lib/find_shared_origin_objects.test.ts | 27 +++++++------ .../src/lib/find_shared_origin_objects.ts | 18 +++++---- .../find_legacy_url_aliases.test.ts | 39 +++++++++++-------- .../find_legacy_url_aliases.ts | 10 ++--- .../src/lib/point_in_time_finder.ts | 3 +- .../src/lib/repository.ts | 2 +- x-pack/plugins/spaces/server/plugin.test.ts | 2 +- .../common/suites/bulk_get.ts | 2 +- 8 files changed, 60 insertions(+), 43 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts index 760a33689dbfc9..e3464f69dec636 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts @@ -10,6 +10,8 @@ import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; import type { CreatePointInTimeFinderFn, PointInTimeFinder } from './point_in_time_finder'; import { savedObjectsPointInTimeFinderMock } from '../mocks/point_in_time_finder.mock'; import { findSharedOriginObjects } from './find_shared_origin_objects'; +import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks'; interface MockFindResultParams { type: string; @@ -19,15 +21,12 @@ interface MockFindResultParams { } describe('findSharedOriginObjects', () => { - let savedObjectsMock: ReturnType; + let savedObjectsMock: jest.Mocked; let pointInTimeFinder: DeeplyMockedKeys; let createPointInTimeFinder: jest.MockedFunction; beforeEach(() => { - savedObjectsMock = savedObjectsPointInTimeFinderMock.createClient(); - savedObjectsMock.openPointInTimeForType.mockResolvedValueOnce({ - id: 'abc123', - }); + savedObjectsMock = savedObjectsRepositoryMock.create(); savedObjectsMock.find.mockResolvedValue({ pit_id: 'foo', saved_objects: [], @@ -79,21 +78,23 @@ describe('findSharedOriginObjects', () => { const result = await findSharedOriginObjects(createPointInTimeFinder, objects); expect(createPointInTimeFinder).toHaveBeenCalledTimes(1); expect(createPointInTimeFinder).toHaveBeenCalledWith( - expect.objectContaining({ type: ['type-1', 'type-2', 'type-3', 'type-4'] }) // filter assertions are below + expect.objectContaining({ type: ['type-1', 'type-2', 'type-3', 'type-4'] }), // filter assertions are below + undefined, + { disableExtensions: true } ); const kueryFilterArgs = createPointInTimeFinder.mock.calls[0][0].filter.arguments; expect(kueryFilterArgs).toHaveLength(8); // 2 for each object [obj1, obj2, obj3].forEach(({ type, origin }, i) => { expect(kueryFilterArgs[i * 2].arguments).toEqual( expect.arrayContaining([ - { type: 'literal', value: `${type}.id`, isQuoted: false }, - { type: 'literal', value: `${type}:${origin}`, isQuoted: false }, + { isQuoted: false, type: 'literal', value: `${type}.id` }, + { isQuoted: false, type: 'literal', value: `${type}:${origin}` }, ]) ); expect(kueryFilterArgs[i * 2 + 1].arguments).toEqual( expect.arrayContaining([ - { type: 'literal', value: `${type}.originId`, isQuoted: false }, - { type: 'literal', value: origin, isQuoted: false }, + { isQuoted: false, type: 'literal', value: `${type}.originId` }, + { isQuoted: false, type: 'literal', value: origin }, ]) ); }); @@ -119,7 +120,11 @@ describe('findSharedOriginObjects', () => { const objects = [obj1, obj2, obj3]; await findSharedOriginObjects(createPointInTimeFinder, objects, 999); expect(createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(createPointInTimeFinder).toHaveBeenCalledWith(expect.objectContaining({ perPage: 999 })); + expect(createPointInTimeFinder).toHaveBeenCalledWith( + expect.objectContaining({ perPage: 999 }), + undefined, + { disableExtensions: true } + ); }); it('does not create a PointInTimeFinder if no objects are passed in', async () => { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts index 578cb2fda238d8..a489e4afa91c37 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts @@ -34,13 +34,17 @@ export async function findSharedOriginObjects( const uniqueObjectTypes = objects.reduce((acc, { type }) => acc.add(type), new Set()); const filter = createAliasKueryFilter(objects); - const finder = createPointInTimeFinder({ - type: [...uniqueObjectTypes], - perPage, - filter, - fields: ['not-a-field'], // Specify a non-existent field to avoid fetching all type-level fields (we only care about root-level fields) - namespaces: [ALL_NAMESPACES_STRING], // We need to search across all spaces to have accurate results - }); + const finder = createPointInTimeFinder( + { + type: [...uniqueObjectTypes], + perPage, + filter, + fields: ['not-a-field'], // Specify a non-existent field to avoid fetching all type-level fields (we only care about root-level fields) + namespaces: [ALL_NAMESPACES_STRING], // We need to search across all spaces to have accurate results + }, + undefined, + { disableExtensions: true } + ); // NOTE: this objectsMap is only used internally (not in an API that is documented for public consumption), and it contains the minimal // amount of information to satisfy our UI needs today. We will need to change this in the future when we implement merging in #130311. const objectsMap = new Map>(); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts index 1af8bec312d778..49fe50e18776d6 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts @@ -15,14 +15,16 @@ import { import type { CreatePointInTimeFinderFn, PointInTimeFinder } from '../point_in_time_finder'; import { findLegacyUrlAliases } from './find_legacy_url_aliases'; import { savedObjectsPointInTimeFinderMock } from '../../mocks'; +import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks'; describe('findLegacyUrlAliases', () => { - let savedObjectsMock: ReturnType; + let savedObjectsMock: jest.Mocked; let pointInTimeFinder: DeeplyMockedKeys; let createPointInTimeFinder: jest.MockedFunction; beforeEach(() => { - savedObjectsMock = savedObjectsPointInTimeFinderMock.createClient(); + savedObjectsMock = savedObjectsRepositoryMock.create(); savedObjectsMock.find.mockResolvedValue({ pit_id: 'foo', saved_objects: [], @@ -31,9 +33,6 @@ describe('findLegacyUrlAliases', () => { page: 1, per_page: 100, }); - savedObjectsMock.openPointInTimeForType.mockResolvedValueOnce({ - id: 'abc123', - }); pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ savedObjectsMock })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too createPointInTimeFinder = jest.fn().mockReturnValue(pointInTimeFinder); }); @@ -74,10 +73,14 @@ describe('findLegacyUrlAliases', () => { const objects = [obj1, obj2, obj3]; const result = await findLegacyUrlAliases(createPointInTimeFinder, objects); expect(createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(createPointInTimeFinder).toHaveBeenCalledWith({ - type: LEGACY_URL_ALIAS_TYPE, - filter: expect.any(Object), // assertions are below - }); + expect(createPointInTimeFinder).toHaveBeenCalledWith( + { + type: LEGACY_URL_ALIAS_TYPE, + filter: expect.any(Object), // assertions are below + }, + undefined, + { disableExtensions: true } + ); const kueryFilterArgs = createPointInTimeFinder.mock.calls[0][0].filter.arguments; expect(kueryFilterArgs).toHaveLength(2); const typeAndIdFilters = kueryFilterArgs[1].arguments; @@ -86,10 +89,10 @@ describe('findLegacyUrlAliases', () => { const typeAndIdFilter = typeAndIdFilters[i].arguments; expect(typeAndIdFilter).toEqual([ expect.objectContaining({ - arguments: expect.arrayContaining([{ type: 'literal', value: type, isQuoted: false }]), + arguments: expect.arrayContaining([{ isQuoted: false, type: 'literal', value: type }]), }), expect.objectContaining({ - arguments: expect.arrayContaining([{ type: 'literal', value: id, isQuoted: false }]), + arguments: expect.arrayContaining([{ isQuoted: false, type: 'literal', value: id }]), }), ]); }); @@ -107,11 +110,15 @@ describe('findLegacyUrlAliases', () => { const objects = [obj1, obj2, obj3]; await findLegacyUrlAliases(createPointInTimeFinder, objects, 999); expect(createPointInTimeFinder).toHaveBeenCalledTimes(1); - expect(createPointInTimeFinder).toHaveBeenCalledWith({ - type: LEGACY_URL_ALIAS_TYPE, - perPage: 999, - filter: expect.any(Object), - }); + expect(createPointInTimeFinder).toHaveBeenCalledWith( + { + type: LEGACY_URL_ALIAS_TYPE, + perPage: 999, + filter: expect.any(Object), + }, + undefined, + { disableExtensions: true } + ); }); it('does not create a PointInTimeFinder if no objects are passed in', async () => { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts index be87a42de718c7..62bf5a51d88936 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts @@ -34,11 +34,11 @@ export async function findLegacyUrlAliases( } const filter = createAliasKueryFilter(objects); - const finder = createPointInTimeFinder({ - type: LEGACY_URL_ALIAS_TYPE, - perPage, - filter, - }); + const finder = createPointInTimeFinder( + { type: LEGACY_URL_ALIAS_TYPE, perPage, filter }, + undefined, + { disableExtensions: true } + ); const aliasesMap = new Map>(); let error: Error | undefined; try { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts index 7d5a8fea1370ff..7dffbbdaa356fe 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts @@ -31,7 +31,8 @@ export interface PointInTimeFinderDependencies */ export type CreatePointInTimeFinderFn = ( findOptions: SavedObjectsCreatePointInTimeFinderOptions, - dependencies?: SavedObjectsCreatePointInTimeFinderDependencies + dependencies?: SavedObjectsCreatePointInTimeFinderDependencies, + internalOptions?: SavedObjectsFindInternalOptions ) => ISavedObjectsPointInTimeFinder; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index e640e1e550a368..305100f8c87b18 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -2730,7 +2730,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { logger: this._logger, client: this, ...dependencies, - /* internalOptions,*/ + internalOptions, }); } diff --git a/x-pack/plugins/spaces/server/plugin.test.ts b/x-pack/plugins/spaces/server/plugin.test.ts index 29f1186bd7c9ac..de448e962ad688 100644 --- a/x-pack/plugins/spaces/server/plugin.test.ts +++ b/x-pack/plugins/spaces/server/plugin.test.ts @@ -54,7 +54,7 @@ describe('Spaces plugin', () => { expect(core.capabilities.registerSwitcher).toHaveBeenCalledTimes(1); }); - it('registers the usage collector', () => { + it('registers the usage collector if the usageCollection plugin is enabled', () => { const initializerContext = coreMock.createPluginInitializerContext({}); const core = coreMock.createSetup() as CoreSetup; const features = featuresPluginMock.createSetup(); diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index c9cb3b9739eee3..2e01e3d419e16e 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -118,7 +118,7 @@ export function bulkGetTestSuiteFactory(context: FtrProviderContext) { after(async () => { await testDataLoader.deleteFtrSpaces(); - await testDataLoader.deleteFtrSavedObjectsData(); + await testDataLoader.deleteFtrSavedObjectsData(); // Should these just be called in the opposite order? }); for (const test of tests) { From e3b3fc121acaa83b0e6420706d0189edbecd4b37 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Fri, 14 Oct 2022 14:50:18 -0400 Subject: [PATCH 14/47] Reordered testDataLoader calls to eliminate error when deleting objects. --- .../saved_object_api_integration/common/suites/bulk_create.ts | 2 +- .../test/saved_object_api_integration/common/suites/bulk_get.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts index bb0bd27ce85d99..87e5d79435f46d 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_create.ts @@ -219,8 +219,8 @@ export function bulkCreateTestSuiteFactory(context: FtrProviderContext) { }); after(async () => { - await testDataLoader.deleteFtrSpaces(); await testDataLoader.deleteFtrSavedObjectsData(); + await testDataLoader.deleteFtrSpaces(); }); const attrs = { attributes: { [NEW_ATTRIBUTE_KEY]: NEW_ATTRIBUTE_VAL } }; diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts index 2e01e3d419e16e..15a51c3db33641 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_get.ts @@ -117,8 +117,8 @@ export function bulkGetTestSuiteFactory(context: FtrProviderContext) { }); after(async () => { + await testDataLoader.deleteFtrSavedObjectsData(); await testDataLoader.deleteFtrSpaces(); - await testDataLoader.deleteFtrSavedObjectsData(); // Should these just be called in the opposite order? }); for (const test of tests) { From 896813b16f4e2a4874e4ced37839ef58b6c03ba4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 20 Oct 2022 13:48:06 -0400 Subject: [PATCH 15/47] Manually adds definitions missed by merge from main. --- .../src/lib/repository.common.test.ts | 4 ++++ .../src/lib/repository.test.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts index 2c6930fc0d97af..62ab8ff1b6b77c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts @@ -126,6 +126,10 @@ export const mockVersionProps = { _seq_no: 1, _primary_term: 1 }; export const mockVersion = encodeHitVersion(mockVersionProps); export const mockTimestamp = '2017-08-14T15:49:14.886Z'; export const mockTimestampFields = { updated_at: mockTimestamp }; +export const mockTimestampFieldsWithCreated = { + updated_at: mockTimestamp, + created_at: mockTimestamp, +}; export const REMOVE_REFS_COUNT = 42; export interface TypeIdTuple { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index bea5fde914f0bb..3f3f5557e18c5c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -113,6 +113,7 @@ import { createGenericNotFoundErrorPayload, expectCreateResult, expectUpdateResult, + mockTimestampFieldsWithCreated, } from './repository.common.test'; const { nodeTypes } = esKuery; From d831567572bc1bdbb81b9090a3472f002dc46b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 20 Oct 2022 17:22:05 -0400 Subject: [PATCH 16/47] Additional manual merge of 'created at' --- .../src/lib/repository.common.test.ts | 4 ++-- .../src/lib/repository.test.ts | 2 +- .../src/lib/repository.ts | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts index 62ab8ff1b6b77c..98d7b84fc3b953 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts @@ -566,7 +566,7 @@ export const getMockBulkCreateResponse = ( namespace, ...(originId && { originId }), references, - ...mockTimestampFields, + ...mockTimestampFieldsWithCreated, migrationVersion: migrationVersion || { [type]: '1.1.1' }, }, ...mockVersionProps, @@ -597,7 +597,7 @@ export const expectCreateResult = (obj: { coreMigrationVersion: KIBANA_VERSION, version: mockVersion, namespaces: obj.namespaces ?? [obj.namespace ?? 'default'], - ...mockTimestampFields, + ...mockTimestampFieldsWithCreated, }); export const getMockBulkUpdateResponse = ( diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index 3f3f5557e18c5c..cf469ebca6397a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -824,7 +824,7 @@ describe('SavedObjectsRepository', () => { const modifiedObj1 = { ...obj1, coreMigrationVersion: '8.0.0' }; await bulkCreateSuccess(client, repository, [modifiedObj1, obj2]); - const docs = [modifiedObj1, obj2].map((x) => ({ ...x, ...mockTimestampFields })); + const docs = [modifiedObj1, obj2].map((x) => ({ ...x, ...mockTimestampFieldsWithCreated })); expectMigrationArgs(docs[0], true, 1); expectMigrationArgs(docs[1], true, 2); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index be044e2bdab253..94fb071db61e5a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -650,6 +650,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { ...(savedObjectNamespace && { namespace: savedObjectNamespace }), ...(savedObjectNamespaces && { namespaces: savedObjectNamespaces }), updated_at: time, + created_at: time, references: object.references || [], originId, }) as SavedObjectSanitizedDoc; From ca77b0a805a79ccc030f3c82a89a66c64708a8f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Tue, 25 Oct 2022 17:29:19 -0400 Subject: [PATCH 17/47] Merges bulk delete with SO extensions. Updates extension test suites with bulk delete tests. --- ...collect_multi_namespace_references.test.ts | 8 +- .../src/lib/internal_bulk_resolve.test.ts | 4 +- .../src/lib/repository.common.test.ts | 74 +- .../lib/repository.security_extension.test.ts | 224 +++- .../lib/repository.spaces_extension.test.ts | 73 ++ .../src/lib/repository.test.ts | 1012 ++++++++--------- .../src/lib/repository.ts | 100 +- .../src/lib/update_objects_spaces.test.ts | 4 +- 8 files changed, 941 insertions(+), 558 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 874b381caea5c5..e60f96e5ab7b8d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -31,7 +31,7 @@ import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-obj import { authMap, enforceError, - mapsAreEqual, + typeMapsAreEqual, setsAreEqual, setupCheckAuthorized, setupCheckUnauthorized, @@ -544,7 +544,7 @@ describe('collectMultiNamespaceReferences', () => { const { typesAndSpaces: actualTypesAndSpaces } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); }); test(`in a non-default state`, async () => { @@ -563,7 +563,7 @@ describe('collectMultiNamespaceReferences', () => { const { typesAndSpaces: actualTypesAndSpaces } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); }); test(`with purpose 'collectMultiNamespaceReferences'`, async () => { @@ -629,7 +629,7 @@ describe('collectMultiNamespaceReferences', () => { const expectedTypesAndSpaces = new Map([[objects[0].type, new Set(['default'])]]); const { typesAndSpaces: actualTypesAndSpaces } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); // Redact is called once per object, but an additional time for object 1 because it has legacy URL aliases in another set of spaces expect(mockSecurityExt.redactNamespaces).toBeCalledTimes(resultObjects.length + 1); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index 196025bcf3fe24..c40382c7e086f5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -35,7 +35,7 @@ import { import { authMap, enforceError, - mapsAreEqual, + typeMapsAreEqual, setsAreEqual, setupCheckAuthorized, setupCheckUnauthorized, @@ -537,7 +537,7 @@ describe('internalBulkResolve', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts index 98d7b84fc3b953..98e316ed40cf69 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts @@ -6,6 +6,7 @@ * Side Public License, v 1. */ +import { mockGetSearchDsl } from './repository.test.mock'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { schema } from '@kbn/config-schema'; import { loggerMock } from '@kbn/logging-mocks'; @@ -24,6 +25,8 @@ import { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-commo import { SavedObjectsBaseOptions, SavedObjectsBulkCreateObject, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, SavedObjectsBulkGetObject, SavedObjectsBulkUpdateObject, SavedObjectsBulkUpdateOptions, @@ -45,7 +48,6 @@ import { } from '@kbn/core-elasticsearch-client-server-mocks'; import { DocumentMigrator } from '@kbn/core-saved-objects-migration-server-internal'; import { SavedObjectsRepository } from './repository'; -import { mockGetSearchDsl } from './repository.test.mock'; export const DEFAULT_SPACE = 'default'; @@ -852,9 +854,77 @@ export function setsAreEqual(setA: Set, setB: Set) { return isEqual(Array(setA).sort(), Array(setB).sort()); } -export function mapsAreEqual(mapA: Map>, mapB: Map>) { +export function typeMapsAreEqual(mapA: Map>, mapB: Map>) { return ( mapA.size === mapB.size && Array.from(mapA.keys()).every((key) => setsAreEqual(mapA.get(key)!, mapB.get(key)!)) ); } + +export function namespaceMapsAreEqual( + mapA: Map, + mapB: Map +) { + return ( + mapA.size === mapB.size && + Array.from(mapA.keys()).every((key) => isEqual(mapA.get(key)?.sort(), mapB.get(key)?.sort())) + ); +} + +export const getMockEsBulkDeleteResponse = ( + registry: SavedObjectTypeRegistry, + objects: TypeIdTuple[], + options?: SavedObjectsBulkDeleteOptions +) => + ({ + items: objects.map(({ type, id }) => ({ + // es response returns more fields than what we're interested in. + delete: { + _id: `${ + registry.isSingleNamespace(type) && options?.namespace ? `${options?.namespace}:` : '' + }${type}:${id}`, + ...mockVersionProps, + result: 'deleted', + }, + })), + } as estypes.BulkResponse); + +export const bulkDeleteSuccess = async ( + client: ElasticsearchClientMock, + repository: SavedObjectsRepository, + registry: SavedObjectTypeRegistry, + objects: SavedObjectsBulkDeleteObject[] = [], + options?: SavedObjectsBulkDeleteOptions, + internalOptions: { + mockMGetResponseObjects?: Array<{ + initialNamespaces: string[] | undefined; + type: string; + id: string; + }>; + } = {} +) => { + const multiNamespaceObjects = objects.filter(({ type }) => { + return registry.isMultiNamespace(type); + }); + + const { mockMGetResponseObjects } = internalOptions; + if (multiNamespaceObjects.length > 0) { + const mockedMGetResponse = mockMGetResponseObjects + ? getMockMgetResponse(registry, mockMGetResponseObjects, options?.namespace) + : getMockMgetResponse(registry, multiNamespaceObjects, options?.namespace); + client.mget.mockResponseOnce(mockedMGetResponse); + } + const mockedEsBulkDeleteResponse = getMockEsBulkDeleteResponse(registry, objects, options); + + client.bulk.mockResponseOnce(mockedEsBulkDeleteResponse); + const result = await repository.bulkDelete(objects, options); + + expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0); + return result; +}; + +export const createBulkDeleteSuccessStatus = ({ type, id }: { type: string; id: string }) => ({ + type, + id, + success: true, +}); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index a9533a302a8bfa..72b9f9fad83e24 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -42,7 +42,7 @@ import { setupRedactPassthrough, MULTI_NAMESPACE_CUSTOM_INDEX_TYPE, setsAreEqual, - mapsAreEqual, + typeMapsAreEqual, authMap, setupEnforceSuccess, updateSuccess, @@ -62,6 +62,8 @@ import { MULTI_NAMESPACE_TYPE, bulkUpdateSuccess, expectUpdateResult, + bulkDeleteSuccess, + createBulkDeleteSuccessStatus, } from './repository.common.test'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; @@ -211,7 +213,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); @@ -365,7 +367,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); @@ -528,7 +530,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); @@ -680,7 +682,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); @@ -788,7 +790,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); @@ -924,7 +926,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); }); @@ -1043,20 +1045,38 @@ describe('SavedObjectsRepository Security Extension', () => { ); }); - // test.only(`calls es search onlywith authorized spaces when partially authorized`, async () => { - // setupCheckPartiallyAuthorized(mockSecurityExt); + // TODO: TEST IN PROGRESS - MOCK CALL ARGUMENT ISSUE + // test.only(`calls es search with only authorized spaces when partially authorized`, async () => { + // // setupCheckPartiallyAuthorized(mockSecurityExt); + // const authRecord: Record = { + // find: { authorizedSpaces: [namespace] }, + // }; + // mockSecurityExt.checkAuthorization.mockResolvedValue({ + // status: 'partially_authorized', + // typeMap: Object.freeze(new Map([[type, authRecord]])), + // }); - // // const result = await repository.find({ type, namespaces: [namespace, 'ns-1'] }); - // await findSuccess(client, repository, { type, namespaces: [namespace, 'ns-1'] }, namespace); - // expect(client.search).toBeCalledWith(expect.objectContaining({})); + // await findSuccess(client, repository, { type, namespaces: [namespace, 'ns-1'] }); - // // expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - // // expect(result).toEqual( - // // expect.objectContaining({ - // // saved_objects: [], - // // total: 0, - // // }) + // const { options: actualOptions }: { options: GetSearchDslOptions } = + // mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap; + + // console.log(`CALLS: ${JSON.stringify(mockGetSearchDsl.mock.calls)}`); + // // console.log( + // // `MAP: ${JSON.stringify( + // // JSON.stringify(mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap.get(type)) + // // )}` // // ); + + // // const actualMap: Map = + // // mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap!; + + // expect(actualOptions.typeToNamespacesMap).toBeDefined(); + // // const actualMap: Map = + // // mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap; + // const expectedMap = new Map(); + // expectedMap.set(type, [namespace]); + // expect(namespaceMapsAreEqual(actualOptions.typeToNamespacesMap!, expectedMap)).toBeTruthy(); // }); test(`returns result of es find when fully authorized`, async () => { @@ -1281,7 +1301,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); }); test(`returns result when authorized`, async () => { @@ -1373,7 +1393,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); // ToDo? reference comparison ok, object is frozen }); @@ -1595,7 +1615,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); @@ -1796,7 +1816,7 @@ describe('SavedObjectsRepository Security Extension', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); @@ -1860,4 +1880,164 @@ describe('SavedObjectsRepository Security Extension', () => { }); }); }); + + describe('#bulkDelete', () => { + beforeEach(() => { + mockDeleteLegacyUrlAliases.mockClear(); + mockDeleteLegacyUrlAliases.mockResolvedValue(); + }); + + const obj1: SavedObjectsBulkUpdateObject = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + }; + const obj2: SavedObjectsBulkUpdateObject = { + type: MULTI_NAMESPACE_TYPE, + id: 'logstash-*', + attributes: { title: 'Test Two' }, + }; + const testObjs = [obj1, obj2]; + const options = { + namespace, + force: true, + }; + const internalOptions = { + mockMGetResponseObjects: [ + { + ...obj1, + initialNamespaces: undefined, + }, + { + ...obj2, + initialNamespaces: [namespace, 'NS-1', 'NS-2'], + }, + ], + }; + + test(`propagates decorated error when checkAuthorization rejects promise`, async () => { + mockSecurityExt.checkAuthorization.mockRejectedValueOnce(checkAuthError); + await expect( + bulkDeleteSuccess(client, repository, registry, testObjs, options) + ).rejects.toThrow(checkAuthError); + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).not.toHaveBeenCalled(); + }); + + test(`propagates decorated error when unauthorized`, async () => { + setupCheckUnauthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + await expect( + bulkDeleteSuccess(client, repository, registry, testObjs, options) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + }); + + test(`returns result when authorized`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + setupRedactPassthrough(mockSecurityExt); + + const result = await bulkDeleteSuccess( + client, + repository, + registry, + testObjs, + options, + internalOptions + ); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(result).toEqual({ + statuses: testObjs.map((obj) => createBulkDeleteSuccessStatus(obj)), + }); + }); + + test(`calls checkAuthorization with type, actions, and namespaces`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await bulkDeleteSuccess(client, repository, registry, testObjs, options, internalOptions); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + const expectedActions = ['bulk_delete']; + const expectedSpaces = new Set(internalOptions.mockMGetResponseObjects[1].initialNamespaces); + const expectedTypes = new Set([obj1.type, obj2.type]); + + const { + actions: actualActions, + spaces: actualSpaces, + types: actualTypes, + } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; + + expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); + expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); + }); + + test(`calls enforceAuthorization with action, type map, and auth map`, async () => { + setupCheckAuthorized(mockSecurityExt); + + await bulkDeleteSuccess(client, repository, registry, testObjs, options, internalOptions); + + expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); + expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledWith( + expect.objectContaining({ + action: 'bulk_delete', + }) + ); + + const expectedTypesAndSpaces = new Map([ + [obj1.type, new Set([namespace])], + [obj2.type, new Set(internalOptions.mockMGetResponseObjects[1].initialNamespaces)], + ]); + + const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = + mockSecurityExt.enforceAuthorization.mock.calls[0][0]; + + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(actualTypeMap).toBe(authMap); + }); + + test(`adds audit event per object when successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceSuccess(mockSecurityExt); + + const objects = [obj1, obj2]; + await bulkDeleteSuccess(client, repository, registry, objects, options); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.DELETE, + savedObject: { type: obj.type, id: obj.id }, + outcome: 'unknown', + }); + }); + }); + + test(`adds audit event per object when not successful`, async () => { + setupCheckAuthorized(mockSecurityExt); + setupEnforceFailure(mockSecurityExt); + + const objects = [obj1, obj2]; + await expect( + bulkDeleteSuccess(client, repository, registry, objects, options) + ).rejects.toThrow(enforceError); + + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledTimes(objects.length); + objects.forEach((obj) => { + expect(mockSecurityExt.addAuditEvent).toHaveBeenCalledWith({ + action: AuditAction.DELETE, + savedObject: { type: obj.type, id: obj.id }, + error: enforceError, + }); + }); + }); + }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts index 517de8b29cc99b..df5cc1d4b4d075 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts @@ -14,6 +14,7 @@ import { mockGetSearchDsl, mockCollectMultiNamespaceReferences, mockInternalBulkResolve, + mockDeleteLegacyUrlAliases, } from './repository.test.mock'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; @@ -54,6 +55,7 @@ import { findSuccess, setupCheckUnauthorized, generateIndexPatternSearchResults, + bulkDeleteSuccess, } from './repository.common.test'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; @@ -797,6 +799,77 @@ describe('SavedObjectsRepository Spaces Extension', () => { expect(mockSpacesExt.getSearchableNamespaces).toBeCalledWith(undefined); // will resolve current space }); }); + + describe('#bulkDelete', () => { + beforeEach(() => { + mockDeleteLegacyUrlAliases.mockClear(); + mockDeleteLegacyUrlAliases.mockResolvedValue(); + }); + + const obj1: SavedObjectsBulkUpdateObject = { + type: 'config', + id: '6.0.0-alpha1', + attributes: { title: 'Test One' }, + }; + const obj2: SavedObjectsBulkUpdateObject = { + type: MULTI_NAMESPACE_TYPE, + id: 'logstash-*', + attributes: { title: 'Test Two' }, + }; + const testObjs = [obj1, obj2]; + const options = { + force: true, + }; + const internalOptions = { + mockMGetResponseObjects: [ + { + ...obj1, + initialNamespaces: undefined, + }, + { + ...obj2, + initialNamespaces: [currentSpace.id, 'NS-1', 'NS-2'], + }, + ], + }; + + test(`throws error if options.namespace is specified`, async () => { + await expect( + bulkDeleteSuccess(client, repository, registry, testObjs, { namespace: 'foo-bar' }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError(ERROR_NAMESPACE_SPECIFIED) + ); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledWith('foo-bar'); + }); + + test(`supplements internal parameters with the current namespace`, async () => { + await bulkDeleteSuccess(client, repository, registry, testObjs, options, internalOptions); + expect(mockSpacesExt.getCurrentNamespace).toBeCalledTimes(1); + expect(mockSpacesExt.getCurrentNamespace).toHaveBeenCalledWith(undefined); + expect(mockSpacesExt.getSearchableNamespaces).not.toHaveBeenCalled(); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.arrayContaining([ + expect.objectContaining({ + delete: expect.objectContaining({ + _id: `${ + currentSpace.expectedNamespace ? `${currentSpace.expectedNamespace}:` : '' + }${obj1.type}:${obj1.id}`, + }), + }), + expect.objectContaining({ + delete: expect.objectContaining({ + _id: `${obj2.type}:${obj2.id}`, + }), + }), + ]), + }), + { maxRetries: 0 } + ); + }); + }); }); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index cf469ebca6397a..9e9d5f8a2fdde4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -45,6 +45,8 @@ import type { SavedObjectsCollectMultiNamespaceReferencesResponse, SavedObjectsUpdateObjectsSpacesObject, SavedObjectsUpdateObjectsSpacesOptions, + SavedObjectsBulkDeleteObject, + SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; import type { SavedObjectsRawDoc, @@ -114,6 +116,9 @@ import { expectCreateResult, expectUpdateResult, mockTimestampFieldsWithCreated, + getMockEsBulkDeleteResponse, + bulkDeleteSuccess, + createBulkDeleteSuccessStatus, } from './repository.common.test'; const { nodeTypes } = esKuery; @@ -1723,516 +1728,503 @@ describe('SavedObjectsRepository', () => { }); }); - // describe('#bulkDelete', () => { - // const obj1: SavedObjectsBulkDeleteObject = { - // type: 'config', - // id: '6.0.0-alpha1', - // }; - // const obj2: SavedObjectsBulkDeleteObject = { - // type: 'index-pattern', - // id: 'logstash-*', - // }; - - // const namespace = 'foo-namespace'; - - // const createNamespaceAwareGetId = (type: string, id: string) => - // `${registry.isSingleNamespace(type) && namespace ? `${namespace}:` : ''}${type}:${id}`; - - // const getMockEsBulkDeleteResponse = ( - // objects: TypeIdTuple[], - // options?: SavedObjectsBulkDeleteOptions - // ) => - // ({ - // items: objects.map(({ type, id }) => ({ - // // es response returns more fields than what we're interested in. - // delete: { - // _id: `${ - // registry.isSingleNamespace(type) && options?.namespace ? `${options?.namespace}:` : '' - // }${type}:${id}`, - // ...mockVersionProps, - // result: 'deleted', - // }, - // })), - // } as estypes.BulkResponse); - - // const repositoryBulkDeleteSuccess = async ( - // objects: SavedObjectsBulkDeleteObject[] = [], - // options?: SavedObjectsBulkDeleteOptions, - // internalOptions: { - // mockMGetResponseWithObject?: { initialNamespaces: string[]; type: string; id: string }; - // } = {} - // ) => { - // const multiNamespaceObjects = objects.filter(({ type }) => { - // return registry.isMultiNamespace(type); - // }); - - // const { mockMGetResponseWithObject } = internalOptions; - // if (multiNamespaceObjects.length > 0) { - // const mockedMGetResponse = mockMGetResponseWithObject - // ? getMockMgetResponse([mockMGetResponseWithObject], options?.namespace) - // : getMockMgetResponse(multiNamespaceObjects, options?.namespace); - // client.mget.mockResponseOnce(mockedMGetResponse); - // } - // const mockedEsBulkDeleteResponse = getMockEsBulkDeleteResponse(objects, options); - - // client.bulk.mockResponseOnce(mockedEsBulkDeleteResponse); - // const result = await savedObjectsRepository.bulkDelete(objects, options); - - // expect(client.mget).toHaveBeenCalledTimes(multiNamespaceObjects?.length ? 1 : 0); - // return result; - // }; - - // // bulk delete calls only has one object for each source -- the action - // const expectClientCallBulkDeleteArgsAction = ( - // objects: TypeIdTuple[], - // { - // method, - // _index = expect.any(String), - // getId = () => expect.any(String), - // overrides = {}, - // }: { - // method: string; - // _index?: string; - // getId?: (type: string, id: string) => string; - // overrides?: Record; - // } - // ) => { - // const body = []; - // for (const { type, id } of objects) { - // body.push({ - // [method]: { - // _index, - // _id: getId(type, id), - // ...overrides, - // }, - // }); - // } - - // expect(client.bulk).toHaveBeenCalledWith( - // expect.objectContaining({ body }), - // expect.anything() - // ); - // }; - - // const createBulkDeleteFailStatus = ({ - // type, - // id, - // error, - // }: { - // type: string; - // id: string; - // error?: ExpectedErrorResult['error']; - // }) => ({ - // type, - // id, - // success: false, - // error: error ?? createBadRequestError(), - // }); - - // const createBulkDeleteSuccessStatus = ({ type, id }: { type: string; id: string }) => ({ - // type, - // id, - // success: true, - // }); - - // // mocks a combination of success, error results for hidden and unknown object object types. - // const repositoryBulkDeleteError = async ( - // obj: SavedObjectsBulkDeleteObject, - // isBulkError: boolean, - // expectedErrorResult: ExpectedErrorResult - // ) => { - // const objects = [obj1, obj, obj2]; - // const mockedBulkDeleteResponse = getMockEsBulkDeleteResponse(objects); - // if (isBulkError) { - // mockGetBulkOperationError.mockReturnValueOnce(undefined); - // mockGetBulkOperationError.mockReturnValueOnce(expectedErrorResult.error as Payload); - // } - // client.bulk.mockResponseOnce(mockedBulkDeleteResponse); - - // const result = await savedObjectsRepository.bulkDelete(objects); - // expect(client.bulk).toHaveBeenCalled(); - // expect(result).toEqual({ - // statuses: [ - // createBulkDeleteSuccessStatus(obj1), - // createBulkDeleteFailStatus({ ...obj, error: expectedErrorResult.error }), - // createBulkDeleteSuccessStatus(obj2), - // ], - // }); - // }; - - // const expectClientCallArgsAction = ( - // objects: TypeIdTuple[], - // { - // method, - // _index = expect.any(String), - // getId = () => expect.any(String), - // overrides = {}, - // }: { - // method: string; - // _index?: string; - // getId?: (type: string, id: string) => string; - // overrides?: Record; - // } - // ) => { - // const body = []; - // for (const { type, id } of objects) { - // body.push({ - // [method]: { - // _index, - // _id: getId(type, id), - // ...overrides, - // }, - // }); - // } - // expect(client.bulk).toHaveBeenCalledWith( - // expect.objectContaining({ body }), - // expect.anything() - // ); - // }; - - // const bulkDeleteMultiNamespaceError = async ( - // [obj1, _obj, obj2]: SavedObjectsBulkDeleteObject[], - // options: SavedObjectsBulkDeleteOptions | undefined, - // mgetResponse: estypes.MgetResponse, - // mgetOptions?: { statusCode?: number } - // ) => { - // const getId = (type: string, id: string) => `${options?.namespace}:${type}:${id}`; - // // mock the response for the not found doc - // client.mget.mockResponseOnce(mgetResponse, { statusCode: mgetOptions?.statusCode }); - // // get a mocked response for the valid docs - // const bulkResponse = getMockEsBulkDeleteResponse([obj1, obj2], { namespace }); - // client.bulk.mockResponseOnce(bulkResponse); - - // const result = await savedObjectsRepository.bulkDelete([obj1, _obj, obj2], options); - // expect(client.bulk).toHaveBeenCalledTimes(1); - // expect(client.mget).toHaveBeenCalledTimes(1); - - // expectClientCallArgsAction([obj1, obj2], { method: 'delete', getId }); - // expect(result).toEqual({ - // statuses: [ - // createBulkDeleteSuccessStatus(obj1), - // { ...expectErrorNotFound(_obj), success: false }, - // createBulkDeleteSuccessStatus(obj2), - // ], - // }); - // }; - - // beforeEach(() => { - // mockDeleteLegacyUrlAliases.mockClear(); - // mockDeleteLegacyUrlAliases.mockResolvedValue(); - // }); - - // describe('client calls', () => { - // it(`should use the ES bulk action by default`, async () => { - // await repositoryBulkDeleteSuccess([obj1, obj2]); - // expect(client.bulk).toHaveBeenCalled(); - // }); - - // it(`should use the ES mget action before bulk action for any types that are multi-namespace`, async () => { - // const objects = [obj1, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }]; - // await repositoryBulkDeleteSuccess(objects); - // expect(client.bulk).toHaveBeenCalled(); - // expect(client.mget).toHaveBeenCalled(); - - // const docs = [ - // expect.objectContaining({ _id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${obj2.id}` }), - // ]; - // expect(client.mget).toHaveBeenCalledWith( - // expect.objectContaining({ body: { docs } }), - // expect.anything() - // ); - // }); - - // it(`should not use the ES bulk action when there are no valid documents to delete`, async () => { - // const objects = [obj1, obj2].map((x) => ({ ...x, type: 'unknownType' })); - // await savedObjectsRepository.bulkDelete(objects); - // expect(client.bulk).toHaveBeenCalledTimes(0); - // }); - - // it(`formats the ES request`, async () => { - // const getId = createNamespaceAwareGetId; - // await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); - // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - // }); - - // it(`formats the ES request for any types that are multi-namespace`, async () => { - // const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - // const getId = createNamespaceAwareGetId; - // await repositoryBulkDeleteSuccess([obj1, _obj2], { namespace }); - // expectClientCallBulkDeleteArgsAction([obj1, _obj2], { method: 'delete', getId }); - // }); - - // it(`defaults to a refresh setting of wait_for`, async () => { - // await repositoryBulkDeleteSuccess([obj1, obj2]); - // expect(client.bulk).toHaveBeenCalledWith( - // expect.objectContaining({ refresh: 'wait_for' }), - // expect.anything() - // ); - // }); - - // it(`does not include the version of the existing document when not using a multi-namespace type`, async () => { - // const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; - // await repositoryBulkDeleteSuccess(objects); - // expectClientCallBulkDeleteArgsAction(objects, { method: 'delete' }); - // }); - - // it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { - // const getId = createNamespaceAwareGetId; - // await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); - // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - // }); - - // it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { - // const getId = (type: string, id: string) => `${type}:${id}`; - // await repositoryBulkDeleteSuccess([obj1, obj2]); - // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - // }); - - // it(`normalizes options.namespace from 'default' to undefined`, async () => { - // const getId = (type: string, id: string) => `${type}:${id}`; - // await repositoryBulkDeleteSuccess([obj1, obj2], { namespace: 'default' }); - // expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); - // }); - - // it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { - // const getId = (type: string, id: string) => `${type}:${id}`; // not expecting namespace prefix; - // const _obj1 = { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }; - // const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; - - // await repositoryBulkDeleteSuccess([_obj1, _obj2], { namespace }); - // expectClientCallBulkDeleteArgsAction([_obj1, _obj2], { method: 'delete', getId }); - // }); - // }); - - // describe('legacy URL aliases', () => { - // it(`doesn't delete legacy URL aliases for single-namespace object types`, async () => { - // await repositoryBulkDeleteSuccess([obj1, obj2]); - // expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); - // }); - - // it(`deletes legacy URL aliases for multi-namespace object types (all spaces)`, async () => { - // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - // const internalOptions = { - // mockMGetResponseWithObject: { - // ...testObject, - // initialNamespaces: [ALL_NAMESPACES_STRING], - // }, - // }; - // await repositoryBulkDeleteSuccess( - // [testObject], - // { namespace, force: true }, - // internalOptions - // ); - // expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( - // expect.objectContaining({ - // type: MULTI_NAMESPACE_TYPE, - // id: testObject.id, - // namespaces: [], - // deleteBehavior: 'exclusive', - // }) - // ); - // }); - - // it(`deletes legacy URL aliases for multi-namespace object types (specific space)`, async () => { - // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - // const internalOptions = { - // mockMGetResponseWithObject: { - // ...testObject, - // initialNamespaces: [namespace], - // }, - // }; - // // specifically test against the current namespace - // await repositoryBulkDeleteSuccess([testObject], { namespace }, internalOptions); - // expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( - // expect.objectContaining({ - // type: MULTI_NAMESPACE_TYPE, - // id: testObject.id, - // namespaces: [namespace], - // deleteBehavior: 'inclusive', - // }) - // ); - // }); - - // it(`deletes legacy URL aliases for multi-namespace object types shared to many specific spaces`, async () => { - // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - // const initialTestObjectNamespaces = [namespace, 'bar-namespace']; - // const internalOptions = { - // mockMGetResponseWithObject: { - // ...testObject, - // initialNamespaces: initialTestObjectNamespaces, - // }, - // }; - // // specifically test against named spaces ('*' is handled specifically, this assures we also take care of named spaces) - // await repositoryBulkDeleteSuccess( - // [testObject], - // { namespace, force: true }, - // internalOptions - // ); - // expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( - // expect.objectContaining({ - // type: MULTI_NAMESPACE_TYPE, - // id: testObject.id, - // namespaces: initialTestObjectNamespaces, - // deleteBehavior: 'inclusive', - // }) - // ); - // }); - - // it(`logs a message when deleteLegacyUrlAliases returns an error`, async () => { - // const testObject = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: obj1.id }; - - // client.mget.mockResolvedValueOnce( - // elasticsearchClientMock.createSuccessTransportRequestPromise( - // getMockMgetResponse([testObject], namespace) - // ) - // ); - // const mockedBulkResponse = getMockEsBulkDeleteResponse([testObject], { namespace }); - // client.bulk.mockResolvedValueOnce(mockedBulkResponse); - - // mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); - - // await savedObjectsRepository.bulkDelete([testObject], { namespace }); - - // expect(client.mget).toHaveBeenCalledTimes(1); - // expect(logger.error).toHaveBeenCalledTimes(1); - // expect(logger.error).toHaveBeenCalledWith( - // 'Unable to delete aliases when deleting an object: Oh no!' - // ); - // }); - // }); - - // describe('errors', () => { - // it(`throws an error when options.namespace is '*'`, async () => { - // await expect( - // savedObjectsRepository.bulkDelete([obj1], { namespace: ALL_NAMESPACES_STRING }) - // ).rejects.toThrowError(createBadRequestError('"options.namespace" cannot be "*"')); - // }); - - // it(`throws an error when client bulk response is not defined`, async () => { - // client.mget.mockResolvedValueOnce( - // elasticsearchClientMock.createSuccessTransportRequestPromise( - // getMockMgetResponse([obj1], namespace) - // ) - // ); - // const mockedBulkResponse = undefined; - // // we have to cast here to test the assumption we always get a response. - // client.bulk.mockResponseOnce(mockedBulkResponse as unknown as estypes.BulkResponse); - // await expect(savedObjectsRepository.bulkDelete([obj1], { namespace })).rejects.toThrowError( - // 'Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined' - // ); - // }); - - // it(`returns an error for the object when the object's type is invalid`, async () => { - // const unknownObjType = { ...obj1, type: 'unknownType' }; - // await repositoryBulkDeleteError( - // unknownObjType, - // false, - // expectErrorInvalidType(unknownObjType) - // ); - // }); - - // it(`returns an error for an object when the object's type is hidden`, async () => { - // const hiddenObject = { ...obj1, type: HIDDEN_TYPE }; - // await repositoryBulkDeleteError(hiddenObject, false, expectErrorInvalidType(hiddenObject)); - // }); - - // it(`returns an error when ES is unable to find the document during mget`, async () => { - // const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; - // const mgetResponse = getMockMgetResponse([notFoundObj], namespace); - // await bulkDeleteMultiNamespaceError([obj1, notFoundObj, obj2], { namespace }, mgetResponse); - // }); - - // it(`returns an error when ES is unable to find the index during mget`, async () => { - // const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; - // await bulkDeleteMultiNamespaceError( - // [obj1, notFoundObj, obj2], - // { namespace }, - // {} as estypes.MgetResponse, - // { - // statusCode: 404, - // } - // ); - // }); - - // it(`returns an error when the type is multi-namespace and the document exists, but not in this namespace`, async () => { - // const obj = { - // type: MULTI_NAMESPACE_ISOLATED_TYPE, - // id: 'three', - // namespace: 'bar-namespace', - // }; - // const mgetResponse = getMockMgetResponse([obj], namespace); - // await bulkDeleteMultiNamespaceError([obj1, obj, obj2], { namespace }, mgetResponse); - // }); - - // it(`returns an error when the type is multi-namespace and the document has multiple namespaces and the force option is not enabled`, async () => { - // const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; - // const internalOptions = { - // mockMGetResponseWithObject: { - // ...testObject, - // initialNamespaces: [namespace, 'bar-namespace'], - // }, - // }; - // const result = await repositoryBulkDeleteSuccess( - // [testObject], - // { namespace }, - // internalOptions - // ); - // expect(result.statuses[0]).toStrictEqual( - // createBulkDeleteFailStatus({ - // ...testObject, - // error: createBadRequestError( - // 'Unable to delete saved object that exists in multiple namespaces, use the "force" option to delete it anyway' - // ), - // }) - // ); - // }); - - // it(`returns an error when the type is multi-namespace and the document has all namespaces and the force option is not enabled`, async () => { - // const testObject = { ...obj1, type: ALL_NAMESPACES_STRING }; - // const internalOptions = { - // mockMGetResponseWithObject: { - // ...testObject, - // initialNamespaces: [namespace, 'bar-namespace'], - // }, - // }; - // const result = await repositoryBulkDeleteSuccess( - // [testObject], - // { namespace }, - // internalOptions - // ); - // expect(result.statuses[0]).toStrictEqual( - // createBulkDeleteFailStatus({ - // ...testObject, - // error: createBadRequestError("Unsupported saved object type: '*'"), - // }) - // ); - // }); - // }); - - // describe('returns', () => { - // it(`returns early for empty objects argument`, async () => { - // await savedObjectsRepository.bulkDelete([], { namespace }); - // expect(client.bulk).toHaveBeenCalledTimes(0); - // }); - - // it(`formats the ES response`, async () => { - // const response = await repositoryBulkDeleteSuccess([obj1, obj2], { namespace }); - // expect(response).toEqual({ - // statuses: [obj1, obj2].map(createBulkDeleteSuccessStatus), - // }); - // }); - - // it(`handles a mix of successful deletes and errors`, async () => { - // const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; - // await bulkDeleteMultiNamespaceError( - // [obj1, notFoundObj, obj2], - // { namespace }, - // {} as estypes.MgetResponse, - // { statusCode: 404 } - // ); - // }); - // }); - // }); + describe('#bulkDelete', () => { + const obj1: SavedObjectsBulkDeleteObject = { + type: 'config', + id: '6.0.0-alpha1', + }; + const obj2: SavedObjectsBulkDeleteObject = { + type: 'index-pattern', + id: 'logstash-*', + }; + + const namespace = 'foo-namespace'; + + const createNamespaceAwareGetId = (type: string, id: string) => + `${registry.isSingleNamespace(type) && namespace ? `${namespace}:` : ''}${type}:${id}`; + + // bulk delete calls only has one object for each source -- the action + const expectClientCallBulkDeleteArgsAction = ( + objects: TypeIdTuple[], + { + method, + _index = expect.any(String), + getId = () => expect.any(String), + overrides = {}, + }: { + method: string; + _index?: string; + getId?: (type: string, id: string) => string; + overrides?: Record; + } + ) => { + const body = []; + for (const { type, id } of objects) { + body.push({ + [method]: { + _index, + _id: getId(type, id), + ...overrides, + }, + }); + } + + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }; + + const createBulkDeleteFailStatus = ({ + type, + id, + error, + }: { + type: string; + id: string; + error?: ExpectedErrorResult['error']; + }) => ({ + type, + id, + success: false, + error: error ?? SavedObjectsErrorHelpers.createBadRequestError(), + }); + + // mocks a combination of success, error results for hidden and unknown object object types. + const repositoryBulkDeleteError = async ( + obj: SavedObjectsBulkDeleteObject, + isBulkError: boolean, + expectedErrorResult: ExpectedErrorResult + ) => { + const objects = [obj1, obj, obj2]; + const mockedBulkDeleteResponse = getMockEsBulkDeleteResponse(registry, objects); + if (isBulkError) { + mockGetBulkOperationError.mockReturnValueOnce(undefined); + mockGetBulkOperationError.mockReturnValueOnce(expectedErrorResult.error as Payload); + } + client.bulk.mockResponseOnce(mockedBulkDeleteResponse); + + const result = await repository.bulkDelete(objects); + expect(client.bulk).toHaveBeenCalled(); + expect(result).toEqual({ + statuses: [ + createBulkDeleteSuccessStatus(obj1), + createBulkDeleteFailStatus({ ...obj, error: expectedErrorResult.error }), + createBulkDeleteSuccessStatus(obj2), + ], + }); + }; + + const expectClientCallArgsAction = ( + objects: TypeIdTuple[], + { + method, + _index = expect.any(String), + getId = () => expect.any(String), + overrides = {}, + }: { + method: string; + _index?: string; + getId?: (type: string, id: string) => string; + overrides?: Record; + } + ) => { + const body = []; + for (const { type, id } of objects) { + body.push({ + [method]: { + _index, + _id: getId(type, id), + ...overrides, + }, + }); + } + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ body }), + expect.anything() + ); + }; + + const bulkDeleteMultiNamespaceError = async ( + [obj1, _obj, obj2]: SavedObjectsBulkDeleteObject[], + options: SavedObjectsBulkDeleteOptions | undefined, + mgetResponse: estypes.MgetResponse, + mgetOptions?: { statusCode?: number } + ) => { + const getId = (type: string, id: string) => `${options?.namespace}:${type}:${id}`; + // mock the response for the not found doc + client.mget.mockResponseOnce(mgetResponse, { statusCode: mgetOptions?.statusCode }); + // get a mocked response for the valid docs + const bulkResponse = getMockEsBulkDeleteResponse(registry, [obj1, obj2], { namespace }); + client.bulk.mockResponseOnce(bulkResponse); + + const result = await repository.bulkDelete([obj1, _obj, obj2], options); + expect(client.bulk).toHaveBeenCalledTimes(1); + expect(client.mget).toHaveBeenCalledTimes(1); + + expectClientCallArgsAction([obj1, obj2], { method: 'delete', getId }); + expect(result).toEqual({ + statuses: [ + createBulkDeleteSuccessStatus(obj1), + { ...expectErrorNotFound(_obj), success: false }, + createBulkDeleteSuccessStatus(obj2), + ], + }); + }; + + beforeEach(() => { + mockDeleteLegacyUrlAliases.mockClear(); + mockDeleteLegacyUrlAliases.mockResolvedValue(); + }); + + describe('client calls', () => { + it(`should use the ES bulk action by default`, async () => { + await bulkDeleteSuccess(client, repository, registry, [obj1, obj2]); + expect(client.bulk).toHaveBeenCalled(); + }); + + it(`should use the ES mget action before bulk action for any types that are multi-namespace`, async () => { + const objects = [obj1, { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }]; + await bulkDeleteSuccess(client, repository, registry, objects); + expect(client.bulk).toHaveBeenCalled(); + expect(client.mget).toHaveBeenCalled(); + + const docs = [ + expect.objectContaining({ _id: `${MULTI_NAMESPACE_ISOLATED_TYPE}:${obj2.id}` }), + ]; + expect(client.mget).toHaveBeenCalledWith( + expect.objectContaining({ body: { docs } }), + expect.anything() + ); + }); + + it(`should not use the ES bulk action when there are no valid documents to delete`, async () => { + const objects = [obj1, obj2].map((x) => ({ ...x, type: 'unknownType' })); + await repository.bulkDelete(objects); + expect(client.bulk).toHaveBeenCalledTimes(0); + }); + + it(`formats the ES request`, async () => { + const getId = createNamespaceAwareGetId; + await bulkDeleteSuccess(client, repository, registry, [obj1, obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`formats the ES request for any types that are multi-namespace`, async () => { + const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; + const getId = createNamespaceAwareGetId; + await bulkDeleteSuccess(client, repository, registry, [obj1, _obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([obj1, _obj2], { method: 'delete', getId }); + }); + + it(`defaults to a refresh setting of wait_for`, async () => { + await bulkDeleteSuccess(client, repository, registry, [obj1, obj2]); + expect(client.bulk).toHaveBeenCalledWith( + expect.objectContaining({ refresh: 'wait_for' }), + expect.anything() + ); + }); + + it(`does not include the version of the existing document when not using a multi-namespace type`, async () => { + const objects = [obj1, { ...obj2, type: NAMESPACE_AGNOSTIC_TYPE }]; + await bulkDeleteSuccess(client, repository, registry, objects); + expectClientCallBulkDeleteArgsAction(objects, { method: 'delete' }); + }); + + it(`prepends namespace to the id when providing namespace for single-namespace type`, async () => { + const getId = createNamespaceAwareGetId; + await bulkDeleteSuccess(client, repository, registry, [obj1, obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`doesn't prepend namespace to the id when providing no namespace for single-namespace type`, async () => { + const getId = (type: string, id: string) => `${type}:${id}`; + await bulkDeleteSuccess(client, repository, registry, [obj1, obj2]); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`normalizes options.namespace from 'default' to undefined`, async () => { + const getId = (type: string, id: string) => `${type}:${id}`; + await bulkDeleteSuccess(client, repository, registry, [obj1, obj2], { + namespace: 'default', + }); + expectClientCallBulkDeleteArgsAction([obj1, obj2], { method: 'delete', getId }); + }); + + it(`doesn't prepend namespace to the id when not using single-namespace type`, async () => { + const getId = (type: string, id: string) => `${type}:${id}`; // not expecting namespace prefix; + const _obj1 = { ...obj1, type: NAMESPACE_AGNOSTIC_TYPE }; + const _obj2 = { ...obj2, type: MULTI_NAMESPACE_ISOLATED_TYPE }; + + await bulkDeleteSuccess(client, repository, registry, [_obj1, _obj2], { namespace }); + expectClientCallBulkDeleteArgsAction([_obj1, _obj2], { method: 'delete', getId }); + }); + }); + + describe('legacy URL aliases', () => { + it(`doesn't delete legacy URL aliases for single-namespace object types`, async () => { + await bulkDeleteSuccess(client, repository, registry, [obj1, obj2]); + expect(mockDeleteLegacyUrlAliases).not.toHaveBeenCalled(); + }); + + it(`deletes legacy URL aliases for multi-namespace object types (all spaces)`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const internalOptions = { + mockMGetResponseObjects: [ + { + ...testObject, + initialNamespaces: [ALL_NAMESPACES_STRING], + }, + ], + }; + await bulkDeleteSuccess( + client, + repository, + registry, + [testObject], + { namespace, force: true }, + internalOptions + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id: testObject.id, + namespaces: [], + deleteBehavior: 'exclusive', + }) + ); + }); + + it(`deletes legacy URL aliases for multi-namespace object types (specific space)`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const internalOptions = { + mockMGetResponseObjects: [ + { + ...testObject, + initialNamespaces: [namespace], + }, + ], + }; + // specifically test against the current namespace + await bulkDeleteSuccess( + client, + repository, + registry, + [testObject], + { namespace }, + internalOptions + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id: testObject.id, + namespaces: [namespace], + deleteBehavior: 'inclusive', + }) + ); + }); + + it(`deletes legacy URL aliases for multi-namespace object types shared to many specific spaces`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const initialTestObjectNamespaces = [namespace, 'bar-namespace']; + const internalOptions = { + mockMGetResponseObjects: [ + { + ...testObject, + initialNamespaces: initialTestObjectNamespaces, + }, + ], + }; + // specifically test against named spaces ('*' is handled specifically, this assures we also take care of named spaces) + await bulkDeleteSuccess( + client, + repository, + registry, + [testObject], + { namespace, force: true }, + internalOptions + ); + expect(mockDeleteLegacyUrlAliases).toHaveBeenCalledWith( + expect.objectContaining({ + type: MULTI_NAMESPACE_TYPE, + id: testObject.id, + namespaces: initialTestObjectNamespaces, + deleteBehavior: 'inclusive', + }) + ); + }); + + it(`logs a message when deleteLegacyUrlAliases returns an error`, async () => { + const testObject = { type: MULTI_NAMESPACE_ISOLATED_TYPE, id: obj1.id }; + + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + getMockMgetResponse(registry, [testObject], namespace) + ) + ); + const mockedBulkResponse = getMockEsBulkDeleteResponse(registry, [testObject], { + namespace, + }); + client.bulk.mockResolvedValueOnce(mockedBulkResponse); + + mockDeleteLegacyUrlAliases.mockRejectedValueOnce(new Error('Oh no!')); + + await repository.bulkDelete([testObject], { namespace }); + + expect(client.mget).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith( + 'Unable to delete aliases when deleting an object: Oh no!' + ); + }); + }); + + describe('errors', () => { + it(`throws an error when options.namespace is '*'`, async () => { + await expect( + repository.bulkDelete([obj1], { namespace: ALL_NAMESPACES_STRING }) + ).rejects.toThrowError( + SavedObjectsErrorHelpers.createBadRequestError('"options.namespace" cannot be "*"') + ); + }); + + it(`throws an error when client bulk response is not defined`, async () => { + client.mget.mockResolvedValueOnce( + elasticsearchClientMock.createSuccessTransportRequestPromise( + getMockMgetResponse(registry, [obj1], namespace) + ) + ); + const mockedBulkResponse = undefined; + // we have to cast here to test the assumption we always get a response. + client.bulk.mockResponseOnce(mockedBulkResponse as unknown as estypes.BulkResponse); + await expect(repository.bulkDelete([obj1], { namespace })).rejects.toThrowError( + 'Unexpected error in bulkDelete saved objects: bulkDeleteResponse is undefined' + ); + }); + + it(`returns an error for the object when the object's type is invalid`, async () => { + const unknownObjType = { ...obj1, type: 'unknownType' }; + await repositoryBulkDeleteError( + unknownObjType, + false, + expectErrorInvalidType(unknownObjType) + ); + }); + + it(`returns an error for an object when the object's type is hidden`, async () => { + const hiddenObject = { ...obj1, type: HIDDEN_TYPE }; + await repositoryBulkDeleteError(hiddenObject, false, expectErrorInvalidType(hiddenObject)); + }); + + it(`returns an error when ES is unable to find the document during mget`, async () => { + const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + const mgetResponse = getMockMgetResponse(registry, [notFoundObj], namespace); + await bulkDeleteMultiNamespaceError([obj1, notFoundObj, obj2], { namespace }, mgetResponse); + }); + + it(`returns an error when ES is unable to find the index during mget`, async () => { + const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + await bulkDeleteMultiNamespaceError( + [obj1, notFoundObj, obj2], + { namespace }, + {} as estypes.MgetResponse, + { + statusCode: 404, + } + ); + }); + + it(`returns an error when the type is multi-namespace and the document exists, but not in this namespace`, async () => { + const obj = { + type: MULTI_NAMESPACE_ISOLATED_TYPE, + id: 'three', + namespace: 'bar-namespace', + }; + const mgetResponse = getMockMgetResponse(registry, [obj], namespace); + await bulkDeleteMultiNamespaceError([obj1, obj, obj2], { namespace }, mgetResponse); + }); + + it(`returns an error when the type is multi-namespace and the document has multiple namespaces and the force option is not enabled`, async () => { + const testObject = { ...obj1, type: MULTI_NAMESPACE_TYPE }; + const internalOptions = { + mockMGetResponseObjects: [ + { + ...testObject, + initialNamespaces: [namespace, 'bar-namespace'], + }, + ], + }; + const result = await bulkDeleteSuccess( + client, + repository, + registry, + [testObject], + { namespace }, + internalOptions + ); + expect(result.statuses[0]).toStrictEqual( + createBulkDeleteFailStatus({ + ...testObject, + error: createBadRequestErrorPayload( + 'Unable to delete saved object that exists in multiple namespaces, use the `force` option to delete it anyway' + ), + }) + ); + }); + + it(`returns an error when the type is multi-namespace and the document has all namespaces and the force option is not enabled`, async () => { + const testObject = { ...obj1, type: ALL_NAMESPACES_STRING }; + const internalOptions = { + mockMGetResponseObjects: [ + { + ...testObject, + initialNamespaces: [namespace, 'bar-namespace'], + }, + ], + }; + const result = await bulkDeleteSuccess( + client, + repository, + registry, + [testObject], + { namespace }, + internalOptions + ); + expect(result.statuses[0]).toStrictEqual( + createBulkDeleteFailStatus({ + ...testObject, + error: createBadRequestErrorPayload("Unsupported saved object type: '*'"), + }) + ); + }); + }); + + describe('returns', () => { + it(`returns early for empty objects argument`, async () => { + await repository.bulkDelete([], { namespace }); + expect(client.bulk).toHaveBeenCalledTimes(0); + }); + + it(`formats the ES response`, async () => { + const response = await bulkDeleteSuccess(client, repository, registry, [obj1, obj2], { + namespace, + }); + expect(response).toEqual({ + statuses: [obj1, obj2].map(createBulkDeleteSuccessStatus), + }); + }); + + it(`handles a mix of successful deletes and errors`, async () => { + const notFoundObj = { ...obj1, type: MULTI_NAMESPACE_ISOLATED_TYPE, found: false }; + await bulkDeleteMultiNamespaceError( + [obj1, notFoundObj, obj2], + { namespace }, + {} as estypes.MgetResponse, + { statusCode: 404 } + ); + }); + }); + }); describe('#checkConflicts', () => { const obj1 = { type: 'dashboard', id: 'one' }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 94fb071db61e5a..50483b7eea67cd 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -1097,7 +1097,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { type, error: errorContent( SavedObjectsErrorHelpers.createBadRequestError( - `Unable to delete saved object that exists in multiple namespaces, use the "force" option to delete it anyway` + 'Unable to delete saved object that exists in multiple namespaces, use the `force` option to delete it anyway' ) ), }, @@ -1126,7 +1126,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { options: SavedObjectsBulkDeleteOptions = {} ): Promise { const { refresh = DEFAULT_REFRESH_SETTING, force } = options; - const namespace = normalizeNamespace(options.namespace); + const namespace = this.getCurrentNamespace(options.namespace); const expectedBulkGetResults = this.presortObjectsByNamespaceType(objects); const multiNamespaceDocsResponse = await this.preflightCheckForBulkDelete({ expectedBulkGetResults, @@ -1134,6 +1134,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }); const bulkDeleteParams: BulkDeleteParams[] = []; + // First round of filtering (Left: object doesn't exist/doesn't exist in namespace, Right: good to proceed) const expectedBulkDeleteMultiNamespaceDocsResults = this.getExpectedBulkDeleteMultiNamespaceDocsResults({ expectedBulkGetResults, @@ -1141,21 +1142,83 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { namespace, force, }); - // bulk up the bulkDeleteParams - expectedBulkDeleteMultiNamespaceDocsResults.map((expectedResult) => { - if (isRight(expectedResult)) { - bulkDeleteParams.push({ - delete: { - _id: this._serializer.generateRawId( - namespace, - expectedResult.value.type, - expectedResult.value.id - ), - _index: this.getIndexForType(expectedResult.value.type), - ...getExpectedVersionProperties(undefined), - }, + + const validObjects = expectedBulkDeleteMultiNamespaceDocsResults.filter(isRight); + if (validObjects.length === 0) { + // We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. + const savedObjects = expectedBulkDeleteMultiNamespaceDocsResults + .filter(isLeft) + .map((expectedResult) => { + return { ...expectedResult.value, success: false }; }); + return { statuses: [...savedObjects] }; + } + + // check auth + const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); + // const getNamespaceString = (objectNamespace?: string) => objectNamespace ?? namespaceString; + const typesAndSpaces = new Map>(); + const spacesToAuthorize = new Set([namespaceString]); // Always check authZ for the active space + if (this._securityExtension) { + for (const { value } of validObjects) { + const { type, esRequestIndex: index } = value; + const preflightResult = + index !== undefined ? multiNamespaceDocsResponse?.body.docs[index] : undefined; + + const spacesToEnforce = typesAndSpaces.get(type) ?? new Set([namespaceString]); // Always enforce authZ for the active space + // objectNamespaces?.forEach((objectSpace) => { + // const objectNamespaceString = getNamespaceString(objectSpace); + // spacesToEnforce.add(objectNamespaceString); + // // spacesToAuthorize.add(objectNamespaceString); + // }); + // typesAndSpaces.set(type, spacesToEnforce); + // spacesToAuthorize.add(namespaceString); + + // @ts-expect-error MultiGetHit._source is optional + for (const space of preflightResult?._source?.namespaces ?? []) { + spacesToEnforce.add(space); + spacesToAuthorize.add(space); // existing namespaces are included + } + typesAndSpaces.set(type, spacesToEnforce); } + } + + const authorizationResult = await this._securityExtension?.checkAuthorization({ + types: new Set(typesAndSpaces.keys()), + spaces: spacesToAuthorize, + actions: ['bulk_delete'], + }); + if (authorizationResult) { + this._securityExtension!.enforceAuthorization({ + typesAndSpaces, + action: 'bulk_delete', + typeMap: authorizationResult.typeMap, + auditCallback: (error) => { + for (const { value } of validObjects) { + this._securityExtension!.addAuditEvent({ + action: AuditAction.DELETE, + savedObject: { type: value.type, id: value.id }, + error, + ...(!error && { outcome: 'unknown' }), // If authorization was a success, the outcome is unknown because the delete operation has not occurred yet + }); + } + }, + }); + } + + // bulk up the bulkDeleteParams + validObjects.map((expectedResult) => { + bulkDeleteParams.push({ + delete: { + _id: this._serializer.generateRawId( + namespace, + expectedResult.value.type, + expectedResult.value.id + ), + _index: this.getIndexForType(expectedResult.value.type), + ...getExpectedVersionProperties(undefined), + }, + }); }); const bulkDeleteResponse = bulkDeleteParams.length @@ -1226,7 +1289,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }); // Delete aliases if necessary, ensuring we don't have too many concurrent operations running. - const mapper = async ({ type, id, namespaces, deleteBehavior }: ObjectToDeleteAliasesFor) => + const mapper = async ({ type, id, namespaces, deleteBehavior }: ObjectToDeleteAliasesFor) => { await deleteLegacyUrlAliases({ mappings: this._mappings, registry: this._registry, @@ -1239,6 +1302,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }).catch((err) => { this._logger.error(`Unable to delete aliases when deleting an object: ${err.message}`); }); + }; await pMap(objectsToDeleteAliasesFor, mapper, { concurrency: MAX_CONCURRENT_ALIAS_DELETIONS }); return { statuses: [...savedObjects] }; @@ -1435,10 +1499,14 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { // This ensures that the query DSL can filter only for object types that the user is authorized to access for a given space const { authorizedSpaces, isGloballyAuthorized } = entry.find; typeToNamespacesMap.set(objType, isGloballyAuthorized ? namespaces : authorizedSpaces); + // TODO: REMOVE console.log(`AUTHZd: ${authorizedSpaces}`); + // TODO: REMOVE console.log(`REPO MAP: ${JSON.stringify(typeToNamespacesMap.get(objType))}`); } } } + // TODO: REMOVE console.log(`getSearchDsl Mock Check: ${getSearchDsl.mock}`); + const esOptions = { // If `pit` is provided, we drop the `index`, otherwise ES returns 400. index: pit ? undefined : this.getIndicesForTypes(allowedTypes), diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 77974b9f71b881..aa848e0c40f933 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -30,7 +30,7 @@ import { authMap, checkAuthError, enforceError, - mapsAreEqual, + typeMapsAreEqual, setsAreEqual, setupCheckAuthorized, setupCheckUnauthorized, @@ -804,7 +804,7 @@ describe('#updateObjectsSpaces', () => { const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = mockSecurityExt.enforceAuthorization.mock.calls[0][0]; - expect(mapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); + expect(typeMapsAreEqual(actualTypesAndSpaces, expectedTypesAndSpaces)).toBeTruthy(); expect(actualTypeMap).toBe(authMap); }); From 087c3a564827cf184a9998e93d71138869e29e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Wed, 26 Oct 2022 09:48:02 -0400 Subject: [PATCH 18/47] Adds test case for partially authorized in find --- .../lib/repository.security_extension.test.ts | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index 72b9f9fad83e24..18d152be81954d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -25,6 +25,7 @@ import { ISavedObjectsSecurityExtension, AuditAction, SavedObjectsRawDocSource, + AuthorizationTypeEntry, } from '@kbn/core-saved-objects-server'; import { kibanaMigratorMock } from '../mocks'; import { @@ -64,6 +65,7 @@ import { expectUpdateResult, bulkDeleteSuccess, createBulkDeleteSuccessStatus, + namespaceMapsAreEqual, } from './repository.common.test'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; @@ -1045,39 +1047,28 @@ describe('SavedObjectsRepository Security Extension', () => { ); }); - // TODO: TEST IN PROGRESS - MOCK CALL ARGUMENT ISSUE - // test.only(`calls es search with only authorized spaces when partially authorized`, async () => { - // // setupCheckPartiallyAuthorized(mockSecurityExt); - // const authRecord: Record = { - // find: { authorizedSpaces: [namespace] }, - // }; - // mockSecurityExt.checkAuthorization.mockResolvedValue({ - // status: 'partially_authorized', - // typeMap: Object.freeze(new Map([[type, authRecord]])), - // }); - - // await findSuccess(client, repository, { type, namespaces: [namespace, 'ns-1'] }); - - // const { options: actualOptions }: { options: GetSearchDslOptions } = - // mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap; - - // console.log(`CALLS: ${JSON.stringify(mockGetSearchDsl.mock.calls)}`); - // // console.log( - // // `MAP: ${JSON.stringify( - // // JSON.stringify(mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap.get(type)) - // // )}` - // // ); - - // // const actualMap: Map = - // // mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap!; - - // expect(actualOptions.typeToNamespacesMap).toBeDefined(); - // // const actualMap: Map = - // // mockGetSearchDsl.mock.calls[0][0].typeToNamespacesMap; - // const expectedMap = new Map(); - // expectedMap.set(type, [namespace]); - // expect(namespaceMapsAreEqual(actualOptions.typeToNamespacesMap!, expectedMap)).toBeTruthy(); - // }); + test(`calls es search with only authorized spaces when partially authorized`, async () => { + // Setup partial authorization with the specific type and space of the current test definition + const authRecord: Record = { + find: { authorizedSpaces: [namespace] }, + }; + mockSecurityExt.checkAuthorization.mockResolvedValue({ + status: 'partially_authorized', + typeMap: Object.freeze(new Map([[type, authRecord]])), + }); + + await findSuccess(client, repository, { type, namespaces: [namespace, 'ns-1'] }); + expect(mockGetSearchDsl.mock.calls[0].length).toBe(3); // Find success verifies this is called once, this shouyld always pass + const { + typeToNamespacesMap: actualMap, + }: { typeToNamespacesMap: Map } = + mockGetSearchDsl.mock.calls[0][2]; + + expect(actualMap).toBeDefined(); + const expectedMap = new Map(); + expectedMap.set(type, [namespace]); + expect(namespaceMapsAreEqual(actualMap, expectedMap)).toBeTruthy(); + }); test(`returns result of es find when fully authorized`, async () => { setupCheckAuthorized(mockSecurityExt); From dbb01f4bfa6b188079fa29c0c125e9c4438a1117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 27 Oct 2022 18:29:19 -0400 Subject: [PATCH 19/47] Adds test_helpers folder for commom test utilites. --- .../src/lib/collect_multi_namespace_references.test.ts | 2 +- .../src/lib/internal_bulk_resolve.test.ts | 2 +- .../src/lib/repository.encryption_extension.test.ts | 2 +- .../src/lib/repository.security_extension.test.ts | 2 +- .../src/lib/repository.spaces_extension.test.ts | 2 +- .../src/lib/repository.test.ts | 2 +- .../src/lib/update_objects_spaces.test.ts | 2 +- .../repository.test.common.ts} | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) rename packages/core/saved-objects/core-saved-objects-api-server-internal/src/{lib/repository.common.test.ts => test_helpers/repository.test.common.ts} (99%) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index e60f96e5ab7b8d..4bb4606131cb00 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -38,7 +38,7 @@ import { setupEnforceFailure, setupEnforceSuccess, setupRedactPassthrough, -} from './repository.common.test'; +} from '../test_helpers/repository.test.common'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; const SPACES = ['default', 'another-space']; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index c40382c7e086f5..b0e9bf5776b1c0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -42,7 +42,7 @@ import { setupEnforceFailure, setupEnforceSuccess, setupRedactPassthrough, -} from './repository.common.test'; +} from '../test_helpers/repository.test.common'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts index 4d7fd552af6d57..2a15bdd39568d2 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts @@ -43,7 +43,7 @@ import { MULTI_NAMESPACE_ENCRYPTED_TYPE, TypeIdTuple, updateSuccess, -} from './repository.common.test'; +} from '../test_helpers/repository.test.common'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index 18d152be81954d..6148d809cfffcb 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -66,7 +66,7 @@ import { bulkDeleteSuccess, createBulkDeleteSuccessStatus, namespaceMapsAreEqual, -} from './repository.common.test'; +} from '../test_helpers/repository.test.common'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts index df5cc1d4b4d075..8484dba10ca565 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts @@ -56,7 +56,7 @@ import { setupCheckUnauthorized, generateIndexPatternSearchResults, bulkDeleteSuccess, -} from './repository.common.test'; +} from '../test_helpers/repository.test.common'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index 9e9d5f8a2fdde4..36b6cb59135661 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -119,7 +119,7 @@ import { getMockEsBulkDeleteResponse, bulkDeleteSuccess, createBulkDeleteSuccessStatus, -} from './repository.common.test'; +} from '../test_helpers/repository.test.common'; const { nodeTypes } = esKuery; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index aa848e0c40f933..9d3f8475bc5b1c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -37,7 +37,7 @@ import { setupEnforceFailure, setupEnforceSuccess, setupRedactPassthrough, -} from './repository.common.test'; +} from '../test_helpers/repository.test.common'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; type SetupParams = Partial< diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts similarity index 99% rename from packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts rename to packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts index 98e316ed40cf69..600b1e967c6776 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.common.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/test_helpers/repository.test.common.ts @@ -6,7 +6,6 @@ * Side Public License, v 1. */ -import { mockGetSearchDsl } from './repository.test.mock'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { schema } from '@kbn/config-schema'; import { loggerMock } from '@kbn/logging-mocks'; @@ -47,7 +46,8 @@ import { ElasticsearchClientMock, } from '@kbn/core-elasticsearch-client-server-mocks'; import { DocumentMigrator } from '@kbn/core-saved-objects-migration-server-internal'; -import { SavedObjectsRepository } from './repository'; +import { mockGetSearchDsl } from '../lib/repository.test.mock'; +import { SavedObjectsRepository } from '../lib/repository'; export const DEFAULT_SPACE = 'default'; From 62c0ec15647976af29159f5e5794177d5d2db82a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 28 Oct 2022 14:25:28 -0400 Subject: [PATCH 20/47] Matches authorization behavior of bulk delete to existing delete. Resolves functional test failures for bulk delete. --- .../src/lib/repository.ts | 46 ++++++++----------- x-pack/test/common/lib/test_data_loader.ts | 4 ++ .../fixtures/kbn_archiver/default_space.json | 10 ++++ .../common/suites/bulk_delete.ts | 10 ++-- .../security_and_spaces/apis/bulk_delete.ts | 7 +-- 5 files changed, 42 insertions(+), 35 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 50483b7eea67cd..e117d7bfee9421 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -1086,7 +1086,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { namespaces = actualResult!._source.namespaces ?? [ SavedObjectsUtils.namespaceIdToString(namespace), ]; - const useForce = force && force === true ? true : false; + const useForce = force && force === true; // the document is shared to more than one space and can only be deleted by force. if (!useForce && (namespaces.length > 1 || namespaces.includes(ALL_NAMESPACES_STRING))) { return { @@ -1143,43 +1143,23 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { force, }); - const validObjects = expectedBulkDeleteMultiNamespaceDocsResults.filter(isRight); - if (validObjects.length === 0) { - // We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. - const savedObjects = expectedBulkDeleteMultiNamespaceDocsResults - .filter(isLeft) - .map((expectedResult) => { - return { ...expectedResult.value, success: false }; - }); - return { statuses: [...savedObjects] }; - } - - // check auth + // Perform Auth Check (on both L/R, we'll deal with that later) const namespaceString = SavedObjectsUtils.namespaceIdToString(namespace); - // const getNamespaceString = (objectNamespace?: string) => objectNamespace ?? namespaceString; const typesAndSpaces = new Map>(); const spacesToAuthorize = new Set([namespaceString]); // Always check authZ for the active space if (this._securityExtension) { - for (const { value } of validObjects) { - const { type, esRequestIndex: index } = value; + for (const { value } of expectedBulkDeleteMultiNamespaceDocsResults) { + const index = (value as { esRequestIndex: number }).esRequestIndex; + const { type } = value; const preflightResult = index !== undefined ? multiNamespaceDocsResponse?.body.docs[index] : undefined; const spacesToEnforce = typesAndSpaces.get(type) ?? new Set([namespaceString]); // Always enforce authZ for the active space - // objectNamespaces?.forEach((objectSpace) => { - // const objectNamespaceString = getNamespaceString(objectSpace); - // spacesToEnforce.add(objectNamespaceString); - // // spacesToAuthorize.add(objectNamespaceString); - // }); - // typesAndSpaces.set(type, spacesToEnforce); - // spacesToAuthorize.add(namespaceString); - + typesAndSpaces.set(type, spacesToEnforce); // @ts-expect-error MultiGetHit._source is optional for (const space of preflightResult?._source?.namespaces ?? []) { - spacesToEnforce.add(space); spacesToAuthorize.add(space); // existing namespaces are included } - typesAndSpaces.set(type, spacesToEnforce); } } @@ -1194,7 +1174,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { action: 'bulk_delete', typeMap: authorizationResult.typeMap, auditCallback: (error) => { - for (const { value } of validObjects) { + for (const { value } of expectedBulkDeleteMultiNamespaceDocsResults) { this._securityExtension!.addAuditEvent({ action: AuditAction.DELETE, savedObject: { type: value.type, id: value.id }, @@ -1206,6 +1186,18 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { }); } + // Filter valid objects + const validObjects = expectedBulkDeleteMultiNamespaceDocsResults.filter(isRight); + if (validObjects.length === 0) { + // We only have error results; return early to avoid potentially trying authZ checks for 0 types which would result in an exception. + const savedObjects = expectedBulkDeleteMultiNamespaceDocsResults + .filter(isLeft) + .map((expectedResult) => { + return { ...expectedResult.value, success: false }; + }); + return { statuses: [...savedObjects] }; + } + // bulk up the bulkDeleteParams validObjects.map((expectedResult) => { bulkDeleteParams.push({ diff --git a/x-pack/test/common/lib/test_data_loader.ts b/x-pack/test/common/lib/test_data_loader.ts index 280c959e691bd5..b379d4b61e3ba2 100644 --- a/x-pack/test/common/lib/test_data_loader.ts +++ b/x-pack/test/common/lib/test_data_loader.ts @@ -71,6 +71,10 @@ const OBJECTS_TO_SHARE: Array<{ spacesToAdd: [SPACE_2.id], objects: [{ type: 'sharedtype', id: 'default_and_space_2' }], }, + { + spacesToAdd: [SPACE_1.id, SPACE_2.id], + objects: [{ type: 'resolvetype', id: 'conflict-newid' }], + }, ]; export function getTestDataLoader({ getService }: Pick) { diff --git a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json index 9a2713fc61872d..6b348cddce5a55 100644 --- a/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json +++ b/x-pack/test/saved_object_api_integration/common/fixtures/kbn_archiver/default_space.json @@ -161,3 +161,13 @@ "version": "WzQ4OCwxXQ==" } +{ + "attributes": { + "title": "Resolve outcome conflict (2 of 2)" + }, + "id": "conflict-newid", + "type": "resolvetype", + "updated_at": "2017-09-21T18:51:23.794Z", + "version": "WzQ4OCwxXQ==" +} + diff --git a/x-pack/test/saved_object_api_integration/common/suites/bulk_delete.ts b/x-pack/test/saved_object_api_integration/common/suites/bulk_delete.ts index d0c90ccc3c2555..578f8a4e0cd1f4 100644 --- a/x-pack/test/saved_object_api_integration/common/suites/bulk_delete.ts +++ b/x-pack/test/saved_object_api_integration/common/suites/bulk_delete.ts @@ -5,14 +5,13 @@ * 2.0. */ -import { SuperTest } from 'supertest'; -import type { Client } from '@elastic/elasticsearch'; import expect from '@kbn/expect'; import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; import { SAVED_OBJECT_TEST_CASES as CASES } from '../lib/saved_object_test_cases'; import { SPACES } from '../lib/spaces'; import { expectResponses, getUrlPrefix, getTestTitle } from '../lib/saved_object_test_utils'; import { ExpectResponseBody, TestCase, TestDefinition, TestSuite, TestUser } from '../lib/types'; +import { FtrProviderContext } from '../ftr_provider_context'; export interface BulkDeleteTestDefinition extends TestDefinition { request: { type: string; id: string; force?: boolean }; @@ -46,7 +45,12 @@ export const TEST_CASES: Record = Object.freeze({ */ const createRequest = ({ type, id, force }: BulkDeleteTestCase) => ({ type, id, force }); -export function bulkDeleteTestSuiteFactory(es: Client, esArchiver: any, supertest: SuperTest) { +export function bulkDeleteTestSuiteFactory(context: FtrProviderContext) { + const esArchiver = context.getService('esArchiver'); + const supertest = context.getService('supertestWithoutAuth'); + const es = context.getService('es'); + // const log = context.getService('log'); + const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_delete'); const expectResponseBody = (testCase: BulkDeleteTestCase, statusCode: 200 | 403, user?: TestUser): ExpectResponseBody => diff --git a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_delete.ts b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_delete.ts index 7a40ea564fb82c..889cdd0d46d5f3 100644 --- a/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_delete.ts +++ b/x-pack/test/saved_object_api_integration/security_and_spaces/apis/bulk_delete.ts @@ -60,12 +60,9 @@ const createTestCases = (spaceId: string) => { return { normalTypes, hiddenType, allTypes }; }; -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertestWithoutAuth'); - const esArchiver = getService('esArchiver'); - const es = getService('es'); +export default function (context: FtrProviderContext) { + const { addTests, createTestDefinitions } = bulkDeleteTestSuiteFactory(context); - const { addTests, createTestDefinitions } = bulkDeleteTestSuiteFactory(es, esArchiver, supertest); const createTests = (spaceId: string) => { const { normalTypes, hiddenType, allTypes } = createTestCases(spaceId); return { From 7f8b670bf961c091ec267ec72301977cb47d62e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 28 Oct 2022 15:25:05 -0400 Subject: [PATCH 21/47] Fixes bulk delete unit test based on changes to authz behavior. --- .../src/lib/repository.security_extension.test.ts | 2 +- .../spaces_only/apis/bulk_delete.ts | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index 6148d809cfffcb..f347eab609af12 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -1985,7 +1985,7 @@ describe('SavedObjectsRepository Security Extension', () => { const expectedTypesAndSpaces = new Map([ [obj1.type, new Set([namespace])], - [obj2.type, new Set(internalOptions.mockMGetResponseObjects[1].initialNamespaces)], + [obj2.type, new Set([namespace])], // only need authz in current space ]); const { typesAndSpaces: actualTypesAndSpaces, typeMap: actualTypeMap } = diff --git a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_delete.ts b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_delete.ts index 84f0c048f1a28b..848a2ff525c5b1 100644 --- a/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_delete.ts +++ b/x-pack/test/saved_object_api_integration/spaces_only/apis/bulk_delete.ts @@ -48,12 +48,8 @@ const createTestCases = (spaceId: string) => [ { ...CASES.DOES_NOT_EXIST, ...fail404() }, ]; -export default function ({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const es = getService('es'); - - const { addTests, createTestDefinitions } = bulkDeleteTestSuiteFactory(es, esArchiver, supertest); +export default function (context: FtrProviderContext) { + const { addTests, createTestDefinitions } = bulkDeleteTestSuiteFactory(context); const createTests = (spaceId: string) => { const testCases = createTestCases(spaceId); return createTestDefinitions(testCases, false, { spaceId }); From 31927097fea5d39e9b5a6b2be76ecdb894e09633 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Mon, 31 Oct 2022 15:39:10 +0100 Subject: [PATCH 22/47] Resolves issues with fleet tests when bulkDelete is called with an empty array of objects. --- .../src/lib/repository.ts | 8 ++- .../fleet/server/services/package_policy.ts | 58 ++++++++++--------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index e117d7bfee9421..553ee369617026 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -1128,11 +1128,14 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const { refresh = DEFAULT_REFRESH_SETTING, force } = options; const namespace = this.getCurrentNamespace(options.namespace); const expectedBulkGetResults = this.presortObjectsByNamespaceType(objects); + if (expectedBulkGetResults.length === 0) { + return { statuses: [] }; + } + const multiNamespaceDocsResponse = await this.preflightCheckForBulkDelete({ expectedBulkGetResults, namespace, }); - const bulkDeleteParams: BulkDeleteParams[] = []; // First round of filtering (Left: object doesn't exist/doesn't exist in namespace, Right: good to proceed) const expectedBulkDeleteMultiNamespaceDocsResults = @@ -1198,7 +1201,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { return { statuses: [...savedObjects] }; } - // bulk up the bulkDeleteParams + // Create the bulkDeleteParams + const bulkDeleteParams: BulkDeleteParams[] = []; validObjects.map((expectedResult) => { bulkDeleteParams.push({ delete: { diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index f52316dd4452b5..67e537c1ef6904 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -706,35 +706,37 @@ class PackagePolicyClientImpl implements PackagePolicyClient { } }); - const { statuses } = await soClient.bulkDelete( - idsToDelete.map((id) => ({ id, type: SAVED_OBJECT_TYPE })) - ); + if (idsToDelete.length > 0) { + const { statuses } = await soClient.bulkDelete( + idsToDelete.map((id) => ({ id, type: SAVED_OBJECT_TYPE })) + ); - statuses.forEach(({ id, success, error }) => { - const packagePolicy = packagePolicies.find((p) => p.id === id); - if (success && packagePolicy) { - result.push({ - id, - name: packagePolicy.name, - success: true, - package: { - name: packagePolicy.package?.name || '', - title: packagePolicy.package?.title || '', - version: packagePolicy.package?.version || '', - }, - policy_id: packagePolicy.policy_id, - }); - } else if (!success && error) { - result.push({ - id, - success: false, - statusCode: error.statusCode, - body: { - message: error.message, - }, - }); - } - }); + statuses.forEach(({ id, success, error }) => { + const packagePolicy = packagePolicies.find((p) => p.id === id); + if (success && packagePolicy) { + result.push({ + id, + name: packagePolicy.name, + success: true, + package: { + name: packagePolicy.package?.name || '', + title: packagePolicy.package?.title || '', + version: packagePolicy.package?.version || '', + }, + policy_id: packagePolicy.policy_id, + }); + } else if (!success && error) { + result.push({ + id, + success: false, + statusCode: error.statusCode, + body: { + message: error.message, + }, + }); + } + }); + } if (!options?.skipUnassignFromAgentPolicies) { const uniquePolicyIdsR = [ From 0cbd92a774029a624480823050a1833e51d5633f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Mon, 31 Oct 2022 17:20:55 +0100 Subject: [PATCH 23/47] Fixes local types check issue --- .../core-saved-objects-api-server-internal/tsconfig.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json index 3fe98195b374a7..4582562d6c9bb4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" From b79b1b5476f818f946ee1e86f99f05e8b54b9682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Wed, 2 Nov 2022 12:32:25 +0100 Subject: [PATCH 24/47] Adds missing TS docs - round 1 --- .../src/apis/base.ts | 7 +- .../src/apis/bulk_create.ts | 13 ++- .../src/apis/bulk_delete.ts | 24 ++++- .../src/apis/bulk_resolve.ts | 7 +- .../src/apis/bulk_update.ts | 21 +++- .../src/apis/create.ts | 7 +- .../src/apis/delete.ts | 8 +- .../src/apis/find.ts | 16 +-- .../src/apis/update.ts | 9 +- .../src/saved_objects_client.ts | 43 +++++--- .../src/simple_saved_object.ts | 35 ++++++ .../src/apis/base.ts | 2 + .../src/apis/bulk_create.ts | 5 + .../src/apis/bulk_delete.ts | 12 +++ .../src/apis/bulk_get.ts | 3 + .../src/apis/bulk_resolve.ts | 5 + .../src/apis/bulk_update.ts | 4 + .../src/apis/close_point_in_time.ts | 13 ++- .../apis/collect_multinamespace_references.ts | 3 + .../src/apis/create.ts | 2 + .../src/apis/create_point_in_time_finder.ts | 14 ++- .../src/apis/find.ts | 28 ++++- .../src/apis/increment_counter.ts | 4 + .../src/apis/open_point_in_time_for_type.ts | 8 +- .../src/apis/update.ts | 6 +- .../src/apis/update_objects_spaces.ts | 1 + .../src/saved_objects_client.ts | 90 +++++++++++----- .../src/saved_objects_repository.ts | 102 ++++++++++-------- .../src/saved_objects.ts | 2 + 29 files changed, 366 insertions(+), 128 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/base.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/base.ts index 6414b206a672b3..74d5223def315b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/base.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/base.ts @@ -8,7 +8,12 @@ import { SimpleSavedObject } from '../simple_saved_object'; -/** @public */ +/** + * Batch response for simple saved objects + * + * @public + */ export interface SavedObjectsBatchResponse { + /** Array of simple saved objects */ savedObjects: Array>; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts index 4e4190cb55675f..6d5ce23205ef82 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts @@ -9,18 +9,23 @@ import type { SavedObjectsCreateOptions } from './create'; /** - * @param type - Create a SavedObject of the given type - * @param attributes - Create a SavedObject with the given attributes + * Per-object parameters for bulk create operation * * @public */ export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions { + /** Create a SavedObject of this type. */ type: string; + /** Attributes for the saved object to be created. */ attributes: T; } -/** @public */ +/** + * Options for bulk create operation + * + * @public + * */ export interface SavedObjectsBulkCreateOptions { - /** If a document with the given `id` already exists, overwrite it's contents (default=false). */ + /** If a document with the given `id` already exists, overwrite its contents (default=false). */ overwrite?: boolean; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts index 1e4b5d2268dea7..532e900338d8ae 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_delete.ts @@ -8,20 +8,38 @@ import { SavedObjectError } from '@kbn/core-saved-objects-common'; -/** @public */ +/** + * Options for bulk delete operation + * + * @public + */ export interface SavedObjectsBulkDeleteOptions { + /** Force deletion of any objects that exist in multiple namespaces (default=false) */ force?: boolean; } -/** @public */ +/** + * Single item within the statuses array of the bulk delete response + * + * @public + */ export interface SavedObjectsBulkDeleteResponseItem { + /** saved object id */ id: string; + /** saved object type */ type: string; + /** true if the delete operation succeeded*/ success: boolean; + /** error from delete operation (undefined if no error) */ error?: SavedObjectError; } -/** @public */ +/** + * Return type of the Saved Objects `bulkDelete()` method. + * + * @public + */ export interface SavedObjectsBulkDeleteResponse { + /** array of statuses per object */ statuses: SavedObjectsBulkDeleteResponseItem[]; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts index 58316983c7a8d8..c5ba4825252985 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts @@ -8,7 +8,12 @@ import type { ResolvedSimpleSavedObject } from './resolve'; -/** @public */ +/** + * Return type of the Saved Objects `bulkResolve()` method. + * + * @public + */ export interface SavedObjectsBulkResolveResponse { + /** Array of {@link ResolvedSimpleSavedObject} that were resolved */ resolved_objects: Array>; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts index f5ba2cc249c19b..c819fb1ac448d1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts @@ -8,16 +8,33 @@ import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; -/** @public */ +/** + * Per-object parameters for bulk update operation + * + * @public + */ export interface SavedObjectsBulkUpdateObject { + /** Type of the saved object to update */ type: string; + /** ID of the saved object to update */ id: string; + /** The attributes to update */ attributes: T; + /** The version string for the saved object */ version?: string; + /** Array of references to other saved objects */ references?: SavedObjectReference[]; } -/** @public */ +/** + * Options for bulk update operation + * + * @public + * */ export interface SavedObjectsBulkUpdateOptions { + /** + * The namespace from which to apply the bulk update operation + * Not permitted if spaces extension is enabled + */ namespace?: string; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts index fb2e70d7d1ebd7..fb6a18219e5bf5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts @@ -11,7 +11,11 @@ import type { SavedObjectsMigrationVersion, } from '@kbn/core-saved-objects-common'; -/** @public */ +/** + * Options for creating a saved object. + * + * @public + */ export interface SavedObjectsCreateOptions { /** * (Not recommended) Specify an id instead of having the saved objects service generate one for you. @@ -23,5 +27,6 @@ export interface SavedObjectsCreateOptions { migrationVersion?: SavedObjectsMigrationVersion; /** A semver value that is used when upgrading objects between Kibana versions. */ coreMigrationVersion?: string; + /** Array of referenced saved objects. */ references?: SavedObjectReference[]; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/delete.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/delete.ts index 598a35a0aad411..c43dbec6845b3e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/delete.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/delete.ts @@ -6,8 +6,12 @@ * Side Public License, v 1. */ -/** @public */ +/** + * Options for deleting a saved object. + * + * @public + */ export interface SavedObjectsDeleteOptions { - /** Force deletion of an object that exists in multiple namespaces */ + /** Force deletion of an object that exists in multiple namespaces (default=false) */ force?: boolean; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts index 7f555764124a0a..39d99a6ee6e86d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts @@ -9,7 +9,11 @@ import type { SavedObjectsFindOptions as SavedObjectFindOptionsServer } from '@kbn/core-saved-objects-api-server'; import type { SavedObjectsBatchResponse } from './base'; +export type { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-server'; + /** + * Browser options for finding saved objects + * * @public */ export type SavedObjectsFindOptions = Omit< @@ -24,16 +28,12 @@ export type SavedObjectsFindOptions = Omit< */ export interface SavedObjectsFindResponse extends SavedObjectsBatchResponse { + /** aggregations from the search query */ aggregations?: A; + /** total number of results */ total: number; + /** number of results per page */ perPage: number; + /** current page in results*/ page: number; } - -/** - * @public - */ -export interface SavedObjectsFindOptionsReference { - type: string; - id: string; -} diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts index dd9a6dc523229c..f8095e35df1e5d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts @@ -8,9 +8,16 @@ import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; -/** @public */ +/** + * Options for updating a saved object + * + * @public + */ export interface SavedObjectsUpdateOptions { + /** version of the saved object */ version?: string; + /** Alternative attributes for the saved object if upserting */ upsert?: Attributes; + /** Array of references to other saved objects */ references?: SavedObjectReference[]; } diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts index d222770a8579da..9ff01104c5ba07 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts @@ -33,7 +33,12 @@ import type { SimpleSavedObject } from './simple_saved_object'; */ export interface SavedObjectsClientContract { /** - * Persists an object + * Creates an object + * + * @param {string} type - the type of object to create + * @param {string} attributes - the attributes of the object + * @param {string} options {@link SavedObjectsCreateOptions} + * @returns The result of the create operation - the created saved object */ create( type: string, @@ -42,7 +47,10 @@ export interface SavedObjectsClientContract { ): Promise>; /** - * Creates multiple documents at once + * Creates multiple objects at once + * + * @param {string} objects - an array of objects containing type, attributes + * @param {string} options {@link SavedObjectsBulkCreateOptions} * @returns The result of the create operation containing created saved objects. */ bulkCreate( @@ -52,6 +60,11 @@ export interface SavedObjectsClientContract { /** * Deletes an object + * + * @param {string} type - the type the of object to delete + * @param {string} id - the id of the object to delete + * @param {string} options {@link SavedObjectsDeleteOptions} + * @param {string} options.force - required to delete objects shared to multiple spaces */ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; @@ -69,8 +82,8 @@ export interface SavedObjectsClientContract { /** * Search for objects * - * @param {object} [options={}] - * @property {string} options.type + * @param {object} [options={}] {@link SavedObjectsFindOptions} + * @property {string} options.type - the type or array of types to find * @property {string} options.search * @property {string} options.searchFields - see Elasticsearch Simple Query String * Query field argument for more information @@ -87,8 +100,8 @@ export interface SavedObjectsClientContract { /** * Fetches a single object * - * @param {string} type - * @param {string} id + * @param {string} type - the type of the object to get + * @param {string} id - the ID of the object to get * @returns The saved object for the given type and id. */ get(type: string, id: string): Promise>; @@ -110,8 +123,8 @@ export interface SavedObjectsClientContract { /** * Resolves a single object * - * @param {string} type - * @param {string} id + * @param {string} type - the type of the object to resolve + * @param {string} id - the ID of the object to resolve * @returns The resolve result for the saved object for the given type and id. * * @note Saved objects that Kibana fails to find are replaced with an error object and an "exactMatch" outcome. The rationale behind the @@ -144,13 +157,13 @@ export interface SavedObjectsClientContract { /** * Updates an object * - * @param {string} type - * @param {string} id - * @param {object} attributes - * @param {object} options + * @param {string} type - the type of the object to update + * @param {string} id - the ID of the object to update + * @param {object} attributes - the attributes to update + * @param {object} options {@link SavedObjectsUpdateOptions} * @prop {integer} options.version - ensures version matches that of persisted object * @prop {object} options.migrationVersion - The optional migrationVersion of this document - * @returns + * @returns the udpated simple saved object */ update( type: string, @@ -162,8 +175,8 @@ export interface SavedObjectsClientContract { /** * Update multiple documents at once * - * @param {array} objects - [{ type, id, attributes, options: { version, references } }] - * @returns The result of the update operation containing both failed and updated saved objects. + * @param {array} objects - an array of objects containing type, id, attributes, and references + * @returns the result of the bulk update operation containing both failed and updated saved objects. */ bulkUpdate( objects: SavedObjectsBulkUpdateObject[] diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts index 61e4359bf3d74c..8edb1347d6a889 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts @@ -18,15 +18,25 @@ import type { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-com * @public */ export interface SimpleSavedObject { + /** attributes of the object, templated */ attributes: T; + /** version of the saved object */ _version?: SavedObjectType['version']; + /** ID of the saved object, unique per type */ id: SavedObjectType['id']; + /** Type of the saved object */ type: SavedObjectType['type']; + /** Migration version of the saved object */ migrationVersion: SavedObjectType['migrationVersion']; + /** Core migration version of the saved object */ coreMigrationVersion: SavedObjectType['coreMigrationVersion']; + /** Error associated with this object, undefined if no error */ error: SavedObjectType['error']; + /** References to other saved objects */ references: SavedObjectType['references']; + /** The date this object was last updated */ updatedAt: SavedObjectType['updated_at']; + /** The date this object was created */ createdAt: SavedObjectType['created_at']; /** * Space(s) that this saved object exists in. This attribute is not used for "global" saved object types which are registered with @@ -34,13 +44,38 @@ export interface SimpleSavedObject { */ namespaces: SavedObjectType['namespaces']; + /** + * Gets an attribute of this object + * + * @param {string} key - the name of the attribute + * @returns The value of the attribute. + */ get(key: string): any; + /** + * Sets an attribute of this object + * + * @param {string} key - the name of the attribute + * @param {string} value - the value for the attribute + * @returns The updated attributes of this object. + */ set(key: string, value: any): T; + /** + * Checks if this object has an attribute + * + * @param {string} key - the name of the attribute + * @returns true if the attribute exists. + */ has(key: string): boolean; + /** + * Saves this object + */ save(): Promise>; + /** + * Deletes this object + */ delete(): Promise<{}>; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts index e02a4142448efa..eb80df8d96d440 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts @@ -24,9 +24,11 @@ export interface SavedObjectsBaseOptions { export type MutatingOperationRefreshSetting = boolean | 'wait_for'; /** + * Base return for saved object bulk operations * * @public */ export interface SavedObjectsBulkResponse { + /** array of saved objects */ saved_objects: Array>; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts index 735564cfec63dd..8538f191764fb7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts @@ -12,14 +12,19 @@ import type { } from '@kbn/core-saved-objects-common'; /** + * Object parameters for the bulk create operation * * @public */ export interface SavedObjectsBulkCreateObject { id?: string; + /** the type of object to create */ type: string; + /** the attributes for the object to create */ attributes: T; + /** the version string for the object to create */ version?: string; + /** array of references to other saved objects */ references?: SavedObjectReference[]; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts index 76d490925c580e..5b390cca73d14c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts @@ -10,15 +10,20 @@ import type { SavedObjectError } from '@kbn/core-saved-objects-common'; import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** + * Object parameters for the bulk delete operation * * @public */ export interface SavedObjectsBulkDeleteObject { + /** The type of the saved object to delete */ type: string; + /** The ID of the saved object to delete */ id: string; } /** + * Options for the bulk delete operation + * * @public */ export interface SavedObjectsBulkDeleteOptions extends SavedObjectsBaseOptions { @@ -31,10 +36,14 @@ export interface SavedObjectsBulkDeleteOptions extends SavedObjectsBaseOptions { } /** + * The per-object result of a bulk delete operation + * * @public */ export interface SavedObjectsBulkDeleteStatus { + /** The ID of the saved object */ id: string; + /** The type of the saved object */ type: string; /** The status of deleting the object: true for deleted, false for error */ success: boolean; @@ -43,8 +52,11 @@ export interface SavedObjectsBulkDeleteStatus { } /** + * Return type of the Saved Objects `bulkDelete()` method + * * @public */ export interface SavedObjectsBulkDeleteResponse { + /** Array of {@link SavedObjectsBulkDeleteStatus} */ statuses: SavedObjectsBulkDeleteStatus[]; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_get.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_get.ts index fbbe6c958594da..1a3f4928db672d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_get.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_get.ts @@ -7,11 +7,14 @@ */ /** + * Object parameters for the bulk get operation * * @public */ export interface SavedObjectsBulkGetObject { + /** ID of the object to get */ id: string; + /** Type of the object to get */ type: string; /** SavedObject fields to include in the response */ fields?: string[]; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts index b52c8caf26e828..ff59adee8be7f5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts @@ -9,18 +9,23 @@ import type { SavedObjectsResolveResponse } from './resolve'; /** + * Object parameters for the bulk resolve operation * * @public */ export interface SavedObjectsBulkResolveObject { + /** ID of the object to resiolve */ id: string; + /** Type of the object to resolve */ type: string; } /** + * Return type of the Saved Objects `bulkResolve()` method. * * @public */ export interface SavedObjectsBulkResolveResponse { + /** array of {@link SavedObjectsResolveResponse} */ resolved_objects: Array>; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts index 858504853dd754..6d10aee397b2f1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts @@ -10,6 +10,7 @@ import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from '. import type { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse } from './update'; /** + * Object parameters for the bulk update operation * * @public */ @@ -31,6 +32,7 @@ export interface SavedObjectsBulkUpdateObject } /** + * Options for the saved objects bulk update operation * * @public */ @@ -40,9 +42,11 @@ export interface SavedObjectsBulkUpdateOptions extends SavedObjectsBaseOptions { } /** + * Return type of the Saved Objects `bulkUpdate()` method. * * @public */ export interface SavedObjectsBulkUpdateResponse { + /** array of {@link SavedObjectsUpdateResponse} */ saved_objects: Array>; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts index 16348155003d24..8996de4474cfec 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts @@ -9,21 +9,20 @@ import type { SavedObjectsBaseOptions } from './base'; /** + * Options for the close point-in-time operation + * * @public */ export type SavedObjectsClosePointInTimeOptions = SavedObjectsBaseOptions; /** + * Return type of the Saved Objects `closePointInTime()` method. + * * @public */ export interface SavedObjectsClosePointInTimeResponse { - /** - * If true, all search contexts associated with the PIT id are - * successfully closed. - */ + /** If true, all search contexts associated with the PIT id are successfully closed */ succeeded: boolean; - /** - * The number of search contexts that have been successfully closed. - */ + /** The number of search contexts that have been successfully closed */ num_freed: number; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts index 59bfd4144a8c1c..fcd0d079961bd7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts @@ -18,7 +18,9 @@ import type { SavedObjectsBaseOptions } from './base'; * @public */ export interface SavedObjectsCollectMultiNamespaceReferencesObject { + /** The ID of the object to collect references for */ id: string; + /** The type of the object to collect references for */ type: string; } @@ -73,5 +75,6 @@ export interface SavedObjectReferenceWithContext { * @public */ export interface SavedObjectsCollectMultiNamespaceReferencesResponse { + /** array of {@link SavedObjectReferenceWithContext} */ objects: SavedObjectReferenceWithContext[]; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts index 2172b75be4b461..78a017ed03aba1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts @@ -13,6 +13,7 @@ import type { import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** + * Options for the saved objects create operation * * @public */ @@ -38,6 +39,7 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions { * field set and you want to create it again. */ coreMigrationVersion?: string; + /** Array of references to other saved objects */ references?: SavedObjectReference[]; /** The Elasticsearch Refresh setting for this operation */ refresh?: MutatingOperationRefreshSetting; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts index 4e9b741d8b658f..b5bd62b75cbdf3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts @@ -10,6 +10,8 @@ import type { SavedObjectsFindOptions, SavedObjectsFindResponse } from './find'; import { ISavedObjectsRepository } from '../saved_objects_repository'; /** + * Options for the create point-in-time finder operation + * * @public */ export type SavedObjectsCreatePointInTimeFinderOptions = Omit< @@ -18,6 +20,9 @@ export type SavedObjectsCreatePointInTimeFinderOptions = Omit< >; /** + * Point-in-time finder client. + * Partially implements {@link ISavedObjectsRepository} + * * @public */ export type SavedObjectsPointInTimeFinderClient = Pick< @@ -26,13 +31,20 @@ export type SavedObjectsPointInTimeFinderClient = Pick< >; /** + * Dependencies for the create point-in-time finder operation + * * @public */ export interface SavedObjectsCreatePointInTimeFinderDependencies { + /** the point-in-time finder client */ client: SavedObjectsPointInTimeFinderClient; } -/** @public */ +/** + * Point-in-time finder + * + * @public + */ export interface ISavedObjectsPointInTimeFinder { /** * An async generator which wraps calls to `savedObjectsClient.find` and diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts index a50506c96c8e50..8e754d26533c34 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts @@ -16,30 +16,44 @@ import type { SavedObject } from '@kbn/core-saved-objects-common'; type KueryNode = any; /** + * An object reference for use in find operation options + * * @public */ export interface SavedObjectsFindOptionsReference { + /** The type of the saved object */ type: string; + /** The ID of the saved object */ id: string; } /** + * Point-in-time parameters + * * @public */ export interface SavedObjectsPitParams { + /** The ID of point-in-time */ id: string; + /** Optionally specify how long ES should keep the PIT alive until the next request. Defaults to `5m`. */ keepAlive?: string; } /** + * Options for finding saved objects * * @public */ export interface SavedObjectsFindOptions { + /** the type or types of objects to find */ type: string | string[]; + /** the page of results to return */ page?: number; + /** the number of objects per page */ perPage?: number; + /** which field to sort by */ sortField?: string; + /** sort order, ascending or descending */ sortOrder?: SortOrder; /** * An array of fields to include in the results @@ -60,33 +74,29 @@ export interface SavedObjectsFindOptions { * be modified. If used in conjunction with `searchFields`, both are concatenated together. */ rootSearchFields?: string[]; - /** * Search for documents having a reference to the specified objects. * Use `hasReferenceOperator` to specify the operator to use when searching for multiple references. */ hasReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[]; - /** * The operator to use when searching by multiple references using the `hasReference` option. Defaults to `OR` */ hasReferenceOperator?: 'AND' | 'OR'; - /** * Search for documents *not* having a reference to the specified objects. * Use `hasNoReferenceOperator` to specify the operator to use when searching for multiple references. */ hasNoReference?: SavedObjectsFindOptionsReference | SavedObjectsFindOptionsReference[]; - /** * The operator to use when searching by multiple references using the `hasNoReference` option. Defaults to `OR` */ hasNoReferenceOperator?: 'AND' | 'OR'; - /** * The search operator to use with the provided filter. Defaults to `OR` */ defaultSearchOperator?: 'AND' | 'OR'; + /** filter string for the search query */ filter?: string | KueryNode; /** * A record of aggregations to perform. @@ -110,6 +120,7 @@ export interface SavedObjectsFindOptions { * @alpha */ aggs?: Record; + /** array of namespaces to search */ namespaces?: string[]; /** * This map defines each type to search for, and the namespace(s) to search for the type in; this is only intended to be used by a saved @@ -128,6 +139,7 @@ export interface SavedObjectsFindOptions { } /** + * Results for a find operation * * @public */ @@ -176,10 +188,16 @@ export interface SavedObjectsFindResult extends SavedObject { * @public */ export interface SavedObjectsFindResponse { + /** aggregations from the search query response */ aggregations?: A; + /** array of found saved objects */ saved_objects: Array>; + /** the total number of objects */ total: number; + /** the number of objects per page */ per_page: number; + /** the current page number */ page: number; + /** the point-in-time ID (undefined if not applicable) */ pit_id?: string; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts index 8a87bb7b38b069..17234d51a6fceb 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts @@ -10,6 +10,8 @@ import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-commo import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** + * Options for the increment counter operation + * * @public */ export interface SavedObjectsIncrementCounterOptions @@ -33,6 +35,8 @@ export interface SavedObjectsIncrementCounterOptions } /** + * The field and increment details for the increment counter operation + * * @public */ export interface SavedObjectsIncrementCounterField { diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/open_point_in_time_for_type.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/open_point_in_time_for_type.ts index ad9a2face8b067..3703cd67db1bec 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/open_point_in_time_for_type.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/open_point_in_time_for_type.ts @@ -7,6 +7,8 @@ */ /** + * Options for the open point-in-time for type operation + * * @public */ export interface SavedObjectsOpenPointInTimeOptions { @@ -30,11 +32,11 @@ export interface SavedObjectsOpenPointInTimeOptions { } /** + * Return type of the Saved Objects `openPointInTimeForType()` method. + * * @public */ export interface SavedObjectsOpenPointInTimeResponse { - /** - * PIT ID returned from ES. - */ + /** PIT ID returned from ES */ id: string; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts index 31bac37b94fff4..db494d9d8d7a7b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts @@ -10,6 +10,7 @@ import type { SavedObjectReference, SavedObject } from '@kbn/core-saved-objects- import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** + * Options for the saved objects update operation * * @public */ @@ -23,7 +24,7 @@ export interface SavedObjectsUpdateOptions extends SavedOb references?: SavedObjectReference[]; /** The Elasticsearch Refresh setting for this operation */ refresh?: MutatingOperationRefreshSetting; - /** If specified, will be used to perform an upsert if the document doesn't exist */ + /** If specified, will be used to perform an upsert if the object doesn't exist */ upsert?: Attributes; /** * The Elasticsearch `retry_on_conflict` setting for this operation. @@ -33,11 +34,14 @@ export interface SavedObjectsUpdateOptions extends SavedOb } /** + * Return type of the Saved Objects `update()` method. * * @public */ export interface SavedObjectsUpdateResponse extends Omit, 'attributes' | 'references'> { + /** partial attributes of the saved object */ attributes: Partial; + /** optionally included references to other saved objects */ references: SavedObjectReference[] | undefined; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts index 148d3480f2975b..a249ef50f418f0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts @@ -49,6 +49,7 @@ export interface SavedObjectsUpdateObjectsSpacesOptions extends SavedObjectsBase * @public */ export interface SavedObjectsUpdateObjectsSpacesResponse { + /** array of {@link SavedObjectsUpdateObjectsSpacesResponseObject} */ objects: SavedObjectsUpdateObjectsSpacesResponseObject[]; } diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts index cfe1f4e6a146b2..95361d74d781af 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts @@ -112,9 +112,10 @@ export interface SavedObjectsClientContract { /** * Persists a SavedObject * - * @param type - * @param attributes - * @param options + * @param type - the type of saved object to create + * @param attributes - attributes for the saved object + * @param options {@link SavedObjectsCreateOptions} + * @returns the created saved object */ create( type: string, @@ -125,8 +126,9 @@ export interface SavedObjectsClientContract { /** * Persists multiple documents batched together as a single request * - * @param objects - * @param options + * @param objects - array of objects to create (contains type, attributes, and optional fields ) + * @param options {@link SavedObjectsCreateOptions} - options for the bulk create operation + * @returns the {@link SavedObjectsBulkResponse} */ bulkCreate( objects: Array>, @@ -137,8 +139,9 @@ export interface SavedObjectsClientContract { * Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are * multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. * - * @param objects - * @param options + * @param objects - array of objects to check (contains ID and type) + * @param options {@link SavedObjectsBaseOptions} + * @returns the {@link SavedObjectsCheckConflictsResponse} */ checkConflicts( objects: SavedObjectsCheckConflictsObject[], @@ -148,17 +151,18 @@ export interface SavedObjectsClientContract { /** * Deletes a SavedObject * - * @param type - * @param id - * @param options + * @param type - the type of saved object to delete + * @param id - the ID of the saved object to delete + * @param options {@link SavedObjectsDeleteOptions} */ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; /** * Deletes multiple SavedObjects batched together as a single request * - * @param objects - * @param options + * @param objects - array of objects to delete (contains ID and type) + * @param options {@link SavedObjectsBulkDeleteOptions} + * @returns the {@link SavedObjectsBulkDeleteResponse} */ bulkDelete( objects: SavedObjectsBulkDeleteObject[], @@ -167,7 +171,8 @@ export interface SavedObjectsClientContract { /** * Find all SavedObjects matching the search query * - * @param options + * @param options {@link SavedObjectsFindOptions} + * @returns the {@link SavedObjectsFindResponse} */ find( options: SavedObjectsFindOptions @@ -176,7 +181,9 @@ export interface SavedObjectsClientContract { /** * Returns an array of objects by id * - * @param objects - an array of ids, or an array of objects containing id, type and optionally fields + * @param objects - array of objects to get (contains id, type, and optional fields) + * @param options {@link SavedObjectsBaseOptions} + * @returns the {@link SavedObjectsBulkResponse} * @example * * bulkGet([ @@ -192,9 +199,9 @@ export interface SavedObjectsClientContract { /** * Retrieves a single object * - * @param type - The type of SavedObject to retrieve - * @param id - The ID of the SavedObject to retrieve - * @param options + * @param type - The type of the object to retrieve + * @param id - The ID of the object to retrieve + * @param options {@link SavedObjectsBaseOptions} */ get( type: string, @@ -205,7 +212,9 @@ export interface SavedObjectsClientContract { /** * Resolves an array of objects by id, using any legacy URL aliases if they exist * - * @param objects - an array of objects containing id, type + * @param objects - an array of objects to resolve (contains id and type) + * @param options {@link SavedObjectsBaseOptions} + * @returns the {@link SavedObjectsBulkResolveResponse} * @example * * bulkResolve([ @@ -227,7 +236,8 @@ export interface SavedObjectsClientContract { * * @param type - The type of SavedObject to retrieve * @param id - The ID of the SavedObject to retrieve - * @param options + * @param options {@link SavedObjectsBaseOptions} + * @returns the {@link SavedObjectsResolveResponse} */ resolve( type: string, @@ -238,9 +248,10 @@ export interface SavedObjectsClientContract { /** * Updates an SavedObject * - * @param type - * @param id - * @param options + * @param type - The type of SavedObject to update + * @param id - The ID of the SavedObject to retrieve + * @param options {@link SavedObjectsUpdateOptions} + * @returns the {@link SavedObjectsUpdateResponse} */ update( type: string, @@ -252,7 +263,9 @@ export interface SavedObjectsClientContract { /** * Bulk Updates multiple SavedObject at once * - * @param objects + * @param objects - array of objects to update (contains ID, type, attributes, and optional namespace) + * @param options {@link SavedObjectsBulkUpdateOptions} + * @returns the {@link SavedObjectsBulkUpdateResponse} */ bulkUpdate( objects: Array>, @@ -261,6 +274,11 @@ export interface SavedObjectsClientContract { /** * Updates all objects containing a reference to the given {type, id} tuple to remove the said reference. + * + * @param type - the type of the object to remove references to + * @param id - the ID of the object to remove references to + * @param options {@link SavedObjectsRemoveReferencesToOptions} + * @returns the {@link SavedObjectsRemoveReferencesToResponse} */ removeReferencesTo( type: string, @@ -275,6 +293,10 @@ export interface SavedObjectsClientContract { * * Only use this API if you have an advanced use case that's not solved by the * {@link SavedObjectsClient.createPointInTimeFinder} method. + * + * @param type - the type or array of types + * @param options {@link SavedObjectsOpenPointInTimeOptions} + * @returns the {@link SavedObjectsOpenPointInTimeResponse} */ openPointInTimeForType( type: string | string[], @@ -288,6 +310,10 @@ export interface SavedObjectsClientContract { * * Only use this API if you have an advanced use case that's not solved by the * {@link SavedObjectsClient.createPointInTimeFinder} method. + * + * @param id - the ID of the PIT to close + * @param options {@link SavedObjectsClosePointInTimeOptions} + * @returns the {@link SavedObjectsClosePointInTimeResponse} */ closePointInTime( id: string, @@ -338,6 +364,10 @@ export interface SavedObjectsClientContract { * } * } * ``` + * + * @param findOptions {@link SavedObjectsCreatePointInTimeFinderOptions} + * @param dependencies {@link SavedObjectsCreatePointInTimeFinderDependencies} + * @returns the created PIT finder */ createPointInTimeFinder( findOptions: SavedObjectsCreatePointInTimeFinderOptions, @@ -347,8 +377,9 @@ export interface SavedObjectsClientContract { /** * Gets all references and transitive references of the listed objects. Ignores any object that is not a multi-namespace type. * - * @param objects - * @param options + * @param objects - array of objects to collect references for (contains ID and type) + * @param options {@link SavedObjectsCollectMultiNamespaceReferencesOptions} + * @retuns the {@link SavedObjectsCollectMultiNamespaceReferencesResponse} */ collectMultiNamespaceReferences( objects: SavedObjectsCollectMultiNamespaceReferencesObject[], @@ -358,10 +389,11 @@ export interface SavedObjectsClientContract { /** * Updates one or more objects to add and/or remove them from specified spaces. * - * @param objects - * @param spacesToAdd - * @param spacesToRemove - * @param options + * @param objects - array of objects to update (contains ID, type, and optional internal-only parameters) + * @param spacesToAdd - array of spaces each object should be included in + * @param spacesToRemove - array of spaces each object should not be included in + * @param options {@link SavedObjectsUpdateObjectsSpacesOptions} + * @returns the {@link SavedObjectsUpdateObjectsSpacesResponse} */ updateObjectsSpaces( objects: SavedObjectsUpdateObjectsSpacesObject[], diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index 48a362305a6b75..c86ddb0b35d1ef 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -71,15 +71,15 @@ export interface ISavedObjectsRepository { /** * Persists an object * - * @param {string} type - * @param {object} attributes - * @param {object} [options={}] + * @param {string} type - the type of object to create + * @param {object} attributes - the attributes for the object to be created + * @param {object} [options={}] {@link SavedObjectsCreateOptions} - options for the create operation * @property {string} [options.id] - force id on creation, not recommended * @property {boolean} [options.overwrite=false] * @property {object} [options.migrationVersion=undefined] * @property {string} [options.namespace] * @property {array} [options.references=[]] - [{ name, type, id }] - * @returns {promise} - { id, type, version, attributes } + * @returns {promise} the created saved object { id, type, version, attributes } */ create( type: string, @@ -90,11 +90,11 @@ export interface ISavedObjectsRepository { /** * Creates multiple documents at once * - * @param {array} objects - [{ type, id, attributes, references, migrationVersion }] - * @param {object} [options={}] + * @param {array} objects - array of objects to create [{ type, attributes, ... }] + * @param {object} [options={}] {@link SavedObjectsCreateOptions} - options for the bulk create operation * @property {boolean} [options.overwrite=false] - overwrites existing documents * @property {string} [options.namespace] - * @returns {promise} - {saved_objects: [[{ id, type, version, references, attributes, error: { message } }]} + * @returns {promise} - {saved_objects: [[{ id, type, version, references, attributes, error: { message } }]} */ bulkCreate( objects: Array>, @@ -104,6 +104,10 @@ export interface ISavedObjectsRepository { /** * Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are * multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. + * + * @param {array} objects - array of objects to check for conflicts [{ id, type }] + * @param {object} options {@link SavedObjectsBaseOptions} - options for the check conflict operation + * @returns {promise} - {errors: [{ id, type, error: { message } }]} */ checkConflicts( objects: SavedObjectsCheckConflictsObject[], @@ -113,18 +117,17 @@ export interface ISavedObjectsRepository { /** * Deletes an object * - * @param {string} type - * @param {string} id - * @param {object} [options={}] + * @param {string} type - the type of the object to delete + * @param {string} id - the id of the object to delete + * @param {object} [options={}] {@link SavedObjectsDeleteOptions} - options for the delete operation * @property {string} [options.namespace] - * @returns {promise} */ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; /** * Deletes multiple documents at once - * @param {array} objects - an array of objects containing id and type - * @param {object} [options={}] + * @param {array} objects - an array of objects to delete (contains id and type) + * @param {object} [options={}] {@link SavedObjectsBulkDeleteOptions} - options for the bulk delete operation * @returns {promise} - { statuses: [{ id, type, success, error: { message } }] } */ bulkDelete( @@ -135,7 +138,8 @@ export interface ISavedObjectsRepository { /** * Deletes all objects from the provided namespace. * - * @param {string} namespace + * @param {string} namespace - the namespace in which to delete all objects + * @param {object} options {@link SavedObjectsDeleteByNamespaceOptions} - options for the delete by namespace operation * @returns {promise} - { took, timed_out, total, deleted, batches, version_conflicts, noops, retries, failures } */ deleteByNamespace( @@ -144,7 +148,7 @@ export interface ISavedObjectsRepository { ): Promise; /** - * @param {object} [options={}] + * @param {object} [options={}] {@link SavedObjectsFindOptions} - options for the find operation * @property {(string|Array)} [options.type] * @property {string} [options.search] * @property {string} [options.defaultSearchOperator] @@ -160,6 +164,7 @@ export interface ISavedObjectsRepository { * @property {object} [options.hasReference] - { type, id } * @property {string} [options.pit] * @property {string} [options.preference] + * @param {object} internalOptions {@link SavedObjectsFindInternalOptions} - internal-only options for the find operation * @returns {promise} - { saved_objects: [{ id, type, version, attributes }], total, per_page, page } */ find( @@ -171,7 +176,7 @@ export interface ISavedObjectsRepository { * Returns an array of objects by id * * @param {array} objects - an array of objects containing id, type and optionally fields - * @param {object} [options={}] + * @param {object} [options={}] {@link SavedObjectsBaseOptions} - options for the bulk get operation * @property {string} [options.namespace] * @returns {promise} - { saved_objects: [{ id, type, version, attributes }] } * @example @@ -190,7 +195,7 @@ export interface ISavedObjectsRepository { * Resolves an array of objects by id, using any legacy URL aliases if they exist * * @param {array} objects - an array of objects containing id, type - * @param {object} [options={}] + * @param {object} [options={}] {@link SavedObjectsBaseOptions} - options for the bulk resolve operation * @property {string} [options.namespace] * @returns {promise} - { resolved_objects: [{ saved_object, outcome }] } * @example @@ -208,9 +213,9 @@ export interface ISavedObjectsRepository { /** * Gets a single object * - * @param {string} type - * @param {string} id - * @param {object} [options={}] + * @param {string} type - the type of the object to get + * @param {string} id - the ID of the object to get + * @param {object} [options={}] {@link SavedObjectsBaseOptions} - options for the get operation * @property {string} [options.namespace] * @returns {promise} - { id, type, version, attributes } */ @@ -223,9 +228,9 @@ export interface ISavedObjectsRepository { /** * Resolves a single object, using any legacy URL alias if it exists * - * @param {string} type - * @param {string} id - * @param {object} [options={}] + * @param {string} type - the type of the object to resolve + * @param {string} id - the id of the object to resolve + * @param {object} [options={}] {@link SavedObjectsBaseOptions} - options for the resolve operation * @property {string} [options.namespace] * @returns {promise} - { saved_object, outcome } */ @@ -238,13 +243,13 @@ export interface ISavedObjectsRepository { /** * Updates an object * - * @param {string} type - * @param {string} id - * @param {object} [options={}] + * @param {string} type - the type of the object to update + * @param {string} id - the ID of the object to update + * @param {object} [options={}] {@link SavedObjectsUpdateOptions} - options for the update operation * @property {string} options.version - ensures version matches that of persisted object * @property {string} [options.namespace] * @property {array} [options.references] - [{ name, type, id }] - * @returns {promise} + * @returns {promise} - updated saved object */ update( type: string, @@ -257,7 +262,9 @@ export interface ISavedObjectsRepository { * Gets all references and transitive references of the given objects. Ignores any object and/or reference that is not a multi-namespace * type. * - * @param objects The objects to get the references for. + * @param {array} objects - The objects to get the references for (contains type and ID) + * @param {object} options {@link SavedObjectsCollectMultiNamespaceReferencesOptions} - the options for the operation + * @returns {promise} - {@link SavedObjectsCollectMultiNamespaceReferencesResponse} { objects: [{ type, id, spaces, inboundReferences, ... }] } */ collectMultiNamespaceReferences( objects: SavedObjectsCollectMultiNamespaceReferencesObject[], @@ -267,10 +274,11 @@ export interface ISavedObjectsRepository { /** * Updates one or more objects to add and/or remove them from specified spaces. * - * @param objects - * @param spacesToAdd - * @param spacesToRemove - * @param options + * @param {array} objects - array of objects to update (contains type, ID, and optional parameters) + * @param {array} spacesToAdd - array of spaces in which the objects should be added + * @param {array} spacesToRemove - array of spaces from which the objects should be removed + * @param {object} options {@link SavedObjectsUpdateObjectsSpacesOptions} - options for the operation + * @returns {promise} - { objects: [{ id, type, spaces, error: { message } }] } */ updateObjectsSpaces( objects: SavedObjectsUpdateObjectsSpacesObject[], @@ -282,7 +290,8 @@ export interface ISavedObjectsRepository { /** * Updates multiple objects in bulk * - * @param {array} objects - [{ type, id, attributes, options: { version, namespace } references }] + * @param {array} objects - array of objects to update (contains type, id, attributes, options: { version, namespace } references) + * @param {object} options {@link SavedObjectsBulkUpdateOptions} - options for the bulk update operation * @property {string} options.version - ensures version matches that of persisted object * @property {string} [options.namespace] * @returns {promise} - {saved_objects: [[{ id, type, version, references, attributes, error: { message } }]} @@ -298,6 +307,11 @@ export interface ISavedObjectsRepository { * @remarks Will throw a conflict error if the `update_by_query` operation returns any failure. In that case * some references might have been removed, and some were not. It is the caller's responsibility * to handle and fix this situation if it was to happen. + * + * @param {string} type - the type of the object to remove references to + * @param {string} id - the ID of the object to remove references to + * @param {object} options {@link SavedObjectsRemoveReferencesToOptions} - options for the remove references operation + * @returns {promise} - { number - the number of objects that have been updated by this operation } */ removeReferencesTo( type: string, @@ -352,11 +366,11 @@ export interface ISavedObjectsRepository { * ) * ``` * - * @param type - The type of saved object whose fields should be incremented - * @param id - The id of the document whose fields should be incremented - * @param counterFields - An array of field names to increment or an array of {@link SavedObjectsIncrementCounterField} - * @param options - {@link SavedObjectsIncrementCounterOptions} - * @returns The saved object after the specified fields were incremented + * @param {string} type - The type of saved object whose fields should be incremented + * @param {string} id - The id of the document whose fields should be incremented + * @param {array} counterFields - An array of field names to increment or an array of {@link SavedObjectsIncrementCounterField} + * @param {object} options {@link SavedObjectsIncrementCounterOptions} + * @returns {promise} - The saved object after the specified fields were incremented */ incrementCounter( type: string, @@ -395,11 +409,11 @@ export interface ISavedObjectsRepository { * await savedObjectsClient.closePointInTime(page2.pit_id); * ``` * - * @param {string|Array} type - * @param {object} [options] - {@link SavedObjectsOpenPointInTimeOptions} + * @param {string|Array} type - the type or types for the PIT + * @param {object} [options] {@link SavedObjectsOpenPointInTimeOptions} - options for the open PIT operation * @property {string} [options.keepAlive] * @property {string} [options.preference] - * @returns {promise} - { id: string } + * @returns {promise} - { id - the ID for the PIT } */ openPointInTimeForType( type: string | string[], @@ -444,9 +458,9 @@ export interface ISavedObjectsRepository { * await repository.closePointInTime(response.pit_id); * ``` * - * @param {string} id - * @param {object} [options] - {@link SavedObjectsClosePointInTimeOptions} - * @returns {promise} - {@link SavedObjectsClosePointInTimeResponse} + * @param {string} id - ID of the saved object + * @param {object} [options] {@link SavedObjectsClosePointInTimeOptions} - options for the close PIT operation + * @returns {promise} - { succeeded, num_freed - number of contexts closed } */ closePointInTime( id: string, diff --git a/packages/core/saved-objects/core-saved-objects-common/src/saved_objects.ts b/packages/core/saved-objects/core-saved-objects-common/src/saved_objects.ts index f98c39871353f8..b9ea336d349bd7 100644 --- a/packages/core/saved-objects/core-saved-objects-common/src/saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-common/src/saved_objects.ts @@ -64,6 +64,7 @@ export interface SavedObjectReference { * @public */ export interface SavedObjectsMigrationVersion { + /** The plugin name and version string */ [pluginName: string]: string; } @@ -78,6 +79,7 @@ export interface SavedObject { created_at?: string; /** Timestamp of the last time this document had been updated. */ updated_at?: string; + /** Error associated with this object, populated if an operation failed for this object. */ error?: SavedObjectError; /** The data for a Saved Object is stored as an object in the `attributes` property. **/ attributes: T; From 33cb8e4d77cbbcccfeff8d8a495a9f79683fcd38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 3 Nov 2022 07:36:57 +0100 Subject: [PATCH 25/47] Adds missing TSDoc comments - round 2. Removes redundant ISavedObjectsRepository definition from repository. --- .../src/lib/repository.ts | 13 +++---- .../src/apis/bulk_create.ts | 9 +++-- .../src/apis/check_conflicts.ts | 5 +++ .../src/saved_objects_client.ts | 39 ++++++++++--------- .../src/saved_objects_repository.ts | 9 +++++ .../src/merge_migration_maps.ts | 4 ++ 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 553ee369617026..e2c20c7485f95f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -58,6 +58,7 @@ import type { SavedObjectsBulkDeleteOptions, SavedObjectsBulkDeleteResponse, SavedObjectsFindInternalOptions, + ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; import { SavedObjectSanitizedDoc, @@ -158,13 +159,6 @@ export const DEFAULT_RETRY_COUNT = 3; const MAX_CONCURRENT_ALIAS_DELETIONS = 10; -/** - * See {@link SavedObjectsRepository} - * - * @public - */ -export type ISavedObjectsRepository = Pick; - /** * @internal */ @@ -199,6 +193,11 @@ function isMgetDoc(doc?: estypes.MgetResponseItem): doc is estypes.GetG } /** + * Saved Objects Respositiry - the client entry point for saved object manipulation. + * + * The SOR calls the Elasticsearch client and leverages extension implementations to + * support spaces, security, and encryption features. + * * @public */ export class SavedObjectsRepository implements ISavedObjectsRepository { diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts index 8538f191764fb7..7a38a909155ffb 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts @@ -17,14 +17,15 @@ import type { * @public */ export interface SavedObjectsBulkCreateObject { + /** Optional ID of the object to create (the ID is generated by default) */ id?: string; - /** the type of object to create */ + /** The type of object to create */ type: string; - /** the attributes for the object to create */ + /** The attributes for the object to create */ attributes: T; - /** the version string for the object to create */ + /** The version string for the object to create */ version?: string; - /** array of references to other saved objects */ + /** Array of references to other saved objects */ references?: SavedObjectReference[]; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion?: SavedObjectsMigrationVersion; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts index 38d1c281b1000b..331b95519a91c0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts @@ -9,19 +9,24 @@ import type { SavedObjectError } from '@kbn/core-saved-objects-common'; /** + * Object parameters for the check conficts operation * * @public */ export interface SavedObjectsCheckConflictsObject { + /** The ID of the object to check */ id: string; + /** The type of the object to check */ type: string; } /** + * Return type of the Saved Objects `checkConflicts()` method. * * @public */ export interface SavedObjectsCheckConflictsResponse { + /** Array of errors (contains the conflicting object ID, type, and error details) */ errors: Array<{ id: string; type: string; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts index 95361d74d781af..9fd83a603cb6b8 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts @@ -114,7 +114,7 @@ export interface SavedObjectsClientContract { * * @param type - the type of saved object to create * @param attributes - attributes for the saved object - * @param options {@link SavedObjectsCreateOptions} + * @param options {@link SavedObjectsCreateOptions} - options for the create operation * @returns the created saved object */ create( @@ -140,7 +140,7 @@ export interface SavedObjectsClientContract { * multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten. * * @param objects - array of objects to check (contains ID and type) - * @param options {@link SavedObjectsBaseOptions} + * @param options {@link SavedObjectsBaseOptions} - options for the check conflicts operation * @returns the {@link SavedObjectsCheckConflictsResponse} */ checkConflicts( @@ -153,7 +153,7 @@ export interface SavedObjectsClientContract { * * @param type - the type of saved object to delete * @param id - the ID of the saved object to delete - * @param options {@link SavedObjectsDeleteOptions} + * @param options {@link SavedObjectsDeleteOptions} - options for the delete operation */ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; @@ -161,7 +161,7 @@ export interface SavedObjectsClientContract { * Deletes multiple SavedObjects batched together as a single request * * @param objects - array of objects to delete (contains ID and type) - * @param options {@link SavedObjectsBulkDeleteOptions} + * @param options {@link SavedObjectsBulkDeleteOptions} - options for the bulk delete operation * @returns the {@link SavedObjectsBulkDeleteResponse} */ bulkDelete( @@ -171,7 +171,7 @@ export interface SavedObjectsClientContract { /** * Find all SavedObjects matching the search query * - * @param options {@link SavedObjectsFindOptions} + * @param options {@link SavedObjectsFindOptions} - options for the find operation * @returns the {@link SavedObjectsFindResponse} */ find( @@ -182,7 +182,7 @@ export interface SavedObjectsClientContract { * Returns an array of objects by id * * @param objects - array of objects to get (contains id, type, and optional fields) - * @param options {@link SavedObjectsBaseOptions} + * @param options {@link SavedObjectsBaseOptions} - options for the bulk get operation * @returns the {@link SavedObjectsBulkResponse} * @example * @@ -201,7 +201,7 @@ export interface SavedObjectsClientContract { * * @param type - The type of the object to retrieve * @param id - The ID of the object to retrieve - * @param options {@link SavedObjectsBaseOptions} + * @param options {@link SavedObjectsBaseOptions} - options for the get operation */ get( type: string, @@ -213,7 +213,7 @@ export interface SavedObjectsClientContract { * Resolves an array of objects by id, using any legacy URL aliases if they exist * * @param objects - an array of objects to resolve (contains id and type) - * @param options {@link SavedObjectsBaseOptions} + * @param options {@link SavedObjectsBaseOptions} - options for the bulk resolve operation * @returns the {@link SavedObjectsBulkResolveResponse} * @example * @@ -236,7 +236,7 @@ export interface SavedObjectsClientContract { * * @param type - The type of SavedObject to retrieve * @param id - The ID of the SavedObject to retrieve - * @param options {@link SavedObjectsBaseOptions} + * @param options {@link SavedObjectsBaseOptions} - options for the resolve operation * @returns the {@link SavedObjectsResolveResponse} */ resolve( @@ -249,8 +249,9 @@ export interface SavedObjectsClientContract { * Updates an SavedObject * * @param type - The type of SavedObject to update - * @param id - The ID of the SavedObject to retrieve - * @param options {@link SavedObjectsUpdateOptions} + * @param id - The ID of the SavedObject to update + * @param attributes - Attributes to update + * @param options {@link SavedObjectsUpdateOptions} - options for the update operation * @returns the {@link SavedObjectsUpdateResponse} */ update( @@ -264,7 +265,7 @@ export interface SavedObjectsClientContract { * Bulk Updates multiple SavedObject at once * * @param objects - array of objects to update (contains ID, type, attributes, and optional namespace) - * @param options {@link SavedObjectsBulkUpdateOptions} + * @param options {@link SavedObjectsBulkUpdateOptions} - options for the bulkUpdate operation * @returns the {@link SavedObjectsBulkUpdateResponse} */ bulkUpdate( @@ -277,7 +278,7 @@ export interface SavedObjectsClientContract { * * @param type - the type of the object to remove references to * @param id - the ID of the object to remove references to - * @param options {@link SavedObjectsRemoveReferencesToOptions} + * @param options {@link SavedObjectsRemoveReferencesToOptions} - options for the remove references opertion * @returns the {@link SavedObjectsRemoveReferencesToResponse} */ removeReferencesTo( @@ -295,7 +296,7 @@ export interface SavedObjectsClientContract { * {@link SavedObjectsClient.createPointInTimeFinder} method. * * @param type - the type or array of types - * @param options {@link SavedObjectsOpenPointInTimeOptions} + * @param options {@link SavedObjectsOpenPointInTimeOptions} - options for the open PIT for type operation * @returns the {@link SavedObjectsOpenPointInTimeResponse} */ openPointInTimeForType( @@ -312,7 +313,7 @@ export interface SavedObjectsClientContract { * {@link SavedObjectsClient.createPointInTimeFinder} method. * * @param id - the ID of the PIT to close - * @param options {@link SavedObjectsClosePointInTimeOptions} + * @param options {@link SavedObjectsClosePointInTimeOptions} - options for the close PIT operation * @returns the {@link SavedObjectsClosePointInTimeResponse} */ closePointInTime( @@ -365,8 +366,8 @@ export interface SavedObjectsClientContract { * } * ``` * - * @param findOptions {@link SavedObjectsCreatePointInTimeFinderOptions} - * @param dependencies {@link SavedObjectsCreatePointInTimeFinderDependencies} + * @param findOptions {@link SavedObjectsCreatePointInTimeFinderOptions} - options for the create PIT finder operation + * @param dependencies {@link SavedObjectsCreatePointInTimeFinderDependencies} - dependencies for the create PIT fimder operation * @returns the created PIT finder */ createPointInTimeFinder( @@ -378,7 +379,7 @@ export interface SavedObjectsClientContract { * Gets all references and transitive references of the listed objects. Ignores any object that is not a multi-namespace type. * * @param objects - array of objects to collect references for (contains ID and type) - * @param options {@link SavedObjectsCollectMultiNamespaceReferencesOptions} + * @param options {@link SavedObjectsCollectMultiNamespaceReferencesOptions} - options for the collect multi namespace references operation * @retuns the {@link SavedObjectsCollectMultiNamespaceReferencesResponse} */ collectMultiNamespaceReferences( @@ -392,7 +393,7 @@ export interface SavedObjectsClientContract { * @param objects - array of objects to update (contains ID, type, and optional internal-only parameters) * @param spacesToAdd - array of spaces each object should be included in * @param spacesToRemove - array of spaces each object should not be included in - * @param options {@link SavedObjectsUpdateObjectsSpacesOptions} + * @param options {@link SavedObjectsUpdateObjectsSpacesOptions} - options for the update spaces operation * @returns the {@link SavedObjectsUpdateObjectsSpacesResponse} */ updateObjectsSpaces( diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index c86ddb0b35d1ef..6181cd51a4e695 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -148,6 +148,8 @@ export interface ISavedObjectsRepository { ): Promise; /** + * Find saved objects by query + * * @param {object} [options={}] {@link SavedObjectsFindOptions} - options for the find operation * @property {(string|Array)} [options.type] * @property {string} [options.search] @@ -245,6 +247,7 @@ export interface ISavedObjectsRepository { * * @param {string} type - the type of the object to update * @param {string} id - the ID of the object to update + * @param {object} attributes - attributes to update * @param {object} [options={}] {@link SavedObjectsUpdateOptions} - options for the update operation * @property {string} options.version - ensures version matches that of persisted object * @property {string} [options.namespace] @@ -413,6 +416,7 @@ export interface ISavedObjectsRepository { * @param {object} [options] {@link SavedObjectsOpenPointInTimeOptions} - options for the open PIT operation * @property {string} [options.keepAlive] * @property {string} [options.preference] + * @param {object} internalOptions {@link SavedObjectsFindInternalOptions} - internal options for the open PIT operation * @returns {promise} - { id - the ID for the PIT } */ openPointInTimeForType( @@ -460,6 +464,7 @@ export interface ISavedObjectsRepository { * * @param {string} id - ID of the saved object * @param {object} [options] {@link SavedObjectsClosePointInTimeOptions} - options for the close PIT operation + * @param {object} internalOptions {@link SavedObjectsFindInternalOptions} - internal options for the close PIT operation * @returns {promise} - { succeeded, num_freed - number of contexts closed } */ closePointInTime( @@ -494,6 +499,10 @@ export interface ISavedObjectsRepository { * PIT will automatically be closed for you once you reach the last page * of results, or if the underlying call to `find` fails for any reason. * + * @param {object} findOptions - {@link SavedObjectsCreatePointInTimeFinderOptions} - the options for creating the point-in-time finder + * @param {object} dependencies - {@link SavedObjectsCreatePointInTimeFinderDependencies} - the dependencies for creating the point-in-time finder + * @returns - the point-in-time finder {@link ISavedObjectsPointInTimeFinder} + * * @example * ```ts * const findOptions: SavedObjectsCreatePointInTimeFinderOptions = { diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts index e5f2f1d74a7cf1..6d7c1c90d25df1 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts @@ -27,6 +27,10 @@ import type { * mergeSavedObjectMigrationMaps({ '1.2.3': f }, { '1.2.3': g }) -> { '1.2.3': (doc, context) => f(g(doc, context), context) } * * @public + * + * @param map1 - The first map to merge + * @param map2 - The second map to merge + * @returns The merged map {@link SavedObjectMigrationMap} */ export const mergeSavedObjectMigrationMaps = ( map1: SavedObjectMigrationMap, From 6a586e36deac2cdc8fbd042c209fd02e07742c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 3 Nov 2022 11:04:23 +0100 Subject: [PATCH 26/47] Removes 'import type' in so package domain. Updates mocks for find shared origin objects and find legacy url aliases tests to remove reference to repository mock. Refactor of scoped client provider getExensions. --- .../src/apis/bulk_create.ts | 2 +- .../src/apis/bulk_resolve.ts | 2 +- .../src/apis/bulk_update.ts | 2 +- .../src/apis/create.ts | 5 +-- .../src/apis/find.ts | 4 +- .../src/apis/resolve.ts | 2 +- .../src/apis/update.ts | 2 +- .../src/saved_objects_client.ts | 6 +-- .../src/simple_saved_object.ts | 2 +- .../src/lib/aggregations/validation.ts | 2 +- .../src/lib/aggregations/validation_utils.ts | 2 +- ...ct_multi_namespace_references.test.mock.ts | 6 +-- ...collect_multi_namespace_references.test.ts | 4 +- .../lib/collect_multi_namespace_references.ts | 8 ++-- .../src/lib/decorate_es_error.ts | 2 +- .../src/lib/filter_utils.ts | 2 +- .../lib/find_shared_origin_objects.test.ts | 21 +++++----- .../src/lib/find_shared_origin_objects.ts | 2 +- .../lib/internal_bulk_resolve.test.mock.ts | 4 +- .../src/lib/internal_bulk_resolve.test.ts | 4 +- .../src/lib/internal_bulk_resolve.ts | 8 ++-- .../src/lib/internal_utils.test.ts | 2 +- .../src/lib/internal_utils.ts | 6 +-- .../delete_legacy_url_aliases.test.mock.ts | 2 +- .../delete_legacy_url_aliases.test.ts | 2 +- .../delete_legacy_url_aliases.ts | 4 +- .../find_legacy_url_aliases.test.ts | 21 +++++----- .../find_legacy_url_aliases.ts | 2 +- .../src/lib/point_in_time_finder.test.ts | 2 +- .../src/lib/point_in_time_finder.ts | 4 +- .../preflight_check_for_create.test.mock.ts | 4 +- .../lib/preflight_check_for_create.test.ts | 6 +-- .../src/lib/preflight_check_for_create.ts | 6 +-- .../src/lib/repository.test.mock.ts | 12 +++--- .../src/lib/repository.test.ts | 8 ++-- .../src/lib/repository.ts | 12 +++--- .../repository_bulk_delete_internal_types.ts | 4 +- .../src/lib/repository_es_client.ts | 4 +- .../src/lib/scoped_client_provider.test.ts | 2 +- .../src/lib/scoped_client_provider.ts | 38 ++++++++++--------- .../src/lib/search_dsl/pit_params.ts | 2 +- .../src/lib/search_dsl/query_params.ts | 4 +- .../src/lib/search_dsl/references_filter.ts | 4 +- .../src/lib/search_dsl/search_dsl.ts | 8 ++-- .../lib/update_objects_spaces.test.mock.ts | 4 +- .../src/lib/update_objects_spaces.test.ts | 4 +- .../src/lib/update_objects_spaces.ts | 10 ++--- .../src/mocks/kibana_migrator.mock.ts | 4 +- .../src/mocks/point_in_time_finder.mock.ts | 4 +- .../src/mocks/repository.mock.ts | 2 +- .../src/saved_objects_client.test.ts | 2 +- .../src/saved_objects_client.ts | 4 +- .../src/point_in_time_finder.mock.ts | 2 +- .../src/repository.mock.ts | 2 +- .../src/saved_objects_client.mock.ts | 2 +- .../src/saved_objects_extensions.mock.ts | 2 +- .../src/scoped_client_provider.mock.ts | 2 +- .../tsconfig.json | 2 - .../src/apis/base.ts | 2 +- .../src/apis/bulk_create.ts | 5 +-- .../src/apis/bulk_delete.ts | 4 +- .../src/apis/bulk_resolve.ts | 2 +- .../src/apis/bulk_update.ts | 4 +- .../src/apis/check_conflicts.ts | 2 +- .../src/apis/close_point_in_time.ts | 2 +- .../apis/collect_multinamespace_references.ts | 2 +- .../src/apis/create.ts | 7 +--- .../src/apis/create_point_in_time_finder.ts | 2 +- .../src/apis/delete.ts | 2 +- .../src/apis/delete_by_namespace.ts | 2 +- .../src/apis/find.ts | 4 +- .../src/apis/increment_counter.ts | 4 +- .../src/apis/remove_references_to.ts | 2 +- .../src/apis/resolve.ts | 2 +- .../src/apis/update.ts | 4 +- .../src/apis/update_objects_spaces.ts | 4 +- .../src/saved_objects_client.ts | 4 +- .../src/saved_objects_repository.ts | 4 +- .../src/mappings/lib/get_property.test.ts | 4 +- .../src/mappings/lib/get_property.ts | 2 +- .../lib/get_root_properties_objects.ts | 2 +- .../src/mappings/types.ts | 2 +- .../src/migration/kibana_migrator.ts | 4 +- .../src/saved_objects_config.ts | 2 +- .../src/saved_objects_type_registry.test.ts | 2 +- .../src/saved_objects_type_registry.ts | 2 +- .../src/serialization/serializer.test.ts | 2 +- .../src/serialization/serializer.ts | 2 +- .../src/utils/get_index_for_type.ts | 2 +- .../src/validation/schema.test.ts | 2 +- .../src/validation/schema.ts | 2 +- .../src/validation/validator.test.ts | 2 +- .../src/validation/validator.ts | 4 +- .../src/saved_objects_type_registry.mock.ts | 4 +- .../src/serializer.mock.ts | 2 +- .../src/saved_objects_client.ts | 8 ++-- .../src/saved_objects_service.ts | 6 +-- .../src/simple_saved_object.test.ts | 4 +- .../src/simple_saved_object.ts | 4 +- .../src/saved_objects_service.mock.ts | 6 +-- .../src/simple_saved_object.mock.ts | 4 +- .../src/contracts.ts | 2 +- .../export/apply_export_transforms.test.ts | 4 +- .../src/export/apply_export_transforms.ts | 6 +-- .../export/collect_exported_objects.test.ts | 4 +- .../src/export/collect_exported_objects.ts | 10 ++--- .../src/export/errors.ts | 2 +- .../src/export/saved_objects_exporter.test.ts | 2 +- .../src/export/saved_objects_exporter.ts | 10 ++--- .../src/export/sort_objects.test.ts | 2 +- .../src/export/sort_objects.ts | 2 +- .../src/export/utils.test.ts | 2 +- .../src/export/utils.ts | 2 +- .../src/import/errors.ts | 2 +- .../import/import_saved_objects.test.mock.ts | 16 ++++---- .../src/import/import_saved_objects.test.ts | 8 ++-- .../src/import/import_saved_objects.ts | 6 +-- .../src/import/lib/check_conflicts.test.ts | 4 +- .../src/import/lib/check_conflicts.ts | 6 +-- .../lib/check_origin_conflicts.test.mock.ts | 2 +- .../import/lib/check_origin_conflicts.test.ts | 8 ++-- .../src/import/lib/check_origin_conflicts.ts | 8 ++-- .../lib/check_reference_origins.test.mock.ts | 2 +- .../lib/check_reference_origins.test.ts | 6 +-- .../src/import/lib/check_reference_origins.ts | 6 +-- .../src/import/lib/collect_saved_objects.ts | 4 +- .../src/import/lib/create_objects_filter.ts | 2 +- .../import/lib/create_saved_objects.test.ts | 2 +- .../src/import/lib/create_saved_objects.ts | 8 ++-- .../import/lib/execute_import_hooks.test.ts | 4 +- .../src/import/lib/execute_import_hooks.ts | 4 +- .../src/import/lib/extract_errors.test.ts | 4 +- .../src/import/lib/extract_errors.ts | 4 +- .../get_import_state_map_for_retries.test.ts | 2 +- .../lib/get_import_state_map_for_retries.ts | 4 +- .../src/import/lib/regenerate_ids.test.ts | 2 +- .../src/import/lib/regenerate_ids.ts | 4 +- .../src/import/lib/split_overwrites.ts | 2 +- .../import/lib/validate_references.test.ts | 2 +- .../src/import/lib/validate_references.ts | 6 +-- .../src/import/lib/validate_retries.test.ts | 2 +- .../src/import/lib/validate_retries.ts | 2 +- .../import/resolve_import_errors.test.mock.ts | 24 ++++++------ .../src/import/resolve_import_errors.test.ts | 6 +-- .../src/import/resolve_import_errors.ts | 6 +-- .../src/import/saved_objects_importer.ts | 6 +-- .../tsconfig.json | 2 - .../src/saved_objects_exporter.mock.ts | 2 +- .../src/saved_objects_importer.mock.ts | 2 +- .../bulk_overwrite_transformed_documents.ts | 8 ++-- .../src/actions/calculate_exclude_filters.ts | 8 ++-- .../actions/check_for_unknown_docs.test.ts | 2 +- .../src/actions/check_for_unknown_docs.ts | 6 +-- .../src/actions/clone_index.ts | 4 +- .../src/actions/close_pit.ts | 2 +- .../src/actions/create_index.ts | 4 +- .../src/actions/es_errors.ts | 2 +- .../src/actions/fetch_indices.ts | 4 +- .../src/actions/index.ts | 8 ++-- .../src/actions/initialize_action.ts | 2 +- .../src/actions/open_pit.ts | 2 +- .../src/actions/pickup_updated_mappings.ts | 2 +- .../src/actions/read_with_pit.ts | 4 +- .../src/actions/refresh_index.ts | 2 +- .../src/actions/reindex.ts | 4 +- .../src/actions/remove_write_block.ts | 2 +- .../actions/search_for_outdated_documents.ts | 4 +- .../src/actions/set_write_block.ts | 2 +- .../src/actions/transform_docs.ts | 4 +- .../src/actions/update_aliases.ts | 2 +- .../src/actions/update_and_pickup_mappings.ts | 4 +- .../src/actions/wait_for_index_status.ts | 2 +- .../src/actions/wait_for_reindex_task.ts | 2 +- .../src/actions/wait_for_task.ts | 4 +- .../src/core/build_active_mappings.test.ts | 2 +- .../src/core/build_active_mappings.ts | 4 +- .../src/core/build_index_map.test.ts | 2 +- .../src/core/build_index_map.ts | 4 +- .../disable_unknown_type_mapping_fields.ts | 4 +- .../src/core/document_migrator.test.ts | 2 +- .../src/core/document_migrator.ts | 6 +-- .../src/core/migrate_raw_docs.ts | 2 +- .../src/core/migration_logger.ts | 4 +- .../src/core/unused_types.ts | 2 +- .../src/initial_state.test.ts | 2 +- .../src/initial_state.ts | 12 +++--- .../src/kibana_migrator.test.ts | 4 +- .../src/kibana_migrator.ts | 10 ++--- .../src/migrations_state_action_machine.ts | 6 +-- .../src/migrations_state_machine_cleanup.ts | 4 +- .../src/model/create_batches.test.ts | 2 +- .../src/model/create_batches.ts | 2 +- .../src/model/extract_errors.ts | 4 +- .../src/model/helpers.ts | 8 ++-- .../src/model/model.test.ts | 4 +- .../src/model/model.ts | 8 ++-- .../src/model/progress.test.ts | 2 +- .../src/model/progress.ts | 2 +- .../src/next.test.ts | 2 +- .../src/next.ts | 6 +-- .../src/run_resilient_migrator.ts | 14 +++---- .../src/state.ts | 16 ++++---- .../src/types.ts | 2 +- .../src/kibana_migrator.mock.ts | 4 +- .../src/migration.mocks.ts | 2 +- .../src/deprecations/deprecation_factory.ts | 6 +-- .../deprecations/unknown_object_types.test.ts | 4 +- .../src/deprecations/unknown_object_types.ts | 6 +-- .../src/internal_types.ts | 6 +-- .../src/mocks/internal_mocks.ts | 4 +- .../src/object_types/registration.ts | 2 +- .../src/routes/bulk_create.ts | 4 +- .../src/routes/bulk_delete.ts | 4 +- .../src/routes/bulk_get.ts | 4 +- .../src/routes/bulk_resolve.ts | 4 +- .../src/routes/bulk_update.ts | 4 +- .../src/routes/create.ts | 4 +- .../src/routes/delete.ts | 4 +- .../deprecations/delete_unknown_types.ts | 2 +- .../src/routes/export.ts | 10 ++--- .../src/routes/find.ts | 4 +- .../src/routes/get.ts | 4 +- .../src/routes/import.ts | 6 +-- .../src/routes/index.ts | 10 ++--- .../src/routes/legacy_import_export/export.ts | 6 +-- .../src/routes/legacy_import_export/import.ts | 8 ++-- .../lib/collect_references_deep.test.ts | 2 +- .../lib/collect_references_deep.ts | 4 +- .../lib/export_dashboards.ts | 2 +- .../lib/import_dashboards.test.ts | 2 +- .../lib/import_dashboards.ts | 4 +- .../src/routes/migrate.ts | 4 +- .../src/routes/resolve.ts | 4 +- .../src/routes/resolve_import_errors.ts | 6 +-- .../src/routes/update.ts | 6 +-- .../src/routes/utils.test.ts | 2 +- .../src/routes/utils.ts | 6 +-- .../saved_objects_route_handler_context.ts | 8 ++-- .../src/saved_objects_service.test.ts | 4 +- .../src/saved_objects_service.ts | 22 +++++------ .../src/status.ts | 4 +- .../src/saved_objects_service.mock.ts | 6 +-- .../tsconfig.json | 2 - .../src/client_factory.ts | 14 +++---- .../src/contracts.ts | 16 ++++---- .../src/encryption.ts | 2 +- .../core-saved-objects-server/src/export.ts | 8 ++-- .../src/extensions.ts | 6 +-- .../src/mapping_definition.ts | 2 +- .../src/migration.ts | 4 +- .../src/request_handler_context.ts | 10 ++--- .../src/saved_objects_management.ts | 6 +-- .../src/saved_objects_type.ts | 16 ++++---- .../core-saved-objects-server/src/security.ts | 2 +- .../src/serialization.ts | 2 +- .../src/type_registry.ts | 2 +- .../src/validation.ts | 2 +- .../src/merge_migration_maps.test.ts | 2 +- .../src/merge_migration_maps.ts | 2 +- .../src/saved_objects_utils.test.ts | 2 +- .../src/saved_objects_utils.ts | 2 +- 261 files changed, 593 insertions(+), 602 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts index 6d5ce23205ef82..3cfcfa2045a33a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsCreateOptions } from './create'; +import { SavedObjectsCreateOptions } from './create'; /** * Per-object parameters for bulk create operation diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts index c5ba4825252985..4fd5bd1a40e2cc 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ResolvedSimpleSavedObject } from './resolve'; +import { ResolvedSimpleSavedObject } from './resolve'; /** * Return type of the Saved Objects `bulkResolve()` method. diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts index c819fb1ac448d1..c14da0c80e6b42 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; +import { SavedObjectReference } from '@kbn/core-saved-objects-common'; /** * Per-object parameters for bulk update operation diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts index fb6a18219e5bf5..6db009f81d0290 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import type { - SavedObjectReference, - SavedObjectsMigrationVersion, -} from '@kbn/core-saved-objects-common'; +import { SavedObjectReference, SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; /** * Options for creating a saved object. diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts index 39d99a6ee6e86d..d10bb4d7a2ae49 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectsFindOptions as SavedObjectFindOptionsServer } from '@kbn/core-saved-objects-api-server'; -import type { SavedObjectsBatchResponse } from './base'; +import { SavedObjectsFindOptions as SavedObjectFindOptionsServer } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsBatchResponse } from './base'; export type { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts index c2383d7cb50d50..0b6c4636a0706e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsResolveResponse } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsResolveResponse } from '@kbn/core-saved-objects-api-server'; import { SimpleSavedObject } from '../simple_saved_object'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts index f8095e35df1e5d..0b491799af4b54 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; +import { SavedObjectReference } from '@kbn/core-saved-objects-common'; /** * Options for updating a saved object diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts index 9ff01104c5ba07..f094214df0dfb9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import { ResolvedSimpleSavedObject, SavedObjectsBatchResponse, SavedObjectsBulkCreateObject, @@ -23,7 +23,7 @@ import type { SavedObjectsBulkDeleteOptions, } from './apis'; -import type { SimpleSavedObject } from './simple_saved_object'; +import { SimpleSavedObject } from './simple_saved_object'; /** * The client-side SavedObjectsClient is a thin convenience library around the SavedObjects diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts index 8edb1347d6a889..51425fe923226b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; +import { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; /** * Very simple wrapper for SavedObjects loaded from the server diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts index b3a6bbae5e9561..273924271f9637 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts @@ -10,7 +10,7 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ObjectType } from '@kbn/config-schema'; import { isPlainObject, isArray } from 'lodash'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { isObjectTypeAttribute, rewriteObjectTypeAttribute, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts index 5548ad4d57a5dd..69ff90c642c739 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { fieldDefined, hasFilterKeyError } from '../filter_utils'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts index 5476f99c3b37dd..bb02460d766c3a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { findLegacyUrlAliases } from './legacy_url_aliases'; -import type { findSharedOriginObjects } from './find_shared_origin_objects'; -import type * as InternalUtils from './internal_utils'; +import { findLegacyUrlAliases } from './legacy_url_aliases'; +import { findSharedOriginObjects } from './find_shared_origin_objects'; +import * as InternalUtils from './internal_utils'; export const mockFindLegacyUrlAliases = jest.fn() as jest.MockedFunction< typeof findLegacyUrlAliases diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 4bb4606131cb00..76dce40be9569a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -13,7 +13,7 @@ import { } from './collect_multi_namespace_references.test.mock'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import type { +import { SavedObjectsCollectMultiNamespaceReferencesObject, SavedObjectsCollectMultiNamespaceReferencesOptions, } from '@kbn/core-saved-objects-api-server'; @@ -25,7 +25,7 @@ import { CollectMultiNamespaceReferencesParams, } from './collect_multi_namespace_references'; import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; -import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import { CreatePointInTimeFinderFn } from './point_in_time_finder'; import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; import { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts index 0db38392efd55f..f683320d646270 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts @@ -7,8 +7,8 @@ */ import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsCollectMultiNamespaceReferencesObject, SavedObjectsCollectMultiNamespaceReferencesOptions, SavedObjectsCollectMultiNamespaceReferencesResponse, @@ -28,8 +28,8 @@ import { import { findLegacyUrlAliases } from './legacy_url_aliases'; import { getRootFields } from './included_fields'; import { getSavedObjectFromSource, rawDocExistsInNamespace } from './internal_utils'; -import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; -import type { RepositoryEsClient } from './repository_es_client'; +import { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import { RepositoryEsClient } from './repository_es_client'; import { findSharedOriginObjects } from './find_shared_origin_objects'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts index 9cfdffc13a5ddc..e719c3433c8004 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts @@ -8,7 +8,7 @@ import { get } from 'lodash'; import { errors as esErrors } from '@elastic/elasticsearch'; -import type { ElasticsearchErrorDetails } from '@kbn/es-errors'; +import { ElasticsearchErrorDetails } from '@kbn/es-errors'; import { isSupportedEsServer } from '@kbn/core-elasticsearch-server-internal'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts index ae7bb08039850c..f4512ebc323076 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts @@ -10,7 +10,7 @@ import { set } from '@kbn/safer-lodash-set'; import { get, cloneDeep } from 'lodash'; import * as esKuery from '@kbn/es-query'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; type KueryNode = any; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts index e3464f69dec636..44c81bc6eb49f5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts @@ -6,12 +6,11 @@ * Side Public License, v 1. */ -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import type { CreatePointInTimeFinderFn, PointInTimeFinder } from './point_in_time_finder'; +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { CreatePointInTimeFinderFn, PointInTimeFinder } from './point_in_time_finder'; import { savedObjectsPointInTimeFinderMock } from '../mocks/point_in_time_finder.mock'; import { findSharedOriginObjects } from './find_shared_origin_objects'; -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; -import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { SavedObjectsPointInTimeFinderClient } from '@kbn/core-saved-objects-api-server'; interface MockFindResultParams { type: string; @@ -21,13 +20,13 @@ interface MockFindResultParams { } describe('findSharedOriginObjects', () => { - let savedObjectsMock: jest.Mocked; + let pitFinderClientMock: jest.Mocked; let pointInTimeFinder: DeeplyMockedKeys; let createPointInTimeFinder: jest.MockedFunction; beforeEach(() => { - savedObjectsMock = savedObjectsRepositoryMock.create(); - savedObjectsMock.find.mockResolvedValue({ + pitFinderClientMock = savedObjectsPointInTimeFinderMock.createClient(); + pitFinderClientMock.find.mockResolvedValue({ pit_id: 'foo', saved_objects: [], // the rest of these fields don't matter but are included for type safety @@ -35,12 +34,14 @@ describe('findSharedOriginObjects', () => { page: 1, per_page: 100, }); - pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ savedObjectsMock })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too + pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ + savedObjectsMock: pitFinderClientMock, + })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too createPointInTimeFinder = jest.fn().mockReturnValue(pointInTimeFinder); }); function mockFindResults(...results: MockFindResultParams[]) { - savedObjectsMock.find.mockResolvedValueOnce({ + pitFinderClientMock.find.mockResolvedValueOnce({ pit_id: 'foo', saved_objects: results.map(({ type, id, originId, namespaces }) => ({ type, @@ -133,7 +134,7 @@ describe('findSharedOriginObjects', () => { }); it('handles PointInTimeFinder.find errors', async () => { - savedObjectsMock.find.mockRejectedValue(new Error('Oh no!')); + pitFinderClientMock.find.mockRejectedValue(new Error('Oh no!')); const objects = [obj1, obj2, obj3]; await expect(() => findSharedOriginObjects(createPointInTimeFinder, objects)).rejects.toThrow( diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts index a489e4afa91c37..6ab78d07ef0989 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts @@ -9,7 +9,7 @@ import * as esKuery from '@kbn/es-query'; import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { getObjectKey } from '@kbn/core-saved-objects-base-server-internal'; -import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import { CreatePointInTimeFinderFn } from './point_in_time_finder'; interface ObjectOrigin { /** The object's type. */ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts index 277d1ae4af34ac..e3763d4107ad3d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import type * as InternalUtils from './internal_utils'; +import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; +import * as InternalUtils from './internal_utils'; export const mockGetSavedObjectFromSource = jest.fn() as jest.MockedFunction< typeof InternalUtils['getSavedObjectFromSource'] diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index b0e9bf5776b1c0..bbfe6e37093d3b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -13,8 +13,8 @@ import { } from './internal_bulk_resolve.test.mock'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsBulkResolveObject, SavedObjectsBaseOptions, } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts index 6e13671661b95d..7a46cd4f4186ca 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { MgetResponseItem } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { MgetResponseItem } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsBaseOptions, SavedObjectsBulkResolveObject, SavedObjectsResolveResponse, @@ -50,7 +50,7 @@ import { isLeft, isRight, } from './internal_utils'; -import type { RepositoryEsClient } from './repository_es_client'; +import { RepositoryEsClient } from './repository_es_client'; const MAX_CONCURRENT_RESOLVE = 10; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts index c295c0b3faad8a..aec44a4a1f9cea 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { encodeHitVersion } from '@kbn/core-saved-objects-base-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts index 73134f4855a370..599fe7c246dfdc 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { Payload } from '@hapi/boom'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { Payload } from '@hapi/boom'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { ISavedObjectTypeRegistry, SavedObjectsRawDoc, SavedObjectsRawDocSource, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts index 0922910936c521..7e326711133c8b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { getErrorMessage } from '@kbn/core-elasticsearch-client-server-internal'; +import { getErrorMessage } from '@kbn/core-elasticsearch-client-server-internal'; export const mockGetEsErrorMessage = jest.fn() as jest.MockedFunction; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts index 77a538672ff198..502a7caa77589a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts @@ -14,7 +14,7 @@ import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-m import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { deleteLegacyUrlAliases } from './delete_legacy_url_aliases'; -import type { DeleteLegacyUrlAliasesParams } from './delete_legacy_url_aliases'; +import { DeleteLegacyUrlAliasesParams } from './delete_legacy_url_aliases'; type SetupParams = Pick< DeleteLegacyUrlAliasesParams, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts index 73489308f59afc..15cd56227db59f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts @@ -9,13 +9,13 @@ import * as esKuery from '@kbn/es-query'; import { getErrorMessage as getEsErrorMessage } from '@kbn/core-elasticsearch-client-server-internal'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { LEGACY_URL_ALIAS_TYPE, type IndexMapping, } from '@kbn/core-saved-objects-base-server-internal'; -import type { RepositoryEsClient } from '../repository_es_client'; +import { RepositoryEsClient } from '../repository_es_client'; import { getSearchDsl } from '../search_dsl'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts index 49fe50e18776d6..41c4a3f726549d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts @@ -6,26 +6,25 @@ * Side Public License, v 1. */ -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; import { type LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE, } from '@kbn/core-saved-objects-base-server-internal'; -import type { CreatePointInTimeFinderFn, PointInTimeFinder } from '../point_in_time_finder'; +import { CreatePointInTimeFinderFn, PointInTimeFinder } from '../point_in_time_finder'; import { findLegacyUrlAliases } from './find_legacy_url_aliases'; import { savedObjectsPointInTimeFinderMock } from '../../mocks'; -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; -import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { SavedObjectsPointInTimeFinderClient } from '@kbn/core-saved-objects-api-server'; describe('findLegacyUrlAliases', () => { - let savedObjectsMock: jest.Mocked; + let pitFinderClientMock: jest.Mocked; let pointInTimeFinder: DeeplyMockedKeys; let createPointInTimeFinder: jest.MockedFunction; beforeEach(() => { - savedObjectsMock = savedObjectsRepositoryMock.create(); - savedObjectsMock.find.mockResolvedValue({ + pitFinderClientMock = savedObjectsPointInTimeFinderMock.createClient(); + pitFinderClientMock.find.mockResolvedValue({ pit_id: 'foo', saved_objects: [], // the rest of these fields don't matter but are included for type safety @@ -33,12 +32,14 @@ describe('findLegacyUrlAliases', () => { page: 1, per_page: 100, }); - pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ savedObjectsMock })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too + pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ + savedObjectsMock: pitFinderClientMock, + })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too createPointInTimeFinder = jest.fn().mockReturnValue(pointInTimeFinder); }); function mockFindResults(...results: LegacyUrlAlias[]) { - savedObjectsMock.find.mockResolvedValueOnce({ + pitFinderClientMock.find.mockResolvedValueOnce({ pit_id: 'foo', saved_objects: results.map((attributes) => ({ id: 'doesnt-matter', @@ -127,7 +128,7 @@ describe('findLegacyUrlAliases', () => { }); it('handles PointInTimeFinder.find errors', async () => { - savedObjectsMock.find.mockRejectedValue(new Error('Oh no!')); + pitFinderClientMock.find.mockRejectedValue(new Error('Oh no!')); const objects = [obj1, obj2, obj3]; await expect(() => findLegacyUrlAliases(createPointInTimeFinder, objects)).rejects.toThrow( diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts index 62bf5a51d88936..3bfcafce49ff4b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts @@ -12,7 +12,7 @@ import { LEGACY_URL_ALIAS_TYPE, getObjectKey, } from '@kbn/core-saved-objects-base-server-internal'; -import type { CreatePointInTimeFinderFn } from '../point_in_time_finder'; +import { CreatePointInTimeFinderFn } from '../point_in_time_finder'; interface FindLegacyUrlAliasesObject { type: string; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts index a8f9722c09e7a5..9d3655dd9a1f4d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts @@ -7,7 +7,7 @@ */ import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; -import type { +import { SavedObjectsFindResult, SavedObjectsCreatePointInTimeFinderOptions, } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts index 7dffbbdaa356fe..08e2f7b8b26fab 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { Logger } from '@kbn/logging'; -import type { +import { Logger } from '@kbn/logging'; +import { SavedObjectsFindOptions, SavedObjectsFindResponse, SavedObjectsCreatePointInTimeFinderDependencies, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts index fe8076b51e5dd4..1a299dd23a7e7e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { findLegacyUrlAliases } from './legacy_url_aliases'; -import type * as InternalUtils from './internal_utils'; +import { findLegacyUrlAliases } from './legacy_url_aliases'; +import * as InternalUtils from './internal_utils'; export const mockFindLegacyUrlAliases = jest.fn() as jest.MockedFunction< typeof findLegacyUrlAliases diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts index d23d2cf5e804e8..fc93473e863622 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts @@ -11,16 +11,16 @@ import { mockRawDocExistsInNamespaces, } from './preflight_check_for_create.test.mock'; -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { SavedObjectsSerializer, LEGACY_URL_ALIAS_TYPE, } from '@kbn/core-saved-objects-base-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import { CreatePointInTimeFinderFn } from './point_in_time_finder'; import { ALIAS_SEARCH_PER_PAGE, PreflightCheckForCreateObject, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts index 1d1b4839635e8c..d3ff4e10c812d4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts @@ -8,7 +8,7 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import type { +import { ISavedObjectTypeRegistry, SavedObjectsRawDoc, SavedObjectsRawDocSource, @@ -26,8 +26,8 @@ import { import { findLegacyUrlAliases } from './legacy_url_aliases'; import { Either, rawDocExistsInNamespaces } from './internal_utils'; import { isLeft, isRight } from './internal_utils'; -import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; -import type { RepositoryEsClient } from './repository_es_client'; +import { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import { RepositoryEsClient } from './repository_es_client'; /** * If the object will be created in this many spaces (or "*" all current and future spaces), we use find to fetch all aliases. diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts index a9c1871e2488e1..30190de044d28f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import type { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; -import type { internalBulkResolve } from './internal_bulk_resolve'; -import type * as InternalUtils from './internal_utils'; -import type { preflightCheckForCreate } from './preflight_check_for_create'; -import type { updateObjectsSpaces } from './update_objects_spaces'; -import type { deleteLegacyUrlAliases } from './legacy_url_aliases'; +import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; +import { internalBulkResolve } from './internal_bulk_resolve'; +import * as InternalUtils from './internal_utils'; +import { preflightCheckForCreate } from './preflight_check_for_create'; +import { updateObjectsSpaces } from './update_objects_spaces'; +import { deleteLegacyUrlAliases } from './legacy_url_aliases'; export const mockCollectMultiNamespaceReferences = jest.fn() as jest.MockedFunction< typeof collectMultiNamespaceReferences diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index 36b6cb59135661..aea8e448ad8439 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -20,11 +20,11 @@ import { mockGetSearchDsl, } from './repository.test.mock'; -import type { Payload } from '@hapi/boom'; +import { Payload } from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-common'; +import { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsUpdateObjectsSpacesResponse, @@ -48,7 +48,7 @@ import type { SavedObjectsBulkDeleteObject, SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectsRawDoc, SavedObjectsRawDocSource, SavedObjectUnsanitizedDoc, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index e2c20c7485f95f..c92405dc35ac40 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -8,17 +8,17 @@ import { omit, isObject } from 'lodash'; import Boom from '@hapi/boom'; -import type { Payload } from '@hapi/boom'; +import { Payload } from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import * as esKuery from '@kbn/es-query'; -import type { Logger } from '@kbn/logging'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Logger } from '@kbn/logging'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { isSupportedEsServer, isNotFoundFromUnsupportedServer, } from '@kbn/core-elasticsearch-server-internal'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsBaseOptions, SavedObjectsIncrementCounterOptions, SavedObjectsDeleteByNamespaceOptions, @@ -128,7 +128,7 @@ import { PreflightCheckForCreateResult, } from './preflight_check_for_create'; import { deleteLegacyUrlAliases } from './legacy_url_aliases'; -import type { +import { BulkDeleteParams, ExpectedBulkDeleteResult, BulkDeleteItemErrorResult, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts index 93d4354d8d7e84..6d1ff26e576bac 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts @@ -5,13 +5,13 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { Payload } from '@hapi/boom'; +import { Payload } from '@hapi/boom'; import { BulkOperationBase, BulkResponseItem, ErrorCause, } from '@elastic/elasticsearch/lib/api/types'; -import type { estypes, TransportResult } from '@elastic/elasticsearch'; +import { estypes, TransportResult } from '@elastic/elasticsearch'; import { Either } from './internal_utils'; import { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts index 14dcb996267892..4e46393e9b37e3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { TransportRequestOptions } from '@elastic/elasticsearch'; +import { TransportRequestOptions } from '@elastic/elasticsearch'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { retryCallCluster } from '@kbn/core-elasticsearch-server-internal'; import { decorateEsError } from './decorate_es_error'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts index 871b38fd89af80..c71d74e7f93cbf 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { Optional } from 'utility-types'; +import { Optional } from 'utility-types'; import { httpServerMock } from '@kbn/core-http-server-mocks'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { SavedObjectsClientProvider } from './scoped_client_provider'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index 413b900b3b48b4..e0fa0a16d2e8df 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { ISavedObjectTypeRegistry, SavedObjectsClientFactory, @@ -19,6 +19,9 @@ import { ENCRYPTION_EXTENSION_ID, SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID, + ISavedObjectsEncryptionExtension, + ISavedObjectsSecurityExtension, + ISavedObjectsSpacesExtension, } from '@kbn/core-saved-objects-server'; /** @@ -82,21 +85,22 @@ export class SavedObjectsClientProvider { } getExtensions(request: KibanaRequest, excludedExtensions: string[]): SavedObjectsExtensions { - const isEncryptionExtensionIncluded = - !excludedExtensions.includes(ENCRYPTION_EXTENSION_ID) && !!this.encryptionExtensionFactory; - const encryptionExtension = isEncryptionExtensionIncluded - ? this.encryptionExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) - : undefined; - const isSecurityExtensionIncluded = - !excludedExtensions.includes(SECURITY_EXTENSION_ID) && !!this.securityExtensionFactory; - const securityExtension = isSecurityExtensionIncluded - ? this.securityExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) - : undefined; - const isSpacesExtensionIncluded = - !excludedExtensions.includes(SPACES_EXTENSION_ID) && !!this.spacesExtensionFactory; - const spacesExtension = isSpacesExtensionIncluded - ? this.spacesExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) - : undefined; + const extensionInfo = [ + { id: ENCRYPTION_EXTENSION_ID, factory: this.encryptionExtensionFactory }, + { id: SECURITY_EXTENSION_ID, factory: this.securityExtensionFactory }, + { id: SPACES_EXTENSION_ID, factory: this.spacesExtensionFactory }, + ]; + + const extensions = extensionInfo.map((extension) => { + const isIncluded = !excludedExtensions.includes(extension.id) && !!extension.factory; + return isIncluded + ? extension.factory?.({ typeRegistry: this._typeRegistry, request }) + : undefined; + }); + + const encryptionExtension = extensions[0] as ISavedObjectsEncryptionExtension | undefined; + const securityExtension = extensions[1] as ISavedObjectsSecurityExtension | undefined; + const spacesExtension = extensions[2] as ISavedObjectsSpacesExtension | undefined; return { encryptionExtension, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts index c8be64e9a0494b..9e8dce4e7aae99 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; export function getPitParams(pit: SavedObjectsPitParams) { return { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts index 896b934c90b805..4a51c19bc8d5cd 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts @@ -7,11 +7,11 @@ */ import * as esKuery from '@kbn/es-query'; -import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; type KueryNode = any; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts index 4dd6bc640f1741..540210a7075251 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import type { SearchOperator } from './query_params'; +import { SearchOperator } from './query_params'; export function getReferencesFilter({ references, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts index 381f20069d25a9..5e55f5ef49881d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts @@ -9,10 +9,10 @@ import Boom from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; import { getQueryParams, SearchOperator } from './query_params'; import { getPitParams } from './pit_params'; import { getSortingParams } from './sorting_params'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts index 043975d5bb52b1..40a8ac88b596db 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type * as InternalUtils from './internal_utils'; -import type { deleteLegacyUrlAliases } from './legacy_url_aliases'; +import * as InternalUtils from './internal_utils'; +import { deleteLegacyUrlAliases } from './legacy_url_aliases'; export const mockGetBulkOperationError = jest.fn() as jest.MockedFunction< typeof InternalUtils['getBulkOperationError'] diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 9d3f8475bc5b1c..77c7e601cd68f7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -16,14 +16,14 @@ import { import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; -import type { SavedObjectsUpdateObjectsSpacesObject } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsUpdateObjectsSpacesObject } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers, ALL_NAMESPACES_STRING, } from '@kbn/core-saved-objects-utils-server'; import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import type { UpdateObjectsSpacesParams } from './update_objects_spaces'; +import { UpdateObjectsSpacesParams } from './update_objects_spaces'; import { updateObjectsSpaces } from './update_objects_spaces'; import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; import { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts index f5b810ea3f3af5..0c59161d7a25d3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts @@ -10,9 +10,9 @@ import pMap from 'p-map'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import intersection from 'lodash/intersection'; -import type { Logger } from '@kbn/logging'; +import { Logger } from '@kbn/logging'; import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import type { +import { SavedObjectsUpdateObjectsSpacesObject, SavedObjectsUpdateObjectsSpacesOptions, SavedObjectsUpdateObjectsSpacesResponse, @@ -30,7 +30,7 @@ import { type DecoratedError, SavedObjectsUtils, } from '@kbn/core-saved-objects-utils-server'; -import type { +import { IndexMapping, SavedObjectsSerializer, } from '@kbn/core-saved-objects-base-server-internal'; @@ -44,8 +44,8 @@ import { isRight, } from './internal_utils'; import { DEFAULT_REFRESH_SETTING } from './repository'; -import type { RepositoryEsClient } from './repository_es_client'; -import type { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; +import { RepositoryEsClient } from './repository_es_client'; +import { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; import { deleteLegacyUrlAliases } from './legacy_url_aliases'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts index e2c7107ca380c7..589f4410384e0f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import type { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; // mock duplicated from `@kbn/core/saved-objects-migration-server-mocks` to avoid cyclic dependencies diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts index d6b7e51f78bd15..4d033ab5a7a416 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts @@ -7,7 +7,7 @@ */ import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; -import type { +import { SavedObjectsClientContract, ISavedObjectsRepository, SavedObjectsPointInTimeFinderClient, @@ -48,7 +48,7 @@ const createPointInTimeFinderMock = ({ const createPointInTimeFinderClientMock = (): jest.Mocked => { return { find: jest.fn(), - openPointInTimeForType: jest.fn(), + openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }), closePointInTime: jest.fn(), }; }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts index dc6c06c0c828d1..c3f95442fc685b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; // mock duplicated from `@kbn/core/saved-objects-api-server-mocks` to avoid cyclic dependencies diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts index 38d4e75a0c528d..c0521a0221f76f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, SavedObjectsBulkResolveObject, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts index 50f78f09dd684f..555a9484f542ac 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract, ISavedObjectsRepository, SavedObjectsBaseOptions, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts index b14715db34dddc..03b6372d987809 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts @@ -7,7 +7,7 @@ */ import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; -import type { +import { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts index 168f4c8de6b59c..32be2cbda2d62a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts @@ -7,7 +7,7 @@ */ import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock'; -import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; const create = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts index 523e5003e650ff..5d1e69e1f27940 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts index f4308ee6254c7c..72549e364f99c1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { ISavedObjectsEncryptionExtension, ISavedObjectsSecurityExtension, ISavedObjectsSpacesExtension, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts index 73efc414a634af..47a4d833af0893 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ISavedObjectsClientProvider } from '@kbn/core-saved-objects-api-server-internal'; +import { ISavedObjectsClientProvider } from '@kbn/core-saved-objects-api-server-internal'; const create = (): jest.Mocked => ({ getClient: jest.fn(), diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json index 3fe98195b374a7..4582562d6c9bb4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts index eb80df8d96d440..5d149ea6fa9183 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; /** * Base options used by most of the savedObject APIs. diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts index 7a38a909155ffb..129cc9c605d10e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import type { - SavedObjectReference, - SavedObjectsMigrationVersion, -} from '@kbn/core-saved-objects-common'; +import { SavedObjectReference, SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; /** * Object parameters for the bulk create operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts index 5b390cca73d14c..717a042bd97324 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectError } from '@kbn/core-saved-objects-common'; -import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import { SavedObjectError } from '@kbn/core-saved-objects-common'; +import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Object parameters for the bulk delete operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts index ff59adee8be7f5..dde3c3cafe8c03 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsResolveResponse } from './resolve'; +import { SavedObjectsResolveResponse } from './resolve'; /** * Object parameters for the bulk resolve operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts index 6d10aee397b2f1..3ee341902a87fa 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; -import type { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse } from './update'; +import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse } from './update'; /** * Object parameters for the bulk update operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts index 331b95519a91c0..003ec7c5e74b3c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectError } from '@kbn/core-saved-objects-common'; +import { SavedObjectError } from '@kbn/core-saved-objects-common'; /** * Object parameters for the check conficts operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts index 8996de4474cfec..9e30fc6cfa40ce 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsBaseOptions } from './base'; +import { SavedObjectsBaseOptions } from './base'; /** * Options for the close point-in-time operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts index fcd0d079961bd7..040aaacd020646 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsBaseOptions } from './base'; +import { SavedObjectsBaseOptions } from './base'; /** * An object to collect references for. It must be a multi-namespace type (in other words, the object type must be registered with the diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts index 78a017ed03aba1..ed352957b14ff2 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts @@ -6,11 +6,8 @@ * Side Public License, v 1. */ -import type { - SavedObjectsMigrationVersion, - SavedObjectReference, -} from '@kbn/core-saved-objects-common'; -import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import { SavedObjectsMigrationVersion, SavedObjectReference } from '@kbn/core-saved-objects-common'; +import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Options for the saved objects create operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts index b5bd62b75cbdf3..4faef0c8fc9542 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsFindOptions, SavedObjectsFindResponse } from './find'; +import { SavedObjectsFindOptions, SavedObjectsFindResponse } from './find'; import { ISavedObjectsRepository } from '../saved_objects_repository'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts index 36c5dab7d689ea..b8068c14346cec 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts index 17eda87e3dd70c..200390e0917f3f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsBaseOptions } from './base'; +import { SavedObjectsBaseOptions } from './base'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts index 8e754d26533c34..447c93f40863a7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import type { +import { SortOrder, AggregationsAggregationContainer, Id as EsId, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; type KueryNode = any; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts index 17234d51a6fceb..0108e0ba18ecec 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; -import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Options for the increment counter operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts index 4e5be5fda67f26..bfdc69b8266c4d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsBaseOptions } from './base'; +import { SavedObjectsBaseOptions } from './base'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts index 9e764053392a5e..7e4d2789ea10d5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts index db494d9d8d7a7b..ed4b79c9c1e0db 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectReference, SavedObject } from '@kbn/core-saved-objects-common'; -import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import { SavedObjectReference, SavedObject } from '@kbn/core-saved-objects-common'; +import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Options for the saved objects update operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts index a249ef50f418f0..fcda3992f695b6 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectError } from '@kbn/core-saved-objects-common'; -import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import { SavedObjectError } from '@kbn/core-saved-objects-common'; +import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * An object that should have its spaces updated. diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts index 9fd83a603cb6b8..8c939cc8bc2978 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsClosePointInTimeOptions, diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index 6181cd51a4e695..fc3fbf5437d138 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsClosePointInTimeOptions, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts index 0cf1cc26eda393..54a51b34bca85d 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; -import type { IndexMapping } from '../types'; +import { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; +import { IndexMapping } from '../types'; import { getProperty } from './get_property'; const MAPPINGS = { diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts index ac1070741af711..57bdb5f9cbb886 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts @@ -7,7 +7,7 @@ */ import { toPath } from 'lodash'; -import type { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; +import { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; import { IndexMapping } from '../types'; function getPropertyMappingFromObjectMapping( diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts index fb5a7666b9071a..0d805851560fad 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { SavedObjectsFieldMapping, SavedObjectsMappingProperties, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts index c93abc2064fb65..241bcf77be714f 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { SavedObjectsTypeMappingDefinition, SavedObjectsMappingProperties, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts index bb078135c8bcc2..d0775b690dc6a3 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts @@ -7,8 +7,8 @@ */ import { Observable } from 'rxjs'; -import type { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; -import type { IndexMapping } from '../mappings'; +import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import { IndexMapping } from '../mappings'; /** @internal */ export interface IKibanaMigrator { 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 c6a506d2b4dd4d..8708235a9c37ee 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 @@ -8,7 +8,7 @@ import { valid } from 'semver'; import { schema, TypeOf } from '@kbn/config-schema'; -import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; +import { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; const migrationSchema = schema.object({ batchSize: schema.number({ defaultValue: 1_000 }), diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts index b7c4a0102ffd59..4113c8d7d8b066 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from './saved_objects_type_registry'; const createType = (type: Partial): SavedObjectsType => ({ diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts index 277050b7a6d6d8..f7b63390a8af33 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts @@ -7,7 +7,7 @@ */ import { deepFreeze } from '@kbn/std'; -import type { SavedObjectsType, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectsType, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; /** * Core internal implementation of {@link ISavedObjectTypeRegistry}. diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts index dae97802578cad..c084d3628df9b9 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts @@ -7,7 +7,7 @@ */ import _ from 'lodash'; -import type { SavedObjectsRawDoc, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectsRawDoc, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { SavedObjectsSerializer } from './serializer'; import { encodeVersion } from '../version'; import { LEGACY_URL_ALIAS_TYPE } from '../legacy_alias'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts index f1e713b8741b59..739c87d6a643c0 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts @@ -7,7 +7,7 @@ */ import typeDetect from 'type-detect'; -import type { +import { ISavedObjectTypeRegistry, ISavedObjectsSerializer, SavedObjectsRawDoc, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts index f3005d3c0e77af..5ff9d0b8b3a7db 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; interface GetIndexForTypeOptions { type: string; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts index 42d33c67587ada..16d1c1b1dbb020 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts @@ -7,7 +7,7 @@ */ import { schema } from '@kbn/config-schema'; -import type { +import { SavedObjectsValidationMap, SavedObjectSanitizedDoc, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts index 8b745caae85de5..9e8b1626d6e714 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts @@ -7,7 +7,7 @@ */ import { schema, type Type } from '@kbn/config-schema'; -import type { +import { SavedObjectsValidationSpec, SavedObjectSanitizedDoc, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts index 96bc93be54c1ac..ea11a1c37e3a65 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts @@ -8,7 +8,7 @@ import { schema } from '@kbn/config-schema'; import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; -import type { +import { SavedObjectSanitizedDoc, SavedObjectsValidationMap, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts index 13cff1621512a4..642b88e34b6331 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { Logger } from '@kbn/logging'; -import type { +import { Logger } from '@kbn/logging'; +import { SavedObjectsValidationMap, SavedObjectSanitizedDoc, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts index 8f792f177b5fc6..d8c2b60e42c90d 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import type { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; const createRegistryMock = (): jest.Mocked< ISavedObjectTypeRegistry & Pick diff --git a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts index 6bdac2e20c1f94..2015bed2536490 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server'; +import { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server'; const createSerializerMock = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts index 1fd111186f5512..3115323f2623ba 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts @@ -7,9 +7,9 @@ */ import { pick, throttle, cloneDeep } from 'lodash'; -import type { HttpSetup, HttpFetchOptions } from '@kbn/core-http-browser'; -import type { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import type { +import { HttpSetup, HttpFetchOptions } from '@kbn/core-http-browser'; +import { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import { SavedObjectsBulkResolveResponse as SavedObjectsBulkResolveResponseServer, SavedObjectsBulkDeleteResponse as SavedObjectsBulkDeleteResponseServer, SavedObjectsClientContract as SavedObjectsApi, @@ -17,7 +17,7 @@ import type { SavedObjectsResolveResponse, SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectsClientContract, SavedObjectsCreateOptions, SavedObjectsDeleteOptions, diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts index 111d98bfcc126d..5228798a918453 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { CoreService } from '@kbn/core-base-browser-internal'; -import type { HttpStart } from '@kbn/core-http-browser'; -import type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; +import { CoreService } from '@kbn/core-base-browser-internal'; +import { HttpStart } from '@kbn/core-http-browser'; +import { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; import { SavedObjectsClient } from './saved_objects_client'; export class SavedObjectsService implements CoreService { diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts index 7ffe708fd1e125..5ca8d6addd6f3d 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; import { SimpleSavedObjectImpl as SimpleSavedObject } from './simple_saved_object'; describe('SimpleSavedObjectImpl', () => { diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts index 414ed5bbf18469..4dd24fe3451b47 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts @@ -8,8 +8,8 @@ import { set } from '@kbn/safer-lodash-set'; import { get, has } from 'lodash'; -import type { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract, SimpleSavedObject, } from '@kbn/core-saved-objects-api-browser'; diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts index 2239b94d7e2ebd..c4788fee96cd74 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { PublicMethodsOf } from '@kbn/utility-types'; -import type { SavedObjectsService } from '@kbn/core-saved-objects-browser-internal'; -import type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; +import { PublicMethodsOf } from '@kbn/utility-types'; +import { SavedObjectsService } from '@kbn/core-saved-objects-browser-internal'; +import { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; type SavedObjectsServiceContract = PublicMethodsOf; diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts index 2e3c30ac17d9ce..194f4088393418 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { +import { SavedObjectsClientContract, SimpleSavedObject, } from '@kbn/core-saved-objects-api-browser'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; type T = unknown; diff --git a/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts index c372f3169ed80d..8f1743173bec2f 100644 --- a/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts index daa13393d962eb..2d92f4665a236c 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts @@ -8,8 +8,8 @@ import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; import { httpServerMock } from '@kbn/core-http-server-mocks'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsExportTransform } from '@kbn/core-saved-objects-server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsExportTransform } from '@kbn/core-saved-objects-server'; import { applyExportTransforms } from './apply_export_transforms'; const createObj = ( diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts index 0c871327136f96..4ee6fcb21aa942 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsExportTransform, SavedObjectsExportTransformContext, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts index 04ea3f984b19b6..f644f029c60a59 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts @@ -7,8 +7,8 @@ */ import { httpServerMock } from '@kbn/core-http-server-mocks'; -import type { SavedObject, SavedObjectError } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObject, SavedObjectError } from '@kbn/core-saved-objects-common'; +import { SavedObjectsExportTransform, SavedObjectsExportablePredicate, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts index 6d9adeb8a0d942..3f4414be8e18dc 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { Logger } from '@kbn/logging'; -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { Logger } from '@kbn/logging'; +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsExportablePredicate, ISavedObjectTypeRegistry, SavedObjectsExportTransform, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts index 6ca23c358f9314..eda08fa53f2e9d 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts index 2674b5a62e14a8..065aab92e8ea50 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts @@ -7,7 +7,7 @@ */ import { httpServerMock } from '@kbn/core-http-server-mocks'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts index c3213eb9108bd0..cf091ca215c033 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import type { Readable } from 'stream'; +import { Readable } from 'stream'; import { createListStream } from '@kbn/utils'; -import type { Logger } from '@kbn/logging'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { +import { Logger } from '@kbn/logging'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract, SavedObjectsFindResult, } from '@kbn/core-saved-objects-api-server'; -import type { +import { ISavedObjectsExporter, ISavedObjectTypeRegistry, SavedObjectsExportResultDetails, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts index 27fbb09a370184..b04ae8abf6638c 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts @@ -8,7 +8,7 @@ import { range } from 'lodash'; import { sortObjects } from './sort_objects'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; describe('sortObjects()', () => { test('should return on empty array', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts index 551ba3989e5271..95743d7084ee90 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; const getId = (object: { type: string; id: string }) => `${object.type}:${object.id}`; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts index 6088217bd92018..d1d5091f5ea51e 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; import { byIdAscComparator, getPreservedOrderComparator } from './utils'; const createObj = (id: string): SavedObject => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts index dbace21e98ca63..ffe0bd3b05b948 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; export type SavedObjectComparator = (a: SavedObject, b: SavedObject) => number; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts index c0350c7eac7f82..f41e0fc2181254 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts index 82e5aa4a5d77f1..c982b77e0a4927 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { collectSavedObjects } from './lib/collect_saved_objects'; -import type { checkReferenceOrigins } from './lib/check_reference_origins'; -import type { regenerateIds } from './lib/regenerate_ids'; -import type { validateReferences } from './lib/validate_references'; -import type { checkConflicts } from './lib/check_conflicts'; -import type { checkOriginConflicts } from './lib/check_origin_conflicts'; -import type { createSavedObjects } from './lib/create_saved_objects'; -import type { executeImportHooks } from './lib/execute_import_hooks'; +import { collectSavedObjects } from './lib/collect_saved_objects'; +import { checkReferenceOrigins } from './lib/check_reference_origins'; +import { regenerateIds } from './lib/regenerate_ids'; +import { validateReferences } from './lib/validate_references'; +import { checkConflicts } from './lib/check_conflicts'; +import { checkOriginConflicts } from './lib/check_origin_conflicts'; +import { createSavedObjects } from './lib/create_saved_objects'; +import { executeImportHooks } from './lib/execute_import_hooks'; export const mockCollectSavedObjects = jest.fn() as jest.MockedFunction; jest.mock('./lib/collect_saved_objects', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts index b44020e1774bef..14fba90efe1258 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts @@ -19,13 +19,13 @@ import { import { Readable } from 'stream'; import { v4 as uuidv4 } from 'uuid'; -import type { +import { SavedObject, SavedObjectsImportFailure, SavedObjectsImportWarning, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsType, ISavedObjectTypeRegistry, SavedObjectsImportHook, @@ -33,7 +33,7 @@ import type { import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { importSavedObjectsFromStream, ImportSavedObjectsOptions } from './import_saved_objects'; -import type { ImportStateMap } from './lib'; +import { ImportStateMap } from './lib'; describe('#importSavedObjectsFromStream', () => { beforeEach(() => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts index 86379b4dfde2f1..b328c9f5ea121e 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts @@ -7,12 +7,12 @@ */ import { Readable } from 'stream'; -import type { +import { SavedObjectsImportFailure, SavedObjectsImportResponse, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectTypeRegistry, SavedObjectsImportHook, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts index 8c58af772b8828..46c026c17158ae 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts @@ -7,12 +7,12 @@ */ import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; -import type { +import { SavedObject, SavedObjectReference, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { checkConflicts } from './check_conflicts'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts index f4f6082ef18abd..4b0c1e0dfffa68 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts @@ -7,14 +7,14 @@ */ import { v4 as uuidv4 } from 'uuid'; -import type { +import { SavedObject, SavedObjectsImportFailure, SavedObjectError, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { ImportStateMap } from './types'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ImportStateMap } from './types'; interface CheckConflictsParams { objects: Array>; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts index 8fb5704af9d821..6c1b21ccf3fbb7 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { createOriginQuery } from './utils'; +import { createOriginQuery } from './utils'; export const mockCreateOriginQuery = jest.fn() as jest.MockedFunction; jest.mock('./utils', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts index 225db282b31eef..c198c826092cea 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts @@ -8,18 +8,18 @@ import { mockCreateOriginQuery } from './check_reference_origins.test.mock'; -import type { +import { SavedObjectReference, SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { checkOriginConflicts } from './check_origin_conflicts'; -import type { ImportStateMap } from './types'; +import { ImportStateMap } from './types'; jest.mock('uuid', () => ({ v4: () => 'uuidv4', diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts index 079c5c22191ed0..a6e7907622d667 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts @@ -8,15 +8,15 @@ import pMap from 'p-map'; import { v4 as uuidv4 } from 'uuid'; -import type { +import { SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { getObjectKey } from '@kbn/core-saved-objects-base-server-internal'; -import type { ImportStateMap } from './types'; +import { ImportStateMap } from './types'; import { createOriginQuery } from './utils'; interface CheckOriginConflictsParams { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts index 8fb5704af9d821..6c1b21ccf3fbb7 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { createOriginQuery } from './utils'; +import { createOriginQuery } from './utils'; export const mockCreateOriginQuery = jest.fn() as jest.MockedFunction; jest.mock('./utils', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts index 37a9aa96dd52fe..dc191d8774d230 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts @@ -8,15 +8,15 @@ import { mockCreateOriginQuery } from './check_reference_origins.test.mock'; -import type { +import { SavedObjectsFindResult, SavedObjectsClientContract, } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { checkReferenceOrigins, CheckReferenceOriginsParams } from './check_reference_origins'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import type { ImportStateMap } from './types'; +import { ImportStateMap } from './types'; const MULTI_NS_TYPE = 'multi'; const OTHER_TYPE = 'other'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts index 65a0f9fd432f21..5261fdc29a5de3 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts @@ -7,10 +7,10 @@ */ import pMap from 'p-map'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { getObjectKey, parseObjectKey } from '@kbn/core-saved-objects-base-server-internal'; -import type { ImportStateMap, ImportStateValue } from './types'; +import { ImportStateMap, ImportStateValue } from './types'; import { createOriginQuery } from './utils'; export interface CheckReferenceOriginsParams { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts index e86ff1f70794db..4e359643164423 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts @@ -14,11 +14,11 @@ import { createPromiseFromStreams, } from '@kbn/utils'; -import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; import { SavedObjectsImportError } from '../errors'; import { getNonUniqueEntries } from './get_non_unique_entries'; import { createLimitStream } from './create_limit_stream'; -import type { ImportStateMap } from './types'; +import { ImportStateMap } from './types'; interface CollectSavedObjectsOptions { readStream: Readable; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts index 4b830e80fa6cb7..870d657ff7e211 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; export function createObjectsFilter(retries: SavedObjectsImportRetry[]) { const retryKeys = new Set(retries.map((retry) => `${retry.type}:${retry.id}`)); diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts index 907532bcc8fbbe..790a2a0f1309a9 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts @@ -7,7 +7,7 @@ */ import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; -import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { createSavedObjects } from './create_saved_objects'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts index 31caf8a130cae2..36bf5d06683948 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { CreatedObject } from '@kbn/core-saved-objects-server'; +import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { CreatedObject } from '@kbn/core-saved-objects-server'; import { extractErrors } from './extract_errors'; -import type { ImportStateMap } from './types'; +import { ImportStateMap } from './types'; export interface CreateSavedObjectsParams { objects: Array>; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts index bebe27492d41a8..334bf69690d00a 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsImportHookResult } from '@kbn/core-saved-objects-server'; +import { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; +import { SavedObjectsImportHookResult } from '@kbn/core-saved-objects-server'; import { executeImportHooks } from './execute_import_hooks'; const createObject = (type: string, id: string): SavedObject => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts index d2e3063b1c49f1..ca7239d6a9166e 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; +import { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; +import { SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; export interface ExecuteImportHooksOptions { objects: SavedObject[]; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts index c4d3d0e4c27220..6e0dc2b5b86496 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { CreatedObject } from '@kbn/core-saved-objects-server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { CreatedObject } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { extractErrors } from './extract_errors'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts index 97eca4a5d57beb..7bc2ebf4ab9acf 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; -import type { CreatedObject } from '@kbn/core-saved-objects-server'; +import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import { CreatedObject } from '@kbn/core-saved-objects-server'; export function extractErrors( // TODO: define saved object type diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts index d598326afa1366..44fd21e7273cc5 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; import { getImportStateMapForRetries } from './get_import_state_map_for_retries'; describe('#getImportStateMapForRetries', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts index dd4ed3036fb8c4..7b3c8f422e0fda 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; -import type { ImportStateMap } from './types'; +import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import { ImportStateMap } from './types'; interface GetImportStateMapForRetriesParams { objects: SavedObject[]; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts index f8a8c502af38a3..8b3c8bd60f64db 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; import { regenerateIds } from './regenerate_ids'; jest.mock('uuid', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts index 1d895c8c7dabf1..066c2c2fdc5cee 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts @@ -7,8 +7,8 @@ */ import { v4 as uuidv4 } from 'uuid'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { ImportStateMap } from './types'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { ImportStateMap } from './types'; /** * Takes an array of saved objects and returns an importStateMap of randomly-generated new IDs. diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts index 818b6e512dcc24..e36777c11a5872 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; export function splitOverwrites( savedObjects: Array>, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts index ceced695b28708..3f0f4dc5196f6e 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts @@ -7,7 +7,7 @@ */ import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; -import type { ValidateReferencesParams } from './validate_references'; +import { ValidateReferencesParams } from './validate_references'; import { validateReferences } from './validate_references'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts index 8f271908bf48d5..6b8061309a243a 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { +import { SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsImportError } from '../errors'; -import type { ImportStateMap } from './types'; +import { ImportStateMap } from './types'; const REF_TYPES_TO_VALIDATE = ['index-pattern', 'search']; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts index 984068b59c575e..6828762087777c 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; import { validateRetries } from './validate_retries'; import { SavedObjectsImportError } from '../errors'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts index 745bb9fd423544..3f3711658258dc 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; import { getNonUniqueEntries } from './get_non_unique_entries'; import { SavedObjectsImportError } from '../errors'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts index 21eadaaf2231a7..6a75540b909622 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts @@ -6,18 +6,18 @@ * Side Public License, v 1. */ -import type { checkReferenceOrigins } from './lib/check_reference_origins'; -import type { validateRetries } from './lib/validate_retries'; -import type { createObjectsFilter } from './lib/create_objects_filter'; -import type { collectSavedObjects } from './lib/collect_saved_objects'; -import type { regenerateIds } from './lib/regenerate_ids'; -import type { validateReferences } from './lib/validate_references'; -import type { checkConflicts } from './lib/check_conflicts'; -import type { checkOriginConflicts } from './lib/check_origin_conflicts'; -import type { getImportStateMapForRetries } from './lib/get_import_state_map_for_retries'; -import type { splitOverwrites } from './lib/split_overwrites'; -import type { createSavedObjects } from './lib/create_saved_objects'; -import type { executeImportHooks } from './lib/execute_import_hooks'; +import { checkReferenceOrigins } from './lib/check_reference_origins'; +import { validateRetries } from './lib/validate_retries'; +import { createObjectsFilter } from './lib/create_objects_filter'; +import { collectSavedObjects } from './lib/collect_saved_objects'; +import { regenerateIds } from './lib/regenerate_ids'; +import { validateReferences } from './lib/validate_references'; +import { checkConflicts } from './lib/check_conflicts'; +import { checkOriginConflicts } from './lib/check_origin_conflicts'; +import { getImportStateMapForRetries } from './lib/get_import_state_map_for_retries'; +import { splitOverwrites } from './lib/split_overwrites'; +import { createSavedObjects } from './lib/create_saved_objects'; +import { executeImportHooks } from './lib/execute_import_hooks'; export const mockCheckReferenceOrigins = jest.fn() as jest.MockedFunction< typeof checkReferenceOrigins diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts index ed93301a458133..e6ded82be2fc05 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts @@ -23,15 +23,15 @@ import { import { Readable } from 'stream'; import { v4 as uuidv4 } from 'uuid'; -import type { +import { SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, SavedObjectReference, SavedObjectsImportWarning, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsType, ISavedObjectTypeRegistry, SavedObjectsImportHook, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts index 9d96126265fd1a..56879ad7ffee92 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts @@ -7,15 +7,15 @@ */ import { Readable } from 'stream'; -import type { +import { SavedObject, SavedObjectsImportRetry, SavedObjectsImportFailure, SavedObjectsImportResponse, SavedObjectsImportSuccess, } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectTypeRegistry, SavedObjectsImportHook, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts index 27251b0af2e2a9..ab2628c26dbc0e 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { SavedObjectsImportResponse } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { SavedObjectsImportResponse } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectTypeRegistry, ISavedObjectsImporter, SavedObjectsImportOptions, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json index 3fe98195b374a7..4582562d6c9bb4 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts index 520ebce27323a8..91217b1b765bfa 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ISavedObjectsExporter } from '@kbn/core-saved-objects-server'; +import { ISavedObjectsExporter } from '@kbn/core-saved-objects-server'; const createExporterMock = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts index 22df181d28db57..8367822ef90225 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ISavedObjectsImporter } from '@kbn/core-saved-objects-server'; +import { ISavedObjectsImporter } from '@kbn/core-saved-objects-server'; const createImporterMock = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts index 39194625e4531f..f24d2c04a0b5b3 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts @@ -8,17 +8,17 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { errors as esErrors } from '@elastic/elasticsearch'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { isWriteBlockException, isIndexNotFoundException } from './es_errors'; import { WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE } from './constants'; -import type { TargetIndexHadWriteBlock, RequestEntityTooLargeException, IndexNotFound } from '.'; +import { TargetIndexHadWriteBlock, RequestEntityTooLargeException, IndexNotFound } from '.'; /** * Given a document and index, creates a valid body for the Bulk API. diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts index d0cf8f85fc4970..0b90e7f00a9ea6 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { withTimeout } from '@kbn/std'; import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectTypeExcludeFromUpgradeFilterHook } from '@kbn/core-saved-objects-server'; -import type { RetryableEsClientError } from '.'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectTypeExcludeFromUpgradeFilterHook } from '@kbn/core-saved-objects-server'; +import { RetryableEsClientError } from '.'; import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; export interface CalculateExcludeFiltersParams { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts index 465d4c8aa7ee84..02282768cf8b4a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts @@ -9,7 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { checkForUnknownDocs } from './check_for_unknown_docs'; import { createAggregateTypesSearchResponse } from './check_for_unknown_docs.mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts index 74dc39bc6fcd41..4278f59a66fa33 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts @@ -9,14 +9,14 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { flatten } from 'lodash'; -import type { +import { AggregationsMultiBucketAggregateBase, Indices, QueryDslQueryContainer, SearchRequest, } from '@elastic/elasticsearch/lib/api/types'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, type RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts index 0e07a68c1ec39c..787260137fcb74 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts @@ -10,12 +10,12 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { pipe } from 'fp-ts/lib/pipeable'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, } from './catch_retryable_es_client_errors'; -import type { IndexNotFound, AcknowledgeResponse } from '.'; +import { IndexNotFound, AcknowledgeResponse } from '.'; import { type IndexNotGreenTimeout, waitForIndexStatus } from './wait_for_index_status'; import { DEFAULT_TIMEOUT, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts index a3ea50edccae67..ff0fd172594d3a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts index 9353d28e9ffd80..36e532352d5a83 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts @@ -10,8 +10,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { pipe } from 'fp-ts/lib/pipeable'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { AcknowledgeResponse } from '.'; import { catchRetryableEsClientErrors, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts index 2bf432de032a67..957dd92ff26d71 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; export const isWriteBlockException = (errorCause?: estypes.ErrorCause): boolean => { return ( diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts index 0eb43380a6990e..13f629f5c0a6cc 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts @@ -8,8 +8,8 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Either from 'fp-ts/lib/Either'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index 2b6d501f787b09..500294003a9628 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -66,11 +66,11 @@ export { refreshIndex } from './refresh_index'; export type { ReindexResponse, ReindexParams } from './reindex'; export { reindex } from './reindex'; -import type { IncompatibleMappingException } from './wait_for_reindex_task'; +import { IncompatibleMappingException } from './wait_for_reindex_task'; export { waitForReindexTask } from './wait_for_reindex_task'; -import type { AliasNotFound, RemoveIndexNotAConcreteIndex } from './update_aliases'; +import { AliasNotFound, RemoveIndexNotAConcreteIndex } from './update_aliases'; export type { AliasAction, UpdateAliasesParams } from './update_aliases'; export { updateAliases } from './update_aliases'; @@ -84,8 +84,8 @@ export type { } from './update_and_pickup_mappings'; export { updateAndPickupMappings } from './update_and_pickup_mappings'; -import type { UnknownDocsFound } from './check_for_unknown_docs'; -import type { IncompatibleClusterRoutingAllocation } from './initialize_action'; +import { UnknownDocsFound } from './check_for_unknown_docs'; +import { IncompatibleClusterRoutingAllocation } from './initialize_action'; import { ClusterShardLimitExceeded } from './create_index'; export type { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts index a1b5e013600187..37fc25416ae645 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts @@ -9,7 +9,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Either from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts index 3966198393c219..013c0879b7abf8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts index 9cb37facc0b5a2..270cb6ad768de0 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts index 19e9ed2072c2bb..16aed0c3c24c6c 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts index 7eb5c5d560d4cc..479c8617253b14 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts @@ -7,7 +7,7 @@ */ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts index 594822f7247602..279ff51ccb9905 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, type RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts index 35a4dff315277e..6807438860a694 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts index ac5508a1400384..5a77aaaae594bd 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts index a84f976ceb6434..f46e170fc3bfbb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts @@ -9,7 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts index 52e79e6ac88c20..b142aab0094032 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts @@ -7,8 +7,8 @@ */ import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import type { TransformRawDocs } from '../types'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { TransformRawDocs } from '../types'; import { DocumentsTransformFailed, DocumentsTransformSuccess } from '../core/migrate_raw_docs'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts index c896dbf27258f2..2b41e2cb4d4729 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts @@ -9,7 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts index 36a00f1096b1da..8bc99d2d4d1252 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { pipe } from 'fp-ts/lib/pipeable'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts index 7ee63e75838511..1568d94b8c379a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts index 3eb7b0da718c2b..dea8b8b81bc2d7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts @@ -10,7 +10,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; import { flow } from 'fp-ts/lib/function'; import { RetryableEsClientError } from './catch_retryable_es_client_errors'; -import type { IndexNotFound, TargetIndexHadWriteBlock } from '.'; +import { IndexNotFound, TargetIndexHadWriteBlock } from '.'; import { waitForTask, WaitForTaskCompletionTimeout } from './wait_for_task'; import { isWriteBlockException, isIncompatibleMappingException } from './es_errors'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts index a5762ff10a122e..6410960550b8b5 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts @@ -5,12 +5,12 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts index 7f1542ffc6008a..1ce1b422331491 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { IndexMapping, SavedObjectsTypeMappingDefinitions, } from '@kbn/core-saved-objects-base-server-internal'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts index f14de6bc72ee02..5f18184a09c35f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts @@ -12,8 +12,8 @@ import crypto from 'crypto'; import { cloneDeep, mapValues } from 'lodash'; -import type { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; -import type { +import { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; +import { IndexMapping, SavedObjectsTypeMappingDefinitions, } from '@kbn/core-saved-objects-base-server-internal'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts index ec82b0ac2c92f8..ac7b612dedd4fd 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { createIndexMap } from './build_index_map'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts index 225b3bb422925a..89e23e431185a9 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import type { SavedObjectsTypeMappingDefinitions } from '@kbn/core-saved-objects-base-server-internal'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectsTypeMappingDefinitions } from '@kbn/core-saved-objects-base-server-internal'; export interface CreateIndexMapOptions { kibanaIndexName: string; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts index 8b1eccbe09d783..6edf15340a95c7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; /** * Merges the active mappings and the source mappings while disabling the diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts index a0dd1cfddc3a48..e5274e46ec23aa 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts @@ -9,7 +9,7 @@ import { mockGetConvertedObjectId } from './document_migrator.test.mock'; import { set } from '@kbn/safer-lodash-set'; import _ from 'lodash'; -import type { SavedObjectUnsanitizedDoc, SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SavedObjectUnsanitizedDoc, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry, LEGACY_URL_ALIAS_TYPE, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts index 1040699a99e9df..bcf1e0a8d2c9c3 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts @@ -46,12 +46,12 @@ import uuidv5 from 'uuid/v5'; import { set } from '@kbn/safer-lodash-set'; import _ from 'lodash'; import Semver from 'semver'; -import type { Logger } from '@kbn/logging'; -import type { +import { Logger } from '@kbn/logging'; +import { SavedObjectsMigrationVersion, SavedObjectsNamespaceType, } from '@kbn/core-saved-objects-common'; -import type { +import { SavedObjectUnsanitizedDoc, SavedObjectsType, ISavedObjectTypeRegistry, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts index d1ca65f1dd1034..5f81cc65627496 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts @@ -11,7 +11,7 @@ */ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Either from 'fp-ts/lib/Either'; -import type { +import { SavedObjectSanitizedDoc, SavedObjectsRawDoc, SavedObjectUnsanitizedDoc, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts index c2ae13b70cbddd..1b5f232ce88ccb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { Logger, LogMeta } from '@kbn/logging'; -import type { SavedObjectsMigrationLogger } from '@kbn/core-saved-objects-server'; +import { Logger, LogMeta } from '@kbn/logging'; +import { SavedObjectsMigrationLogger } from '@kbn/core-saved-objects-server'; /* * This file provides a helper class for ensuring that all logging diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts index 7dbcf2c270ba47..67f1f83644308c 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; /** * Types that are no longer registered and need to be removed diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts index d1c19aa7d212ef..d42a93d3eea0e8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts @@ -8,7 +8,7 @@ import { ByteSizeValue } from '@kbn/config-schema'; import * as Option from 'fp-ts/Option'; -import type { DocLinksServiceSetup } from '@kbn/core-doc-links-server'; +import { DocLinksServiceSetup } from '@kbn/core-doc-links-server'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { type SavedObjectsMigrationConfigType, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts index 1843227934bcd0..00a008a3897073 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts @@ -7,15 +7,15 @@ */ import * as Option from 'fp-ts/Option'; -import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import type { Logger } from '@kbn/logging'; -import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import type { +import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { Logger } from '@kbn/logging'; +import { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { IndexMapping, SavedObjectsMigrationConfigType, } from '@kbn/core-saved-objects-base-server-internal'; -import type { InitState } from './state'; +import { InitState } from './state'; import { excludeUnusedTypesQuery } from './core'; /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index 5d1cb32eca6d22..9e6420177bc7ec 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -7,11 +7,11 @@ */ import { take } from 'rxjs/operators'; -import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { DocumentMigrator } from './core/document_migrator'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts index 1d8d766d8ac88a..4bb2a5462f0cd2 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts @@ -13,11 +13,11 @@ import { BehaviorSubject } from 'rxjs'; import Semver from 'semver'; -import type { Logger } from '@kbn/logging'; -import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import type { +import { Logger } from '@kbn/logging'; +import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SavedObjectUnsanitizedDoc, SavedObjectsRawDoc, ISavedObjectTypeRegistry, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts index f21651d82f8f9f..d0828138c36a7b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts @@ -8,13 +8,13 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import * as Option from 'fp-ts/lib/Option'; -import type { Logger, LogMeta } from '@kbn/logging'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { Logger, LogMeta } from '@kbn/logging'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { getErrorMessage, getRequestDebugMeta, } from '@kbn/core-elasticsearch-client-server-internal'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { Model, Next, stateActionMachine } from './state_action_machine'; import { cleanup } from './migrations_state_machine_cleanup'; import { ReindexSourceToTempTransform, ReindexSourceToTempIndexBulk, State } from './state'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts index 8b1f93c327857b..98eb4b35716182 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import * as Actions from './actions'; -import type { State } from './state'; +import { State } from './state'; export async function cleanup(client: ElasticsearchClient, state?: State) { if (!state) return; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts index ec9afc31f90d1b..551c5336f78bfe 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import * as Either from 'fp-ts/lib/Either'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { createBatches } from './create_batches'; describe('createBatches', () => { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts index a591505f542c72..1f81fa98902675 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts @@ -7,7 +7,7 @@ */ import * as Either from 'fp-ts/lib/Either'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { createBulkOperationBody } from '../actions/bulk_overwrite_transformed_documents'; /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts index 9d00cb41cc07ca..9e26fd89390e29 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { TransformErrorObjects } from '../core'; -import type { DocumentIdAndType } from '../actions'; +import { TransformErrorObjects } from '../core'; +import { DocumentIdAndType } from '../actions'; /** * Constructs migration failure message strings from corrupt document ids and document transformation errors diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index 5f84dc01af008b..034c56e35e5888 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -7,14 +7,14 @@ */ import { gt, valid } from 'semver'; -import type { +import { QueryDslBoolQuery, QueryDslQueryContainer, } from '@elastic/elasticsearch/lib/api/types'; import * as Either from 'fp-ts/lib/Either'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import type { State } from '../state'; -import type { FetchIndexResponse } from '../actions'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { State } from '../state'; +import { FetchIndexResponse } from '../actions'; /** * A helper function/type for ensuring that all control state's are handled. diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index 67b7e40dc5affc..aa9d37c1b24edb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -8,8 +8,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import type { +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { FatalState, State, LegacySetWriteBlockState, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index 8eca6d12c4f0ad..0ef73de1674b19 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -10,8 +10,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; import { type AliasAction, isTypeof } from '../actions'; -import type { AllActionStates, State } from '../state'; -import type { ResponseType } from '../next'; +import { AllActionStates, State } from '../state'; +import { ResponseType } from '../next'; import { disableUnknownTypeMappingFields } from '../core'; import { createInitialProgress, @@ -27,7 +27,7 @@ import { extractDiscardedUnknownDocs, extractDiscardedCorruptDocs, } from './extract_errors'; -import type { ExcludeRetryableEsError } from './types'; +import { ExcludeRetryableEsError } from './types'; import { addExcludedTypesToBoolQuery, addMustClausesToBoolQuery, @@ -40,7 +40,7 @@ import { throwBadResponse, } from './helpers'; import { createBatches } from './create_batches'; -import type { MigrationLog } from '../types'; +import { MigrationLog } from '../types'; export const FATAL_REASON_REQUEST_ENTITY_TOO_LARGE = `While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option.`; const CLUSTER_SHARD_LIMIT_EXCEEDED_REASON = `[cluster_shard_limit_exceeded] Upgrading Kibana requires adding a small number of new shards. Ensure that Kibana is able to add 10 more shards by increasing the cluster.max_shards_per_node setting, or removing indices to clear up resources.`; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts index 2086774dd2fb41..6de961a6e1b7a0 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { MigrationLog } from '../types'; +import { MigrationLog } from '../types'; import { createInitialProgress, incrementProcessedProgress, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts index ef66283fcf6823..5ec21ff06092ac 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { MigrationLog, Progress } from '../types'; +import { MigrationLog, Progress } from '../types'; /** * Returns an initial state of the progress object (everything undefined) diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts index 548c6b8c43b51b..59ed9756b23a53 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { next } from './next'; import { State } from './state'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index 9ac29a3a849ba7..a55f828931faaf 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { AllActionStates, ReindexSourceToTempOpenPit, ReindexSourceToTempRead, @@ -41,7 +41,7 @@ import type { CheckUnknownDocumentsState, CalculateExcludeFiltersState, } from './state'; -import type { TransformRawDocs } from './types'; +import { TransformRawDocs } from './types'; import * as Actions from './actions'; type ActionMap = ReturnType; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts index 47fe92ad82c54f..c88e433ee92238 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts @@ -6,17 +6,17 @@ * Side Public License, v 1. */ -import type { Logger } from '@kbn/logging'; -import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import type { +import { Logger } from '@kbn/logging'; +import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { IndexMapping, SavedObjectsMigrationConfigType, MigrationResult, } from '@kbn/core-saved-objects-base-server-internal'; -import type { TransformRawDocs } from './types'; +import { TransformRawDocs } from './types'; import { next } from './next'; import { model } from './model'; import { createInitialState } from './initial_state'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index eb8d2e2fbb05b2..a12fd7314e71aa 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -7,17 +7,17 @@ */ import * as Option from 'fp-ts/lib/Option'; -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import type { DocLinks } from '@kbn/doc-links'; -import type { +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import { DocLinks } from '@kbn/doc-links'; +import { SavedObjectsRawDoc, SavedObjectTypeExcludeFromUpgradeFilterHook, } from '@kbn/core-saved-objects-server'; -import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import type { ControlState } from './state_action_machine'; -import type { AliasAction } from './actions'; -import type { TransformErrorObjects } from './core'; -import type { MigrationLog, Progress } from './types'; +import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import { ControlState } from './state_action_machine'; +import { AliasAction } from './actions'; +import { TransformErrorObjects } from './core'; +import { MigrationLog, Progress } from './types'; export interface BaseState extends ControlState { /** The first part of the index name such as `.kibana` or `.kibana_task_manager` */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts index cf7c74c305f2b8..7d12eb2d5f5c7b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts @@ -7,7 +7,7 @@ */ import * as TaskEither from 'fp-ts/lib/TaskEither'; -import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { DocumentsTransformFailed, DocumentsTransformSuccess } from './core'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts index e7df2a0363a26a..f7fe210f0ef2d3 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts @@ -7,8 +7,8 @@ */ import { BehaviorSubject } from 'rxjs'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import type { +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { IKibanaMigrator, KibanaMigratorStatus, } from '@kbn/core-saved-objects-base-server-internal'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts index 6cb3cc3d1fd867..5e4e9eec851581 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { SavedObjectMigrationContext, SavedObjectsMigrationLogger, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts index 3a363ea5a4c200..91d33df14471e6 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; -import type { RegisterDeprecationsConfig } from '@kbn/core-deprecations-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import { RegisterDeprecationsConfig } from '@kbn/core-deprecations-server'; import { getUnknownTypesDeprecations } from './unknown_object_types'; interface GetDeprecationProviderOptions { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts index 2199a7fb32b5e9..c61c7b98260f1f 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts @@ -8,11 +8,11 @@ import { getIndexForTypeMock } from './unknown_object_types.test.mocks'; -import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { deleteUnknownTypeObjects, getUnknownTypesDeprecations } from './unknown_object_types'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { SavedObjectsType } from '@kbn/core-saved-objects-server'; const createAggregateTypesSearchResponse = ( typesIds: Record = {} diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts index 2149fd6cfaefdf..f40e8ccdec52f3 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts @@ -7,9 +7,9 @@ */ import { i18n } from '@kbn/i18n'; -import type { DeprecationsDetails } from '@kbn/core-deprecations-common'; -import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; -import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import { DeprecationsDetails } from '@kbn/core-deprecations-common'; +import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { getIndexForType } from '@kbn/core-saved-objects-base-server-internal'; import { getAggregatedTypesDocuments, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts index a004799a46029f..5302e630caa7e4 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server'; -import type { ElasticsearchRequestHandlerContext } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsRequestHandlerContext } from '@kbn/core-saved-objects-server'; +import { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server'; +import { ElasticsearchRequestHandlerContext } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsRequestHandlerContext } from '@kbn/core-saved-objects-server'; /** * Request handler context used by core's savedObjects routes. diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts index db000cffc9e2a8..73bf87307d02c0 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { +import { DeprecationsServiceSetup, DeprecationRegistryProvider, } from '@kbn/core-deprecations-server'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; export const createDeprecationsSetupMock = () => { const setupContract: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts index 5d31cb1e030771..43bc2ba2b6be36 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; +import { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry, LEGACY_URL_ALIAS_TYPE, 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 4192e800e51d23..35f8f8ad2423cf 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 f435eadebd0669..451a219be07592 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 ec8d7a1c842056..33d60c053b4e68 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 8947719bd2ab57..8e7b37e6622de3 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 dbe60203afddfb..d6b38777c6f344 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 2ef810ebebed73..fd0f6c6040c0d6 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 bfef6deb885efc..f4d0f8b9966afa 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts index 5d369949283635..040d7c433eb162 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts @@ -7,7 +7,7 @@ */ import { catchAndReturnBoomErrors } from '../utils'; -import type { InternalSavedObjectRouter } from '../../internal_types'; +import { InternalSavedObjectRouter } from '../../internal_types'; import { deleteUnknownTypeObjects } from '../../deprecations'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts index 1d8a55b9e3fdcd..e478fb39d6656c 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts @@ -10,15 +10,15 @@ import { schema } from '@kbn/config-schema'; import stringify from 'json-stable-stringify'; import { createPromiseFromStreams, createMapStream, createConcatStream } from '@kbn/utils'; -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObjectsExportByTypeOptions, SavedObjectsExportByObjectOptions, } from '@kbn/core-saved-objects-server'; -import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsExportError } from '@kbn/core-saved-objects-import-export-server-internal'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { validateTypes, validateObjects, catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 983b31caf7a2b0..df9fab83ee0c0f 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 dbe2c1a4d9d8d6..89cd9614f1f024 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts index 8e8722ae588c07..9a1b6f282a6784 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts @@ -9,10 +9,10 @@ import { Readable } from 'stream'; import { extname } from 'path'; import { schema } from '@kbn/config-schema'; -import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsImportError } from '@kbn/core-saved-objects-import-export-server-internal'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; interface RouteDependencies { 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 89d5b41dd88856..518e0467162b03 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 @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { Logger } from '@kbn/logging'; -import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; -import type { +import { Logger } from '@kbn/logging'; +import { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import { SavedObjectConfig, IKibanaMigrator, } from '@kbn/core-saved-objects-base-server-internal'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectsRequestHandlerContext } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectsRequestHandlerContext } from '../internal_types'; import { registerGetRoute } from './get'; import { registerResolveRoute } from './resolve'; import { registerCreateRoute } from './create'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts index 3ccbee914e8b16..ac86f8c7f3b638 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts @@ -8,9 +8,9 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; -import type { Logger } from '@kbn/logging'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../../internal_types'; +import { Logger } from '@kbn/logging'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../../internal_types'; import { exportDashboards } from './lib'; export const registerLegacyExportRoute = ( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts index 44ee99aa2c9702..2ace325352d8e8 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts @@ -7,10 +7,10 @@ */ import { schema } from '@kbn/config-schema'; -import type { Logger } from '@kbn/logging'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../../internal_types'; +import { Logger } from '@kbn/logging'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../../internal_types'; import { importDashboards } from './lib'; export const registerLegacyImportRoute = ( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts index 4470983fafc620..d06ed05d0d4b51 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject, SavedObjectAttributes } from '@kbn/core-saved-objects-common'; +import { SavedObject, SavedObjectAttributes } from '@kbn/core-saved-objects-common'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { collectReferencesDeep } from './collect_references_deep'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts index 0c193f618624d5..5cf6cb06715bea 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; const MAX_BULK_GET_SIZE = 10000; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts index 8b069ee31f91a1..f07c8c57e0c5f8 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { collectReferencesDeep } from './collect_references_deep'; export async function exportDashboards( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts index 87452664ac5178..9b8986198baea7 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { importDashboards } from './import_dashboards'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts index 51a63f23322bf0..80ce8ad308add0 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; export async function importDashboards( savedObjectsClient: SavedObjectsClientContract, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts index 2222b17c4e6f99..29e2b9549f8973 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; export const registerMigrateRoute = ( 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 a1804c1fa31767..6bd635ee194dd5 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts index 26380bd2aea84d..656974dc00a2ab 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts @@ -10,10 +10,10 @@ import { extname } from 'path'; import { Readable } from 'stream'; import { chain } from 'lodash'; import { schema } from '@kbn/config-schema'; -import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsImportError } from '@kbn/core-saved-objects-import-export-server-internal'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; interface RouteDependencies { 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 463b5465b53a01..e7b2cd3b671d55 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 @@ -7,9 +7,9 @@ */ import { schema } from '@kbn/config-schema'; -import type { SavedObjectsUpdateOptions } from '@kbn/core-saved-objects-api-server'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { InternalSavedObjectRouter } from '../internal_types'; +import { SavedObjectsUpdateOptions } from '@kbn/core-saved-objects-api-server'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts index ddeff043f2bbab..ffde86dbff7fb3 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts @@ -11,7 +11,7 @@ import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; import { catchAndReturnBoomErrors } from './utils'; import Boom from '@hapi/boom'; -import type { +import { KibanaRequest, RequestHandler, RequestHandlerContextBase, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts index 463d59baf62072..faf4d724cbd1d7 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts @@ -16,9 +16,9 @@ import { createConcatStream, } from '@kbn/utils'; import Boom from '@hapi/boom'; -import type { RequestHandlerWrapper } from '@kbn/core-http-server'; -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsExportResultDetails } from '@kbn/core-saved-objects-server'; +import { RequestHandlerWrapper } from '@kbn/core-http-server'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsExportResultDetails } from '@kbn/core-saved-objects-server'; export async function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { const savedObjects = await createPromiseFromStreams([ diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts index 495537ab32f63b..74c55cb23290fa 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsRequestHandlerContext, ISavedObjectTypeRegistry, SavedObjectsClientProviderOptions, } from '@kbn/core-saved-objects-server'; -import type { InternalSavedObjectsServiceStart } from './saved_objects_service'; +import { InternalSavedObjectsServiceStart } from './saved_objects_service'; /** * The {@link SavedObjectsRequestHandlerContext} implementation. diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 0f0d8a87363721..264fd8fb5dbc8c 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -25,9 +25,9 @@ import { getEnvOptions } from '@kbn/config-mocks'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock, httpServerMock } from '@kbn/core-http-server-mocks'; -import type { SavedObjectsClientFactoryProvider } from '@kbn/core-saved-objects-server'; +import { SavedObjectsClientFactoryProvider } from '@kbn/core-saved-objects-server'; import { configServiceMock } from '@kbn/config-mocks'; -import type { NodesVersionCompatibility } from '@kbn/core-elasticsearch-server-internal'; +import { NodesVersionCompatibility } from '@kbn/core-elasticsearch-server-internal'; import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-internal'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; 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 a102ac01595f1c..be62e7349d8d4c 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 @@ -8,18 +8,18 @@ import { Subject, Observable, firstValueFrom } from 'rxjs'; import { filter, take, switchMap } from 'rxjs/operators'; -import type { Logger } from '@kbn/logging'; -import type { ServiceStatus } from '@kbn/core-status-common'; -import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; -import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { +import { Logger } from '@kbn/logging'; +import { ServiceStatus } from '@kbn/core-status-common'; +import { CoreContext, CoreService } from '@kbn/core-base-server-internal'; +import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import { KibanaRequest } from '@kbn/core-http-server'; +import { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { InternalElasticsearchServiceSetup, InternalElasticsearchServiceStart, } from '@kbn/core-elasticsearch-server-internal'; -import type { +import { SavedObjectsServiceSetup, SavedObjectsServiceStart, SavedObjectsRepositoryFactory, @@ -49,8 +49,8 @@ import { SavedObjectsExporter, SavedObjectsImporter, } from '@kbn/core-saved-objects-import-export-server-internal'; -import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import type { DeprecationRegistryProvider } from '@kbn/core-deprecations-server'; +import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import { DeprecationRegistryProvider } from '@kbn/core-deprecations-server'; import { registerRoutes } from './routes'; import { calculateStatus$ } from './status'; import { registerCoreObjectTypes } from './object_types'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts index ab00eeab788885..3aa81ed1a1ccf7 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts @@ -9,8 +9,8 @@ import { Observable, combineLatest } from 'rxjs'; import { startWith, map } from 'rxjs/operators'; import { type ServiceStatus, ServiceStatusLevels } from '@kbn/core-status-common'; -import type { SavedObjectStatusMeta } from '@kbn/core-saved-objects-server'; -import type { KibanaMigratorStatus } from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectStatusMeta } from '@kbn/core-saved-objects-server'; +import { KibanaMigratorStatus } from '@kbn/core-saved-objects-base-server-internal'; export const calculateStatus$ = ( rawMigratorStatus$: Observable, diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts index 2de6d7afcb6a1b..d48f82123666f8 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts @@ -7,14 +7,14 @@ */ import { BehaviorSubject } from 'rxjs'; -import type { PublicMethodsOf } from '@kbn/utility-types'; +import { PublicMethodsOf } from '@kbn/utility-types'; import { ServiceStatusLevels } from '@kbn/core-status-common'; -import type { +import { SavedObjectsServiceSetup, SavedObjectsServiceStart, ISavedObjectTypeRegistry, } from '@kbn/core-saved-objects-server'; -import type { +import { SavedObjectsService, InternalSavedObjectsServiceSetup, InternalSavedObjectsServiceStart, diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json index 3fe98195b374a7..4582562d6c9bb4 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts index 7a3547c598c34f..5b9bc1514601fc 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts @@ -6,16 +6,16 @@ * Side Public License, v 1. */ -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectsEncryptionExtension } from './encryption'; -import type { SavedObjectsExtensions } from './extensions'; -import type { ISavedObjectsSecurityExtension } from './security'; -import type { ISavedObjectsSpacesExtension } from './spaces'; -import type { ISavedObjectTypeRegistry } from './type_registry'; +import { ISavedObjectsEncryptionExtension } from './encryption'; +import { SavedObjectsExtensions } from './extensions'; +import { ISavedObjectsSecurityExtension } from './security'; +import { ISavedObjectsSpacesExtension } from './spaces'; +import { ISavedObjectTypeRegistry } from './type_registry'; /** * Describes the factory used to create instances of the Saved Objects Client. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index a181865271f1f6..38c19428997df8 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -6,23 +6,23 @@ * Side Public License, v 1. */ -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectsSerializer } from './serialization'; -import type { +import { ISavedObjectsSerializer } from './serialization'; +import { SavedObjectsClientFactoryProvider, SavedObjectsClientProviderOptions, SavedObjectsEncryptionExtensionFactory, SavedObjectsSecurityExtensionFactory, SavedObjectsSpacesExtensionFactory, } from './client_factory'; -import type { SavedObjectsType } from './saved_objects_type'; -import type { ISavedObjectTypeRegistry } from './type_registry'; -import type { ISavedObjectsExporter } from './export'; -import type { ISavedObjectsImporter } from './import'; +import { SavedObjectsType } from './saved_objects_type'; +import { ISavedObjectTypeRegistry } from './type_registry'; +import { ISavedObjectsExporter } from './export'; +import { ISavedObjectsImporter } from './import'; import { SavedObjectsExtensions } from './extensions'; /** diff --git a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts index 124406fc659bb9..2a0f3609f17308 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; /** * The EncryptedObjectDescriptor interface contains settings for describing diff --git a/packages/core/saved-objects/core-saved-objects-server/src/export.ts b/packages/core/saved-objects/core-saved-objects-server/src/export.ts index 67afd0b011a1dd..9b0c1c65f29382 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/export.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/export.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import type { Readable } from 'stream'; -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-server'; +import { Readable } from 'stream'; +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-server'; /** * Utility class used to export savedObjects. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts index a51184b61a113a..b11bc0e3a7474d 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { ISavedObjectsEncryptionExtension } from './encryption'; -import type { ISavedObjectsSecurityExtension } from './security'; -import type { ISavedObjectsSpacesExtension } from './spaces'; +import { ISavedObjectsEncryptionExtension } from './encryption'; +import { ISavedObjectsSecurityExtension } from './security'; +import { ISavedObjectsSpacesExtension } from './spaces'; /** * The SavedObjectsExtensions interface contains the intefaces for three diff --git a/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts b/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts index 80ba357009530b..c38c97c6958ca0 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { PropertyName as EsPropertyName, MappingProperty as EsMappingProperty, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/migration.ts b/packages/core/saved-objects/core-saved-objects-server/src/migration.ts index 4634edef3d2bf0..fa2f277ec52b34 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/migration.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/migration.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { LogMeta } from '@kbn/logging'; -import type { SavedObjectUnsanitizedDoc } from './serialization'; +import { LogMeta } from '@kbn/logging'; +import { SavedObjectUnsanitizedDoc } from './serialization'; /** * A migration function for a {@link SavedObjectsType | saved object type} diff --git a/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts b/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts index b5d1c26ac4a93e..6c2960f891de40 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectsExporter } from './export'; -import type { ISavedObjectsImporter } from './import'; -import type { ISavedObjectTypeRegistry } from './type_registry'; -import type { SavedObjectsClientProviderOptions } from './client_factory'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { ISavedObjectsExporter } from './export'; +import { ISavedObjectsImporter } from './import'; +import { ISavedObjectTypeRegistry } from './type_registry'; +import { SavedObjectsClientProviderOptions } from './client_factory'; /** * Core's `savedObjects` request handler context. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts index 7a67d70cbf4d4c..bea39e65fd77d2 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsExportTransform } from './export'; -import type { SavedObjectsImportHook } from './import'; +import { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObjectsExportTransform } from './export'; +import { SavedObjectsImportHook } from './import'; /** * Configuration options for the {@link SavedObjectsType | type}'s management section. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts index b26be6f8bfb1a2..e849bbaa4cad87 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import type { MaybePromise } from '@kbn/utility-types'; -import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import type { SavedObjectsNamespaceType } from '@kbn/core-saved-objects-common'; -import type { SavedObjectsTypeManagementDefinition } from './saved_objects_management'; -import type { SavedObjectsValidationMap } from './validation'; -import type { SavedObjectMigrationMap } from './migration'; -import type { SavedObjectsTypeMappingDefinition } from './mapping_definition'; +import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import { MaybePromise } from '@kbn/utility-types'; +import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import { SavedObjectsNamespaceType } from '@kbn/core-saved-objects-common'; +import { SavedObjectsTypeManagementDefinition } from './saved_objects_management'; +import { SavedObjectsValidationMap } from './validation'; +import { SavedObjectMigrationMap } from './migration'; +import { SavedObjectsTypeMappingDefinition } from './mapping_definition'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-server/src/security.ts b/packages/core/saved-objects/core-saved-objects-server/src/security.ts index 0623b5efb59d76..17e986b0dac202 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/security.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/security.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObject } from '@kbn/core-saved-objects-common'; +import { SavedObject } from '@kbn/core-saved-objects-common'; import { EcsEventOutcome } from '@kbn/logging'; /** diff --git a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts index 7752d0dd99e901..75f12260a0c7e7 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { SavedObjectsMigrationVersion, SavedObjectReference, } from '@kbn/core-saved-objects-common'; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts b/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts index ac3647da81f7fe..43eaaacdf764b6 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { SavedObjectsType } from './saved_objects_type'; +import { SavedObjectsType } from './saved_objects_type'; /** * Registry holding information about all the registered {@link SavedObjectsType | saved object types}. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/validation.ts b/packages/core/saved-objects/core-saved-objects-server/src/validation.ts index a60581e3939af9..bf5d8fc355ade7 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/validation.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/validation.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { ObjectType } from '@kbn/config-schema'; +import { ObjectType } from '@kbn/config-schema'; /** * Allows for validating properties using @kbn/config-schema validations. diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts index 28b3ebd3735b4f..bd060f672e264b 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import type { +import { SavedObjectMigrationContext, SavedObjectMigrationMap, SavedObjectUnsanitizedDoc, diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts index 6d7c1c90d25df1..e5b0f113bf0a80 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts @@ -7,7 +7,7 @@ */ import { mergeWith } from 'lodash'; -import type { +import { SavedObjectMigrationContext, SavedObjectMigrationFn, SavedObjectMigrationMap, diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts index 717b52ef248ca8..1b89f3064e07f9 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts @@ -8,7 +8,7 @@ import { mockUuidv1, mockUuidv5 } from './saved_objects_utils.test.mock'; -import type { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server'; +import { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsUtils } from './saved_objects_utils'; describe('SavedObjectsUtils', () => { diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts index 240abb53db289a..215743cb9101a4 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts @@ -8,7 +8,7 @@ import uuidv1 from 'uuid/v1'; import uuidv5 from 'uuid/v5'; -import type { +import { SavedObjectsFindOptions, SavedObjectsFindResponse, } from '@kbn/core-saved-objects-api-server'; From 7472293b4f60ede17b74dbe854dd338d763faec7 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Thu, 3 Nov 2022 10:42:44 +0000 Subject: [PATCH 27/47] [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' --- .../src/lib/update_objects_spaces.ts | 5 +---- .../src/validation/schema.test.ts | 5 +---- .../src/validation/validator.test.ts | 5 +---- .../src/validation/validator.ts | 5 +---- .../src/simple_saved_object.ts | 5 +---- .../src/simple_saved_object.mock.ts | 5 +---- .../src/import/import_saved_objects.ts | 5 +---- .../src/import/resolve_import_errors.ts | 5 +---- .../src/model/helpers.ts | 5 +---- .../core-saved-objects-server-internal/src/routes/index.ts | 5 +---- .../core-saved-objects-server/src/serialization.ts | 5 +---- 11 files changed, 11 insertions(+), 44 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts index 0c59161d7a25d3..07a7fe8a750701 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts @@ -30,10 +30,7 @@ import { type DecoratedError, SavedObjectsUtils, } from '@kbn/core-saved-objects-utils-server'; -import { - IndexMapping, - SavedObjectsSerializer, -} from '@kbn/core-saved-objects-base-server-internal'; +import { IndexMapping, SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObject } from '@kbn/core-saved-objects-common'; import { getBulkOperationError, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts index 16d1c1b1dbb020..8767a693999ad0 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts @@ -7,10 +7,7 @@ */ import { schema } from '@kbn/config-schema'; -import { - SavedObjectsValidationMap, - SavedObjectSanitizedDoc, -} from '@kbn/core-saved-objects-server'; +import { SavedObjectsValidationMap, SavedObjectSanitizedDoc } from '@kbn/core-saved-objects-server'; import { createSavedObjectSanitizedDocSchema } from './schema'; describe('Saved Objects type validation schema', () => { diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts index ea11a1c37e3a65..8481364de0291d 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts @@ -8,10 +8,7 @@ import { schema } from '@kbn/config-schema'; import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; -import { - SavedObjectSanitizedDoc, - SavedObjectsValidationMap, -} from '@kbn/core-saved-objects-server'; +import { SavedObjectSanitizedDoc, SavedObjectsValidationMap } from '@kbn/core-saved-objects-server'; import { SavedObjectsTypeValidator } from './validator'; describe('Saved Objects type validator', () => { diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts index 642b88e34b6331..dd9f34660e550d 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts @@ -7,10 +7,7 @@ */ import { Logger } from '@kbn/logging'; -import { - SavedObjectsValidationMap, - SavedObjectSanitizedDoc, -} from '@kbn/core-saved-objects-server'; +import { SavedObjectsValidationMap, SavedObjectSanitizedDoc } from '@kbn/core-saved-objects-server'; import { createSavedObjectSanitizedDocSchema } from './schema'; /** diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts index 4dd24fe3451b47..f86dffb849514a 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts @@ -9,10 +9,7 @@ import { set } from '@kbn/safer-lodash-set'; import { get, has } from 'lodash'; import { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; -import { - SavedObjectsClientContract, - SimpleSavedObject, -} from '@kbn/core-saved-objects-api-browser'; +import { SavedObjectsClientContract, SimpleSavedObject } from '@kbn/core-saved-objects-api-browser'; /** * Core internal implementation of {@link SimpleSavedObject} diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts index 194f4088393418..3e75014180ed60 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { - SavedObjectsClientContract, - SimpleSavedObject, -} from '@kbn/core-saved-objects-api-browser'; +import { SavedObjectsClientContract, SimpleSavedObject } from '@kbn/core-saved-objects-api-browser'; import { SavedObject } from '@kbn/core-saved-objects-common'; type T = unknown; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts index b328c9f5ea121e..7a75c05c463799 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts @@ -12,10 +12,7 @@ import { SavedObjectsImportResponse, } from '@kbn/core-saved-objects-common'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { - ISavedObjectTypeRegistry, - SavedObjectsImportHook, -} from '@kbn/core-saved-objects-server'; +import { ISavedObjectTypeRegistry, SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; import { checkReferenceOrigins, validateReferences, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts index 56879ad7ffee92..3b4172041601be 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts @@ -15,10 +15,7 @@ import { SavedObjectsImportSuccess, } from '@kbn/core-saved-objects-common'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { - ISavedObjectTypeRegistry, - SavedObjectsImportHook, -} from '@kbn/core-saved-objects-server'; +import { ISavedObjectTypeRegistry, SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; import { collectSavedObjects, createObjectsFilter, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index 034c56e35e5888..b5eed210b63e89 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -7,10 +7,7 @@ */ import { gt, valid } from 'semver'; -import { - QueryDslBoolQuery, - QueryDslQueryContainer, -} from '@elastic/elasticsearch/lib/api/types'; +import { QueryDslBoolQuery, QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import * as Either from 'fp-ts/lib/Either'; import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { State } from '../state'; 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 518e0467162b03..2757439b8055b2 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 @@ -8,10 +8,7 @@ import { Logger } from '@kbn/logging'; import { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; -import { - SavedObjectConfig, - IKibanaMigrator, -} from '@kbn/core-saved-objects-base-server-internal'; +import { SavedObjectConfig, IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; import { InternalSavedObjectsRequestHandlerContext } from '../internal_types'; import { registerGetRoute } from './get'; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts index 75f12260a0c7e7..42f900991559c6 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts @@ -6,10 +6,7 @@ * Side Public License, v 1. */ -import { - SavedObjectsMigrationVersion, - SavedObjectReference, -} from '@kbn/core-saved-objects-common'; +import { SavedObjectsMigrationVersion, SavedObjectReference } from '@kbn/core-saved-objects-common'; /** * A serializer that can be used to manually convert {@link SavedObjectsRawDoc | raw} or From 7f8214f57c8760de6b6b3e539eaa83b94020764b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 3 Nov 2022 15:10:34 +0100 Subject: [PATCH 28/47] Removes 'import type' in so package domain. Updates mocks for find shared origin objects and find legacy url aliases tests to remove reference to repository mock. Refactor of scoped client provider getExensions. --- .../lib/find_shared_origin_objects.test.ts | 21 +++++----- .../find_legacy_url_aliases.test.ts | 21 +++++----- .../src/lib/scoped_client_provider.ts | 38 ++++++++++--------- 3 files changed, 43 insertions(+), 37 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts index e3464f69dec636..44c81bc6eb49f5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.test.ts @@ -6,12 +6,11 @@ * Side Public License, v 1. */ -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import type { CreatePointInTimeFinderFn, PointInTimeFinder } from './point_in_time_finder'; +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { CreatePointInTimeFinderFn, PointInTimeFinder } from './point_in_time_finder'; import { savedObjectsPointInTimeFinderMock } from '../mocks/point_in_time_finder.mock'; import { findSharedOriginObjects } from './find_shared_origin_objects'; -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; -import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { SavedObjectsPointInTimeFinderClient } from '@kbn/core-saved-objects-api-server'; interface MockFindResultParams { type: string; @@ -21,13 +20,13 @@ interface MockFindResultParams { } describe('findSharedOriginObjects', () => { - let savedObjectsMock: jest.Mocked; + let pitFinderClientMock: jest.Mocked; let pointInTimeFinder: DeeplyMockedKeys; let createPointInTimeFinder: jest.MockedFunction; beforeEach(() => { - savedObjectsMock = savedObjectsRepositoryMock.create(); - savedObjectsMock.find.mockResolvedValue({ + pitFinderClientMock = savedObjectsPointInTimeFinderMock.createClient(); + pitFinderClientMock.find.mockResolvedValue({ pit_id: 'foo', saved_objects: [], // the rest of these fields don't matter but are included for type safety @@ -35,12 +34,14 @@ describe('findSharedOriginObjects', () => { page: 1, per_page: 100, }); - pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ savedObjectsMock })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too + pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ + savedObjectsMock: pitFinderClientMock, + })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too createPointInTimeFinder = jest.fn().mockReturnValue(pointInTimeFinder); }); function mockFindResults(...results: MockFindResultParams[]) { - savedObjectsMock.find.mockResolvedValueOnce({ + pitFinderClientMock.find.mockResolvedValueOnce({ pit_id: 'foo', saved_objects: results.map(({ type, id, originId, namespaces }) => ({ type, @@ -133,7 +134,7 @@ describe('findSharedOriginObjects', () => { }); it('handles PointInTimeFinder.find errors', async () => { - savedObjectsMock.find.mockRejectedValue(new Error('Oh no!')); + pitFinderClientMock.find.mockRejectedValue(new Error('Oh no!')); const objects = [obj1, obj2, obj3]; await expect(() => findSharedOriginObjects(createPointInTimeFinder, objects)).rejects.toThrow( diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts index 49fe50e18776d6..41c4a3f726549d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.test.ts @@ -6,26 +6,25 @@ * Side Public License, v 1. */ -import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; import { type LegacyUrlAlias, LEGACY_URL_ALIAS_TYPE, } from '@kbn/core-saved-objects-base-server-internal'; -import type { CreatePointInTimeFinderFn, PointInTimeFinder } from '../point_in_time_finder'; +import { CreatePointInTimeFinderFn, PointInTimeFinder } from '../point_in_time_finder'; import { findLegacyUrlAliases } from './find_legacy_url_aliases'; import { savedObjectsPointInTimeFinderMock } from '../../mocks'; -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; -import { savedObjectsRepositoryMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { SavedObjectsPointInTimeFinderClient } from '@kbn/core-saved-objects-api-server'; describe('findLegacyUrlAliases', () => { - let savedObjectsMock: jest.Mocked; + let pitFinderClientMock: jest.Mocked; let pointInTimeFinder: DeeplyMockedKeys; let createPointInTimeFinder: jest.MockedFunction; beforeEach(() => { - savedObjectsMock = savedObjectsRepositoryMock.create(); - savedObjectsMock.find.mockResolvedValue({ + pitFinderClientMock = savedObjectsPointInTimeFinderMock.createClient(); + pitFinderClientMock.find.mockResolvedValue({ pit_id: 'foo', saved_objects: [], // the rest of these fields don't matter but are included for type safety @@ -33,12 +32,14 @@ describe('findLegacyUrlAliases', () => { page: 1, per_page: 100, }); - pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ savedObjectsMock })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too + pointInTimeFinder = savedObjectsPointInTimeFinderMock.create({ + savedObjectsMock: pitFinderClientMock, + })(); // PIT finder mock uses the actual implementation, but it doesn't need to be created with real params because the SOR is mocked too createPointInTimeFinder = jest.fn().mockReturnValue(pointInTimeFinder); }); function mockFindResults(...results: LegacyUrlAlias[]) { - savedObjectsMock.find.mockResolvedValueOnce({ + pitFinderClientMock.find.mockResolvedValueOnce({ pit_id: 'foo', saved_objects: results.map((attributes) => ({ id: 'doesnt-matter', @@ -127,7 +128,7 @@ describe('findLegacyUrlAliases', () => { }); it('handles PointInTimeFinder.find errors', async () => { - savedObjectsMock.find.mockRejectedValue(new Error('Oh no!')); + pitFinderClientMock.find.mockRejectedValue(new Error('Oh no!')); const objects = [obj1, obj2, obj3]; await expect(() => findLegacyUrlAliases(createPointInTimeFinder, objects)).rejects.toThrow( diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index 413b900b3b48b4..e0fa0a16d2e8df 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import type { KibanaRequest } from '@kbn/core-http-server'; -import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import { KibanaRequest } from '@kbn/core-http-server'; +import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { ISavedObjectTypeRegistry, SavedObjectsClientFactory, @@ -19,6 +19,9 @@ import { ENCRYPTION_EXTENSION_ID, SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID, + ISavedObjectsEncryptionExtension, + ISavedObjectsSecurityExtension, + ISavedObjectsSpacesExtension, } from '@kbn/core-saved-objects-server'; /** @@ -82,21 +85,22 @@ export class SavedObjectsClientProvider { } getExtensions(request: KibanaRequest, excludedExtensions: string[]): SavedObjectsExtensions { - const isEncryptionExtensionIncluded = - !excludedExtensions.includes(ENCRYPTION_EXTENSION_ID) && !!this.encryptionExtensionFactory; - const encryptionExtension = isEncryptionExtensionIncluded - ? this.encryptionExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) - : undefined; - const isSecurityExtensionIncluded = - !excludedExtensions.includes(SECURITY_EXTENSION_ID) && !!this.securityExtensionFactory; - const securityExtension = isSecurityExtensionIncluded - ? this.securityExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) - : undefined; - const isSpacesExtensionIncluded = - !excludedExtensions.includes(SPACES_EXTENSION_ID) && !!this.spacesExtensionFactory; - const spacesExtension = isSpacesExtensionIncluded - ? this.spacesExtensionFactory?.({ typeRegistry: this._typeRegistry, request }) - : undefined; + const extensionInfo = [ + { id: ENCRYPTION_EXTENSION_ID, factory: this.encryptionExtensionFactory }, + { id: SECURITY_EXTENSION_ID, factory: this.securityExtensionFactory }, + { id: SPACES_EXTENSION_ID, factory: this.spacesExtensionFactory }, + ]; + + const extensions = extensionInfo.map((extension) => { + const isIncluded = !excludedExtensions.includes(extension.id) && !!extension.factory; + return isIncluded + ? extension.factory?.({ typeRegistry: this._typeRegistry, request }) + : undefined; + }); + + const encryptionExtension = extensions[0] as ISavedObjectsEncryptionExtension | undefined; + const securityExtension = extensions[1] as ISavedObjectsSecurityExtension | undefined; + const spacesExtension = extensions[2] as ISavedObjectsSpacesExtension | undefined; return { encryptionExtension, From d6042938c97f60475d0cb12152084ce9b339c4fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 3 Nov 2022 15:18:00 +0100 Subject: [PATCH 29/47] Reverting removal of import type --- .../src/apis/bulk_create.ts | 2 +- .../src/apis/bulk_resolve.ts | 2 +- .../src/apis/bulk_update.ts | 2 +- .../src/apis/create.ts | 5 +++- .../src/apis/find.ts | 4 ++-- .../src/apis/resolve.ts | 2 +- .../src/apis/update.ts | 2 +- .../src/saved_objects_client.ts | 6 ++--- .../src/simple_saved_object.ts | 2 +- .../src/lib/aggregations/validation.ts | 2 +- .../src/lib/aggregations/validation_utils.ts | 2 +- ...ct_multi_namespace_references.test.mock.ts | 6 ++--- ...collect_multi_namespace_references.test.ts | 4 ++-- .../lib/collect_multi_namespace_references.ts | 8 +++---- .../src/lib/decorate_es_error.ts | 2 +- .../src/lib/filter_utils.ts | 2 +- .../src/lib/find_shared_origin_objects.ts | 2 +- .../lib/internal_bulk_resolve.test.mock.ts | 4 ++-- .../src/lib/internal_bulk_resolve.test.ts | 4 ++-- .../src/lib/internal_bulk_resolve.ts | 8 +++---- .../src/lib/internal_utils.test.ts | 2 +- .../src/lib/internal_utils.ts | 6 ++--- .../delete_legacy_url_aliases.test.mock.ts | 2 +- .../delete_legacy_url_aliases.test.ts | 2 +- .../delete_legacy_url_aliases.ts | 4 ++-- .../find_legacy_url_aliases.ts | 2 +- .../src/lib/point_in_time_finder.test.ts | 2 +- .../src/lib/point_in_time_finder.ts | 4 ++-- .../preflight_check_for_create.test.mock.ts | 4 ++-- .../lib/preflight_check_for_create.test.ts | 6 ++--- .../src/lib/preflight_check_for_create.ts | 6 ++--- .../src/lib/repository.test.mock.ts | 12 +++++----- .../src/lib/repository.test.ts | 8 +++---- .../src/lib/repository.ts | 12 +++++----- .../repository_bulk_delete_internal_types.ts | 4 ++-- .../src/lib/repository_es_client.ts | 4 ++-- .../src/lib/scoped_client_provider.test.ts | 2 +- .../src/lib/search_dsl/pit_params.ts | 2 +- .../src/lib/search_dsl/query_params.ts | 4 ++-- .../src/lib/search_dsl/references_filter.ts | 4 ++-- .../src/lib/search_dsl/search_dsl.ts | 8 +++---- .../lib/update_objects_spaces.test.mock.ts | 4 ++-- .../src/lib/update_objects_spaces.test.ts | 4 ++-- .../src/lib/update_objects_spaces.ts | 13 ++++++---- .../src/mocks/kibana_migrator.mock.ts | 4 ++-- .../src/mocks/point_in_time_finder.mock.ts | 4 ++-- .../src/mocks/repository.mock.ts | 2 +- .../src/saved_objects_client.test.ts | 2 +- .../src/saved_objects_client.ts | 4 ++-- .../src/point_in_time_finder.mock.ts | 2 +- .../src/repository.mock.ts | 2 +- .../src/saved_objects_client.mock.ts | 2 +- .../src/saved_objects_extensions.mock.ts | 2 +- .../src/scoped_client_provider.mock.ts | 2 +- .../tsconfig.json | 2 ++ .../src/apis/base.ts | 2 +- .../src/apis/bulk_create.ts | 5 +++- .../src/apis/bulk_delete.ts | 4 ++-- .../src/apis/bulk_resolve.ts | 2 +- .../src/apis/bulk_update.ts | 4 ++-- .../src/apis/check_conflicts.ts | 2 +- .../src/apis/close_point_in_time.ts | 2 +- .../apis/collect_multinamespace_references.ts | 2 +- .../src/apis/create.ts | 7 ++++-- .../src/apis/create_point_in_time_finder.ts | 2 +- .../src/apis/delete.ts | 2 +- .../src/apis/delete_by_namespace.ts | 2 +- .../src/apis/find.ts | 4 ++-- .../src/apis/increment_counter.ts | 4 ++-- .../src/apis/remove_references_to.ts | 2 +- .../src/apis/resolve.ts | 2 +- .../src/apis/update.ts | 4 ++-- .../src/apis/update_objects_spaces.ts | 4 ++-- .../src/saved_objects_client.ts | 4 ++-- .../src/saved_objects_repository.ts | 4 ++-- .../src/mappings/lib/get_property.test.ts | 4 ++-- .../src/mappings/lib/get_property.ts | 2 +- .../lib/get_root_properties_objects.ts | 2 +- .../src/mappings/types.ts | 2 +- .../src/migration/kibana_migrator.ts | 4 ++-- .../src/saved_objects_config.ts | 2 +- .../src/saved_objects_type_registry.test.ts | 2 +- .../src/saved_objects_type_registry.ts | 2 +- .../src/serialization/serializer.test.ts | 2 +- .../src/serialization/serializer.ts | 2 +- .../src/utils/get_index_for_type.ts | 2 +- .../src/validation/schema.test.ts | 5 +++- .../src/validation/schema.ts | 2 +- .../src/validation/validator.test.ts | 5 +++- .../src/validation/validator.ts | 7 ++++-- .../src/saved_objects_type_registry.mock.ts | 4 ++-- .../src/serializer.mock.ts | 2 +- .../src/saved_objects_client.ts | 8 +++---- .../src/saved_objects_service.ts | 6 ++--- .../src/simple_saved_object.test.ts | 4 ++-- .../src/simple_saved_object.ts | 7 ++++-- .../src/saved_objects_service.mock.ts | 6 ++--- .../src/simple_saved_object.mock.ts | 7 ++++-- .../src/contracts.ts | 2 +- .../export/apply_export_transforms.test.ts | 4 ++-- .../src/export/apply_export_transforms.ts | 6 ++--- .../export/collect_exported_objects.test.ts | 4 ++-- .../src/export/collect_exported_objects.ts | 10 ++++---- .../src/export/errors.ts | 2 +- .../src/export/saved_objects_exporter.test.ts | 2 +- .../src/export/saved_objects_exporter.ts | 10 ++++---- .../src/export/sort_objects.test.ts | 2 +- .../src/export/sort_objects.ts | 2 +- .../src/export/utils.test.ts | 2 +- .../src/export/utils.ts | 2 +- .../src/import/errors.ts | 2 +- .../import/import_saved_objects.test.mock.ts | 16 ++++++------- .../src/import/import_saved_objects.test.ts | 8 +++---- .../src/import/import_saved_objects.ts | 9 ++++--- .../src/import/lib/check_conflicts.test.ts | 4 ++-- .../src/import/lib/check_conflicts.ts | 6 ++--- .../lib/check_origin_conflicts.test.mock.ts | 2 +- .../import/lib/check_origin_conflicts.test.ts | 8 +++---- .../src/import/lib/check_origin_conflicts.ts | 8 +++---- .../lib/check_reference_origins.test.mock.ts | 2 +- .../lib/check_reference_origins.test.ts | 6 ++--- .../src/import/lib/check_reference_origins.ts | 6 ++--- .../src/import/lib/collect_saved_objects.ts | 4 ++-- .../src/import/lib/create_objects_filter.ts | 2 +- .../import/lib/create_saved_objects.test.ts | 2 +- .../src/import/lib/create_saved_objects.ts | 8 +++---- .../import/lib/execute_import_hooks.test.ts | 4 ++-- .../src/import/lib/execute_import_hooks.ts | 4 ++-- .../src/import/lib/extract_errors.test.ts | 4 ++-- .../src/import/lib/extract_errors.ts | 4 ++-- .../get_import_state_map_for_retries.test.ts | 2 +- .../lib/get_import_state_map_for_retries.ts | 4 ++-- .../src/import/lib/regenerate_ids.test.ts | 2 +- .../src/import/lib/regenerate_ids.ts | 4 ++-- .../src/import/lib/split_overwrites.ts | 2 +- .../import/lib/validate_references.test.ts | 2 +- .../src/import/lib/validate_references.ts | 6 ++--- .../src/import/lib/validate_retries.test.ts | 2 +- .../src/import/lib/validate_retries.ts | 2 +- .../import/resolve_import_errors.test.mock.ts | 24 +++++++++---------- .../src/import/resolve_import_errors.test.ts | 6 ++--- .../src/import/resolve_import_errors.ts | 9 ++++--- .../src/import/saved_objects_importer.ts | 6 ++--- .../tsconfig.json | 2 ++ .../src/saved_objects_exporter.mock.ts | 2 +- .../src/saved_objects_importer.mock.ts | 2 +- .../bulk_overwrite_transformed_documents.ts | 8 +++---- .../src/actions/calculate_exclude_filters.ts | 8 +++---- .../actions/check_for_unknown_docs.test.ts | 2 +- .../src/actions/check_for_unknown_docs.ts | 6 ++--- .../src/actions/clone_index.ts | 4 ++-- .../src/actions/close_pit.ts | 2 +- .../src/actions/create_index.ts | 4 ++-- .../src/actions/es_errors.ts | 2 +- .../src/actions/fetch_indices.ts | 4 ++-- .../src/actions/index.ts | 8 +++---- .../src/actions/initialize_action.ts | 2 +- .../src/actions/open_pit.ts | 2 +- .../src/actions/pickup_updated_mappings.ts | 2 +- .../src/actions/read_with_pit.ts | 4 ++-- .../src/actions/refresh_index.ts | 2 +- .../src/actions/reindex.ts | 4 ++-- .../src/actions/remove_write_block.ts | 2 +- .../actions/search_for_outdated_documents.ts | 4 ++-- .../src/actions/set_write_block.ts | 2 +- .../src/actions/transform_docs.ts | 4 ++-- .../src/actions/update_aliases.ts | 2 +- .../src/actions/update_and_pickup_mappings.ts | 4 ++-- .../src/actions/wait_for_index_status.ts | 2 +- .../src/actions/wait_for_reindex_task.ts | 2 +- .../src/actions/wait_for_task.ts | 4 ++-- .../src/core/build_active_mappings.test.ts | 2 +- .../src/core/build_active_mappings.ts | 4 ++-- .../src/core/build_index_map.test.ts | 2 +- .../src/core/build_index_map.ts | 4 ++-- .../disable_unknown_type_mapping_fields.ts | 4 ++-- .../src/core/document_migrator.test.ts | 2 +- .../src/core/document_migrator.ts | 6 ++--- .../src/core/migrate_raw_docs.ts | 2 +- .../src/core/migration_logger.ts | 4 ++-- .../src/core/unused_types.ts | 2 +- .../src/initial_state.test.ts | 2 +- .../src/initial_state.ts | 12 +++++----- .../src/kibana_migrator.test.ts | 4 ++-- .../src/kibana_migrator.ts | 10 ++++---- .../src/migrations_state_action_machine.ts | 6 ++--- .../src/migrations_state_machine_cleanup.ts | 4 ++-- .../src/model/create_batches.test.ts | 2 +- .../src/model/create_batches.ts | 2 +- .../src/model/extract_errors.ts | 4 ++-- .../src/model/helpers.ts | 11 +++++---- .../src/model/model.test.ts | 4 ++-- .../src/model/model.ts | 8 +++---- .../src/model/progress.test.ts | 2 +- .../src/model/progress.ts | 2 +- .../src/next.test.ts | 2 +- .../src/next.ts | 6 ++--- .../src/run_resilient_migrator.ts | 14 +++++------ .../src/state.ts | 16 ++++++------- .../src/types.ts | 2 +- .../src/kibana_migrator.mock.ts | 4 ++-- .../src/migration.mocks.ts | 2 +- .../src/deprecations/deprecation_factory.ts | 6 ++--- .../deprecations/unknown_object_types.test.ts | 4 ++-- .../src/deprecations/unknown_object_types.ts | 6 ++--- .../src/internal_types.ts | 6 ++--- .../src/mocks/internal_mocks.ts | 4 ++-- .../src/object_types/registration.ts | 2 +- .../src/routes/bulk_create.ts | 4 ++-- .../src/routes/bulk_delete.ts | 4 ++-- .../src/routes/bulk_get.ts | 4 ++-- .../src/routes/bulk_resolve.ts | 4 ++-- .../src/routes/bulk_update.ts | 4 ++-- .../src/routes/create.ts | 4 ++-- .../src/routes/delete.ts | 4 ++-- .../deprecations/delete_unknown_types.ts | 2 +- .../src/routes/export.ts | 10 ++++---- .../src/routes/find.ts | 4 ++-- .../src/routes/get.ts | 4 ++-- .../src/routes/import.ts | 6 ++--- .../src/routes/index.ts | 13 ++++++---- .../src/routes/legacy_import_export/export.ts | 6 ++--- .../src/routes/legacy_import_export/import.ts | 8 +++---- .../lib/collect_references_deep.test.ts | 2 +- .../lib/collect_references_deep.ts | 4 ++-- .../lib/export_dashboards.ts | 2 +- .../lib/import_dashboards.test.ts | 2 +- .../lib/import_dashboards.ts | 4 ++-- .../src/routes/migrate.ts | 4 ++-- .../src/routes/resolve.ts | 4 ++-- .../src/routes/resolve_import_errors.ts | 6 ++--- .../src/routes/update.ts | 6 ++--- .../src/routes/utils.test.ts | 2 +- .../src/routes/utils.ts | 6 ++--- .../saved_objects_route_handler_context.ts | 8 +++---- .../src/saved_objects_service.test.ts | 4 ++-- .../src/saved_objects_service.ts | 22 ++++++++--------- .../src/status.ts | 4 ++-- .../src/saved_objects_service.mock.ts | 6 ++--- .../tsconfig.json | 2 ++ .../src/client_factory.ts | 14 +++++------ .../src/contracts.ts | 16 ++++++------- .../src/encryption.ts | 2 +- .../core-saved-objects-server/src/export.ts | 8 +++---- .../src/extensions.ts | 6 ++--- .../src/mapping_definition.ts | 2 +- .../src/migration.ts | 4 ++-- .../src/request_handler_context.ts | 10 ++++---- .../src/saved_objects_management.ts | 6 ++--- .../src/saved_objects_type.ts | 16 ++++++------- .../core-saved-objects-server/src/security.ts | 2 +- .../src/serialization.ts | 5 +++- .../src/type_registry.ts | 2 +- .../src/validation.ts | 2 +- .../src/merge_migration_maps.test.ts | 2 +- .../src/merge_migration_maps.ts | 2 +- .../src/saved_objects_utils.test.ts | 2 +- .../src/saved_objects_utils.ts | 2 +- 258 files changed, 598 insertions(+), 550 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts index 3cfcfa2045a33a..6d5ce23205ef82 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_create.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsCreateOptions } from './create'; +import type { SavedObjectsCreateOptions } from './create'; /** * Per-object parameters for bulk create operation diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts index 4fd5bd1a40e2cc..c5ba4825252985 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ResolvedSimpleSavedObject } from './resolve'; +import type { ResolvedSimpleSavedObject } from './resolve'; /** * Return type of the Saved Objects `bulkResolve()` method. diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts index c14da0c80e6b42..c819fb1ac448d1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/bulk_update.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectReference } from '@kbn/core-saved-objects-common'; +import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; /** * Per-object parameters for bulk update operation diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts index 6db009f81d0290..fb6a18219e5bf5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/create.ts @@ -6,7 +6,10 @@ * Side Public License, v 1. */ -import { SavedObjectReference, SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import type { + SavedObjectReference, + SavedObjectsMigrationVersion, +} from '@kbn/core-saved-objects-common'; /** * Options for creating a saved object. diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts index d10bb4d7a2ae49..39d99a6ee6e86d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/find.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectsFindOptions as SavedObjectFindOptionsServer } from '@kbn/core-saved-objects-api-server'; -import { SavedObjectsBatchResponse } from './base'; +import type { SavedObjectsFindOptions as SavedObjectFindOptionsServer } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsBatchResponse } from './base'; export type { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts index 0b6c4636a0706e..c2383d7cb50d50 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsResolveResponse } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsResolveResponse } from '@kbn/core-saved-objects-api-server'; import { SimpleSavedObject } from '../simple_saved_object'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts index 0b491799af4b54..f8095e35df1e5d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/update.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectReference } from '@kbn/core-saved-objects-common'; +import type { SavedObjectReference } from '@kbn/core-saved-objects-common'; /** * Options for updating a saved object diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts index f094214df0dfb9..9ff01104c5ba07 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/saved_objects_client.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import type { ResolvedSimpleSavedObject, SavedObjectsBatchResponse, SavedObjectsBulkCreateObject, @@ -23,7 +23,7 @@ import { SavedObjectsBulkDeleteOptions, } from './apis'; -import { SimpleSavedObject } from './simple_saved_object'; +import type { SimpleSavedObject } from './simple_saved_object'; /** * The client-side SavedObjectsClient is a thin convenience library around the SavedObjects diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts index 51425fe923226b..8edb1347d6a889 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; +import type { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; /** * Very simple wrapper for SavedObjects loaded from the server diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts index 273924271f9637..b3a6bbae5e9561 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation.ts @@ -10,7 +10,7 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { ObjectType } from '@kbn/config-schema'; import { isPlainObject, isArray } from 'lodash'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { isObjectTypeAttribute, rewriteObjectTypeAttribute, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts index 69ff90c642c739..5548ad4d57a5dd 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/aggregations/validation_utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { fieldDefined, hasFilterKeyError } from '../filter_utils'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts index bb02460d766c3a..5476f99c3b37dd 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.mock.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { findLegacyUrlAliases } from './legacy_url_aliases'; -import { findSharedOriginObjects } from './find_shared_origin_objects'; -import * as InternalUtils from './internal_utils'; +import type { findLegacyUrlAliases } from './legacy_url_aliases'; +import type { findSharedOriginObjects } from './find_shared_origin_objects'; +import type * as InternalUtils from './internal_utils'; export const mockFindLegacyUrlAliases = jest.fn() as jest.MockedFunction< typeof findLegacyUrlAliases diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 76dce40be9569a..4bb4606131cb00 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -13,7 +13,7 @@ import { } from './collect_multi_namespace_references.test.mock'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { +import type { SavedObjectsCollectMultiNamespaceReferencesObject, SavedObjectsCollectMultiNamespaceReferencesOptions, } from '@kbn/core-saved-objects-api-server'; @@ -25,7 +25,7 @@ import { CollectMultiNamespaceReferencesParams, } from './collect_multi_namespace_references'; import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; -import { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; import { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts index f683320d646270..0db38392efd55f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts @@ -7,8 +7,8 @@ */ import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsCollectMultiNamespaceReferencesObject, SavedObjectsCollectMultiNamespaceReferencesOptions, SavedObjectsCollectMultiNamespaceReferencesResponse, @@ -28,8 +28,8 @@ import { import { findLegacyUrlAliases } from './legacy_url_aliases'; import { getRootFields } from './included_fields'; import { getSavedObjectFromSource, rawDocExistsInNamespace } from './internal_utils'; -import { CreatePointInTimeFinderFn } from './point_in_time_finder'; -import { RepositoryEsClient } from './repository_es_client'; +import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import type { RepositoryEsClient } from './repository_es_client'; import { findSharedOriginObjects } from './find_shared_origin_objects'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts index e719c3433c8004..9cfdffc13a5ddc 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/decorate_es_error.ts @@ -8,7 +8,7 @@ import { get } from 'lodash'; import { errors as esErrors } from '@elastic/elasticsearch'; -import { ElasticsearchErrorDetails } from '@kbn/es-errors'; +import type { ElasticsearchErrorDetails } from '@kbn/es-errors'; import { isSupportedEsServer } from '@kbn/core-elasticsearch-server-internal'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts index f4512ebc323076..ae7bb08039850c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/filter_utils.ts @@ -10,7 +10,7 @@ import { set } from '@kbn/safer-lodash-set'; import { get, cloneDeep } from 'lodash'; import * as esKuery from '@kbn/es-query'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; type KueryNode = any; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts index 6ab78d07ef0989..a489e4afa91c37 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/find_shared_origin_objects.ts @@ -9,7 +9,7 @@ import * as esKuery from '@kbn/es-query'; import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { getObjectKey } from '@kbn/core-saved-objects-base-server-internal'; -import { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; interface ObjectOrigin { /** The object's type. */ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts index e3763d4107ad3d..277d1ae4af34ac 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import * as InternalUtils from './internal_utils'; +import type { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; +import type * as InternalUtils from './internal_utils'; export const mockGetSavedObjectFromSource = jest.fn() as jest.MockedFunction< typeof InternalUtils['getSavedObjectFromSource'] diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index bbfe6e37093d3b..b0e9bf5776b1c0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -13,8 +13,8 @@ import { } from './internal_bulk_resolve.test.mock'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsBulkResolveObject, SavedObjectsBaseOptions, } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts index 7a46cd4f4186ca..6e13671661b95d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { MgetResponseItem } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { MgetResponseItem } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsBaseOptions, SavedObjectsBulkResolveObject, SavedObjectsResolveResponse, @@ -50,7 +50,7 @@ import { isLeft, isRight, } from './internal_utils'; -import { RepositoryEsClient } from './repository_es_client'; +import type { RepositoryEsClient } from './repository_es_client'; const MAX_CONCURRENT_RESOLVE = 10; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts index aec44a4a1f9cea..c295c0b3faad8a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { encodeHitVersion } from '@kbn/core-saved-objects-base-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts index 599fe7c246dfdc..73134f4855a370 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_utils.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { Payload } from '@hapi/boom'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { Payload } from '@hapi/boom'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { ISavedObjectTypeRegistry, SavedObjectsRawDoc, SavedObjectsRawDocSource, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts index 7e326711133c8b..0922910936c521 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { getErrorMessage } from '@kbn/core-elasticsearch-client-server-internal'; +import type { getErrorMessage } from '@kbn/core-elasticsearch-client-server-internal'; export const mockGetEsErrorMessage = jest.fn() as jest.MockedFunction; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts index 502a7caa77589a..77a538672ff198 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.test.ts @@ -14,7 +14,7 @@ import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-m import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { deleteLegacyUrlAliases } from './delete_legacy_url_aliases'; -import { DeleteLegacyUrlAliasesParams } from './delete_legacy_url_aliases'; +import type { DeleteLegacyUrlAliasesParams } from './delete_legacy_url_aliases'; type SetupParams = Pick< DeleteLegacyUrlAliasesParams, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts index 15cd56227db59f..73489308f59afc 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/delete_legacy_url_aliases.ts @@ -9,13 +9,13 @@ import * as esKuery from '@kbn/es-query'; import { getErrorMessage as getEsErrorMessage } from '@kbn/core-elasticsearch-client-server-internal'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { ALL_NAMESPACES_STRING } from '@kbn/core-saved-objects-utils-server'; import { LEGACY_URL_ALIAS_TYPE, type IndexMapping, } from '@kbn/core-saved-objects-base-server-internal'; -import { RepositoryEsClient } from '../repository_es_client'; +import type { RepositoryEsClient } from '../repository_es_client'; import { getSearchDsl } from '../search_dsl'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts index 3bfcafce49ff4b..62bf5a51d88936 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/legacy_url_aliases/find_legacy_url_aliases.ts @@ -12,7 +12,7 @@ import { LEGACY_URL_ALIAS_TYPE, getObjectKey, } from '@kbn/core-saved-objects-base-server-internal'; -import { CreatePointInTimeFinderFn } from '../point_in_time_finder'; +import type { CreatePointInTimeFinderFn } from '../point_in_time_finder'; interface FindLegacyUrlAliasesObject { type: string; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts index 9d3655dd9a1f4d..a8f9722c09e7a5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts @@ -7,7 +7,7 @@ */ import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; -import { +import type { SavedObjectsFindResult, SavedObjectsCreatePointInTimeFinderOptions, } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts index 08e2f7b8b26fab..7dffbbdaa356fe 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { Logger } from '@kbn/logging'; -import { +import type { Logger } from '@kbn/logging'; +import type { SavedObjectsFindOptions, SavedObjectsFindResponse, SavedObjectsCreatePointInTimeFinderDependencies, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts index 1a299dd23a7e7e..fe8076b51e5dd4 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { findLegacyUrlAliases } from './legacy_url_aliases'; -import * as InternalUtils from './internal_utils'; +import type { findLegacyUrlAliases } from './legacy_url_aliases'; +import type * as InternalUtils from './internal_utils'; export const mockFindLegacyUrlAliases = jest.fn() as jest.MockedFunction< typeof findLegacyUrlAliases diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts index fc93473e863622..d23d2cf5e804e8 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts @@ -11,16 +11,16 @@ import { mockRawDocExistsInNamespaces, } from './preflight_check_for_create.test.mock'; -import { DeeplyMockedKeys } from '@kbn/utility-types-jest'; +import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { SavedObjectsSerializer, LEGACY_URL_ALIAS_TYPE, } from '@kbn/core-saved-objects-base-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; import { ALIAS_SEARCH_PER_PAGE, PreflightCheckForCreateObject, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts index d3ff4e10c812d4..1d1b4839635e8c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts @@ -8,7 +8,7 @@ import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import { +import type { ISavedObjectTypeRegistry, SavedObjectsRawDoc, SavedObjectsRawDocSource, @@ -26,8 +26,8 @@ import { import { findLegacyUrlAliases } from './legacy_url_aliases'; import { Either, rawDocExistsInNamespaces } from './internal_utils'; import { isLeft, isRight } from './internal_utils'; -import { CreatePointInTimeFinderFn } from './point_in_time_finder'; -import { RepositoryEsClient } from './repository_es_client'; +import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; +import type { RepositoryEsClient } from './repository_es_client'; /** * If the object will be created in this many spaces (or "*" all current and future spaces), we use find to fetch all aliases. diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts index 30190de044d28f..a9c1871e2488e1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.mock.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; -import { internalBulkResolve } from './internal_bulk_resolve'; -import * as InternalUtils from './internal_utils'; -import { preflightCheckForCreate } from './preflight_check_for_create'; -import { updateObjectsSpaces } from './update_objects_spaces'; -import { deleteLegacyUrlAliases } from './legacy_url_aliases'; +import type { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; +import type { internalBulkResolve } from './internal_bulk_resolve'; +import type * as InternalUtils from './internal_utils'; +import type { preflightCheckForCreate } from './preflight_check_for_create'; +import type { updateObjectsSpaces } from './update_objects_spaces'; +import type { deleteLegacyUrlAliases } from './legacy_url_aliases'; export const mockCollectMultiNamespaceReferences = jest.fn() as jest.MockedFunction< typeof collectMultiNamespaceReferences diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index aea8e448ad8439..36b6cb59135661 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -20,11 +20,11 @@ import { mockGetSearchDsl, } from './repository.test.mock'; -import { Payload } from '@hapi/boom'; +import type { Payload } from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject, SavedObjectReference } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsUpdateObjectsSpacesResponse, @@ -48,7 +48,7 @@ import { SavedObjectsBulkDeleteObject, SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; -import { +import type { SavedObjectsRawDoc, SavedObjectsRawDocSource, SavedObjectUnsanitizedDoc, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index c92405dc35ac40..e2c20c7485f95f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -8,17 +8,17 @@ import { omit, isObject } from 'lodash'; import Boom from '@hapi/boom'; -import { Payload } from '@hapi/boom'; +import type { Payload } from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import * as esKuery from '@kbn/es-query'; -import { Logger } from '@kbn/logging'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { Logger } from '@kbn/logging'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { isSupportedEsServer, isNotFoundFromUnsupportedServer, } from '@kbn/core-elasticsearch-server-internal'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsBaseOptions, SavedObjectsIncrementCounterOptions, SavedObjectsDeleteByNamespaceOptions, @@ -128,7 +128,7 @@ import { PreflightCheckForCreateResult, } from './preflight_check_for_create'; import { deleteLegacyUrlAliases } from './legacy_url_aliases'; -import { +import type { BulkDeleteParams, ExpectedBulkDeleteResult, BulkDeleteItemErrorResult, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts index 6d1ff26e576bac..93d4354d8d7e84 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts @@ -5,13 +5,13 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { Payload } from '@hapi/boom'; +import type { Payload } from '@hapi/boom'; import { BulkOperationBase, BulkResponseItem, ErrorCause, } from '@elastic/elasticsearch/lib/api/types'; -import { estypes, TransportResult } from '@elastic/elasticsearch'; +import type { estypes, TransportResult } from '@elastic/elasticsearch'; import { Either } from './internal_utils'; import { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts index 4e46393e9b37e3..14dcb996267892 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_es_client.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { TransportRequestOptions } from '@elastic/elasticsearch'; +import type { TransportRequestOptions } from '@elastic/elasticsearch'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { retryCallCluster } from '@kbn/core-elasticsearch-server-internal'; import { decorateEsError } from './decorate_es_error'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts index c71d74e7f93cbf..871b38fd89af80 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { Optional } from 'utility-types'; +import type { Optional } from 'utility-types'; import { httpServerMock } from '@kbn/core-http-server-mocks'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { SavedObjectsClientProvider } from './scoped_client_provider'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts index 9e8dce4e7aae99..c8be64e9a0494b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/pit_params.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; export function getPitParams(pit: SavedObjectsPitParams) { return { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts index 4a51c19bc8d5cd..896b934c90b805 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/query_params.ts @@ -7,11 +7,11 @@ */ import * as esKuery from '@kbn/es-query'; -import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; type KueryNode = any; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts index 540210a7075251..4dd6bc640f1741 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/references_filter.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import { SearchOperator } from './query_params'; +import type { SearchOperator } from './query_params'; export function getReferencesFilter({ references, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts index 5e55f5ef49881d..381f20069d25a9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts @@ -9,10 +9,10 @@ import Boom from '@hapi/boom'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; import { getQueryParams, SearchOperator } from './query_params'; import { getPitParams } from './pit_params'; import { getSortingParams } from './sorting_params'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts index 40a8ac88b596db..043975d5bb52b1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import * as InternalUtils from './internal_utils'; -import { deleteLegacyUrlAliases } from './legacy_url_aliases'; +import type * as InternalUtils from './internal_utils'; +import type { deleteLegacyUrlAliases } from './legacy_url_aliases'; export const mockGetBulkOperationError = jest.fn() as jest.MockedFunction< typeof InternalUtils['getBulkOperationError'] diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 77c7e601cd68f7..9d3f8475bc5b1c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -16,14 +16,14 @@ import { import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; -import { SavedObjectsUpdateObjectsSpacesObject } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsUpdateObjectsSpacesObject } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers, ALL_NAMESPACES_STRING, } from '@kbn/core-saved-objects-utils-server'; import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import { UpdateObjectsSpacesParams } from './update_objects_spaces'; +import type { UpdateObjectsSpacesParams } from './update_objects_spaces'; import { updateObjectsSpaces } from './update_objects_spaces'; import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; import { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts index 07a7fe8a750701..f5b810ea3f3af5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts @@ -10,9 +10,9 @@ import pMap from 'p-map'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import intersection from 'lodash/intersection'; -import { Logger } from '@kbn/logging'; +import type { Logger } from '@kbn/logging'; import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal'; -import { +import type { SavedObjectsUpdateObjectsSpacesObject, SavedObjectsUpdateObjectsSpacesOptions, SavedObjectsUpdateObjectsSpacesResponse, @@ -30,7 +30,10 @@ import { type DecoratedError, SavedObjectsUtils, } from '@kbn/core-saved-objects-utils-server'; -import { IndexMapping, SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-internal'; +import type { + IndexMapping, + SavedObjectsSerializer, +} from '@kbn/core-saved-objects-base-server-internal'; import { SavedObject } from '@kbn/core-saved-objects-common'; import { getBulkOperationError, @@ -41,8 +44,8 @@ import { isRight, } from './internal_utils'; import { DEFAULT_REFRESH_SETTING } from './repository'; -import { RepositoryEsClient } from './repository_es_client'; -import { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; +import type { RepositoryEsClient } from './repository_es_client'; +import type { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; import { deleteLegacyUrlAliases } from './legacy_url_aliases'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts index 589f4410384e0f..e2c7107ca380c7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/kibana_migrator.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; // mock duplicated from `@kbn/core/saved-objects-migration-server-mocks` to avoid cyclic dependencies diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts index 4d033ab5a7a416..d6b7e51f78bd15 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts @@ -7,7 +7,7 @@ */ import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; -import { +import type { SavedObjectsClientContract, ISavedObjectsRepository, SavedObjectsPointInTimeFinderClient, @@ -48,7 +48,7 @@ const createPointInTimeFinderMock = ({ const createPointInTimeFinderClientMock = (): jest.Mocked => { return { find: jest.fn(), - openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }), + openPointInTimeForType: jest.fn(), closePointInTime: jest.fn(), }; }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts index c3f95442fc685b..dc6c06c0c828d1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/repository.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; // mock duplicated from `@kbn/core/saved-objects-api-server-mocks` to avoid cyclic dependencies diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts index c0521a0221f76f..38d4e75a0c528d 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { SavedObjectsBulkCreateObject, SavedObjectsBulkGetObject, SavedObjectsBulkResolveObject, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts index 555a9484f542ac..50f78f09dd684f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/saved_objects_client.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract, ISavedObjectsRepository, SavedObjectsBaseOptions, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts index 03b6372d987809..b14715db34dddc 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts @@ -7,7 +7,7 @@ */ import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; -import { +import type { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts index 32be2cbda2d62a..168f4c8de6b59c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/repository.mock.ts @@ -7,7 +7,7 @@ */ import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock'; -import { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectsRepository } from '@kbn/core-saved-objects-api-server'; const create = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts index 5d1e69e1f27940..523e5003e650ff 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_client.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { savedObjectsPointInTimeFinderMock } from './point_in_time_finder.mock'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts index 72549e364f99c1..f4308ee6254c7c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/saved_objects_extensions.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { ISavedObjectsEncryptionExtension, ISavedObjectsSecurityExtension, ISavedObjectsSpacesExtension, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts index 47a4d833af0893..73efc414a634af 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/scoped_client_provider.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISavedObjectsClientProvider } from '@kbn/core-saved-objects-api-server-internal'; +import type { ISavedObjectsClientProvider } from '@kbn/core-saved-objects-api-server-internal'; const create = (): jest.Mocked => ({ getClient: jest.fn(), diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json index 4582562d6c9bb4..3fe98195b374a7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/tsconfig.json @@ -4,6 +4,8 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", + "rootDir": ".", + "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts index 5d149ea6fa9183..eb80df8d96d440 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/base.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; /** * Base options used by most of the savedObject APIs. diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts index 129cc9c605d10e..7a38a909155ffb 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_create.ts @@ -6,7 +6,10 @@ * Side Public License, v 1. */ -import { SavedObjectReference, SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import type { + SavedObjectReference, + SavedObjectsMigrationVersion, +} from '@kbn/core-saved-objects-common'; /** * Object parameters for the bulk create operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts index 717a042bd97324..5b390cca73d14c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_delete.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectError } from '@kbn/core-saved-objects-common'; -import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import type { SavedObjectError } from '@kbn/core-saved-objects-common'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Object parameters for the bulk delete operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts index dde3c3cafe8c03..ff59adee8be7f5 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsResolveResponse } from './resolve'; +import type { SavedObjectsResolveResponse } from './resolve'; /** * Object parameters for the bulk resolve operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts index 3ee341902a87fa..6d10aee397b2f1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/bulk_update.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; -import { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse } from './update'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import type { SavedObjectsUpdateOptions, SavedObjectsUpdateResponse } from './update'; /** * Object parameters for the bulk update operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts index 003ec7c5e74b3c..331b95519a91c0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/check_conflicts.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectError } from '@kbn/core-saved-objects-common'; +import type { SavedObjectError } from '@kbn/core-saved-objects-common'; /** * Object parameters for the check conficts operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts index 9e30fc6cfa40ce..8996de4474cfec 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/close_point_in_time.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsBaseOptions } from './base'; +import type { SavedObjectsBaseOptions } from './base'; /** * Options for the close point-in-time operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts index 040aaacd020646..fcd0d079961bd7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/collect_multinamespace_references.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsBaseOptions } from './base'; +import type { SavedObjectsBaseOptions } from './base'; /** * An object to collect references for. It must be a multi-namespace type (in other words, the object type must be registered with the diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts index ed352957b14ff2..78a017ed03aba1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ -import { SavedObjectsMigrationVersion, SavedObjectReference } from '@kbn/core-saved-objects-common'; -import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import type { + SavedObjectsMigrationVersion, + SavedObjectReference, +} from '@kbn/core-saved-objects-common'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Options for the saved objects create operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts index 4faef0c8fc9542..b5bd62b75cbdf3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsFindOptions, SavedObjectsFindResponse } from './find'; +import type { SavedObjectsFindOptions, SavedObjectsFindResponse } from './find'; import { ISavedObjectsRepository } from '../saved_objects_repository'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts index b8068c14346cec..36c5dab7d689ea 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts index 200390e0917f3f..17eda87e3dd70c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/delete_by_namespace.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsBaseOptions } from './base'; +import type { SavedObjectsBaseOptions } from './base'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts index 447c93f40863a7..8e754d26533c34 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/find.ts @@ -6,12 +6,12 @@ * Side Public License, v 1. */ -import { +import type { SortOrder, AggregationsAggregationContainer, Id as EsId, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; type KueryNode = any; diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts index 0108e0ba18ecec..17234d51a6fceb 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/increment_counter.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; -import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Options for the increment counter operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts index bfdc69b8266c4d..4e5be5fda67f26 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/remove_references_to.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsBaseOptions } from './base'; +import type { SavedObjectsBaseOptions } from './base'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts index 7e4d2789ea10d5..9e764053392a5e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/resolve.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; /** * diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts index ed4b79c9c1e0db..db494d9d8d7a7b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectReference, SavedObject } from '@kbn/core-saved-objects-common'; -import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import type { SavedObjectReference, SavedObject } from '@kbn/core-saved-objects-common'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * Options for the saved objects update operation diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts index fcda3992f695b6..a249ef50f418f0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/update_objects_spaces.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectError } from '@kbn/core-saved-objects-common'; -import { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; +import type { SavedObjectError } from '@kbn/core-saved-objects-common'; +import type { MutatingOperationRefreshSetting, SavedObjectsBaseOptions } from './base'; /** * An object that should have its spaces updated. diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts index 8c939cc8bc2978..9fd83a603cb6b8 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsClosePointInTimeOptions, diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts index fc3fbf5437d138..6181cd51a4e695 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_repository.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsBaseOptions, SavedObjectsFindOptions, SavedObjectsClosePointInTimeOptions, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts index 54a51b34bca85d..0cf1cc26eda393 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; -import { IndexMapping } from '../types'; +import type { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; +import type { IndexMapping } from '../types'; import { getProperty } from './get_property'; const MAPPINGS = { diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts index 57bdb5f9cbb886..ac1070741af711 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts @@ -7,7 +7,7 @@ */ import { toPath } from 'lodash'; -import { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; import { IndexMapping } from '../types'; function getPropertyMappingFromObjectMapping( diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts index 0d805851560fad..fb5a7666b9071a 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { SavedObjectsFieldMapping, SavedObjectsMappingProperties, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts index 241bcf77be714f..c93abc2064fb65 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { SavedObjectsTypeMappingDefinition, SavedObjectsMappingProperties, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts index d0775b690dc6a3..bb078135c8bcc2 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/migration/kibana_migrator.ts @@ -7,8 +7,8 @@ */ import { Observable } from 'rxjs'; -import { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; -import { IndexMapping } from '../mappings'; +import type { SavedObjectUnsanitizedDoc } from '@kbn/core-saved-objects-server'; +import type { IndexMapping } from '../mappings'; /** @internal */ export interface IKibanaMigrator { 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 8708235a9c37ee..c6a506d2b4dd4d 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 @@ -8,7 +8,7 @@ import { valid } from 'semver'; import { schema, TypeOf } from '@kbn/config-schema'; -import { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; +import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal'; const migrationSchema = schema.object({ batchSize: schema.number({ defaultValue: 1_000 }), diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts index 4113c8d7d8b066..b7c4a0102ffd59 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from './saved_objects_type_registry'; const createType = (type: Partial): SavedObjectsType => ({ diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts index f7b63390a8af33..277050b7a6d6d8 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/saved_objects_type_registry.ts @@ -7,7 +7,7 @@ */ import { deepFreeze } from '@kbn/std'; -import { SavedObjectsType, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsType, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; /** * Core internal implementation of {@link ISavedObjectTypeRegistry}. diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts index c084d3628df9b9..dae97802578cad 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.test.ts @@ -7,7 +7,7 @@ */ import _ from 'lodash'; -import { SavedObjectsRawDoc, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsRawDoc, ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { SavedObjectsSerializer } from './serializer'; import { encodeVersion } from '../version'; import { LEGACY_URL_ALIAS_TYPE } from '../legacy_alias'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts index 739c87d6a643c0..f1e713b8741b59 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/serialization/serializer.ts @@ -7,7 +7,7 @@ */ import typeDetect from 'type-detect'; -import { +import type { ISavedObjectTypeRegistry, ISavedObjectsSerializer, SavedObjectsRawDoc, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts index 5ff9d0b8b3a7db..f3005d3c0e77af 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/utils/get_index_for_type.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; interface GetIndexForTypeOptions { type: string; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts index 8767a693999ad0..42d33c67587ada 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.test.ts @@ -7,7 +7,10 @@ */ import { schema } from '@kbn/config-schema'; -import { SavedObjectsValidationMap, SavedObjectSanitizedDoc } from '@kbn/core-saved-objects-server'; +import type { + SavedObjectsValidationMap, + SavedObjectSanitizedDoc, +} from '@kbn/core-saved-objects-server'; import { createSavedObjectSanitizedDocSchema } from './schema'; describe('Saved Objects type validation schema', () => { diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts index 9e8b1626d6e714..8b745caae85de5 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/schema.ts @@ -7,7 +7,7 @@ */ import { schema, type Type } from '@kbn/config-schema'; -import { +import type { SavedObjectsValidationSpec, SavedObjectSanitizedDoc, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts index 8481364de0291d..96bc93be54c1ac 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.test.ts @@ -8,7 +8,10 @@ import { schema } from '@kbn/config-schema'; import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; -import { SavedObjectSanitizedDoc, SavedObjectsValidationMap } from '@kbn/core-saved-objects-server'; +import type { + SavedObjectSanitizedDoc, + SavedObjectsValidationMap, +} from '@kbn/core-saved-objects-server'; import { SavedObjectsTypeValidator } from './validator'; describe('Saved Objects type validator', () => { diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts index dd9f34660e550d..13cff1621512a4 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/validation/validator.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ -import { Logger } from '@kbn/logging'; -import { SavedObjectsValidationMap, SavedObjectSanitizedDoc } from '@kbn/core-saved-objects-server'; +import type { Logger } from '@kbn/logging'; +import type { + SavedObjectsValidationMap, + SavedObjectSanitizedDoc, +} from '@kbn/core-saved-objects-server'; import { createSavedObjectSanitizedDocSchema } from './schema'; /** diff --git a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts index d8c2b60e42c90d..8f792f177b5fc6 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/saved_objects_type_registry.mock.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; const createRegistryMock = (): jest.Mocked< ISavedObjectTypeRegistry & Pick diff --git a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts index 2015bed2536490..6bdac2e20c1f94 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-mocks/src/serializer.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectsSerializer } from '@kbn/core-saved-objects-server'; const createSerializerMock = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts index 3115323f2623ba..1fd111186f5512 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_client.ts @@ -7,9 +7,9 @@ */ import { pick, throttle, cloneDeep } from 'lodash'; -import { HttpSetup, HttpFetchOptions } from '@kbn/core-http-browser'; -import { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import { +import type { HttpSetup, HttpFetchOptions } from '@kbn/core-http-browser'; +import type { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsBulkResolveResponse as SavedObjectsBulkResolveResponseServer, SavedObjectsBulkDeleteResponse as SavedObjectsBulkDeleteResponseServer, SavedObjectsClientContract as SavedObjectsApi, @@ -17,7 +17,7 @@ import { SavedObjectsResolveResponse, SavedObjectsBulkDeleteOptions, } from '@kbn/core-saved-objects-api-server'; -import { +import type { SavedObjectsClientContract, SavedObjectsCreateOptions, SavedObjectsDeleteOptions, diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts index 5228798a918453..111d98bfcc126d 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/saved_objects_service.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { CoreService } from '@kbn/core-base-browser-internal'; -import { HttpStart } from '@kbn/core-http-browser'; -import { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; +import type { CoreService } from '@kbn/core-base-browser-internal'; +import type { HttpStart } from '@kbn/core-http-browser'; +import type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; import { SavedObjectsClient } from './saved_objects_client'; export class SavedObjectsService implements CoreService { diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts index 5ca8d6addd6f3d..7ffe708fd1e125 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; import { SimpleSavedObjectImpl as SimpleSavedObject } from './simple_saved_object'; describe('SimpleSavedObjectImpl', () => { diff --git a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts index f86dffb849514a..414ed5bbf18469 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-internal/src/simple_saved_object.ts @@ -8,8 +8,11 @@ import { set } from '@kbn/safer-lodash-set'; import { get, has } from 'lodash'; -import { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract, SimpleSavedObject } from '@kbn/core-saved-objects-api-browser'; +import type { SavedObject as SavedObjectType } from '@kbn/core-saved-objects-common'; +import type { + SavedObjectsClientContract, + SimpleSavedObject, +} from '@kbn/core-saved-objects-api-browser'; /** * Core internal implementation of {@link SimpleSavedObject} diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts index c4788fee96cd74..2239b94d7e2ebd 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/saved_objects_service.mock.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { PublicMethodsOf } from '@kbn/utility-types'; -import { SavedObjectsService } from '@kbn/core-saved-objects-browser-internal'; -import { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; +import type { PublicMethodsOf } from '@kbn/utility-types'; +import type { SavedObjectsService } from '@kbn/core-saved-objects-browser-internal'; +import type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; type SavedObjectsServiceContract = PublicMethodsOf; diff --git a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts index 3e75014180ed60..2e3c30ac17d9ce 100644 --- a/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-browser-mocks/src/simple_saved_object.mock.ts @@ -6,8 +6,11 @@ * Side Public License, v 1. */ -import { SavedObjectsClientContract, SimpleSavedObject } from '@kbn/core-saved-objects-api-browser'; -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { + SavedObjectsClientContract, + SimpleSavedObject, +} from '@kbn/core-saved-objects-api-browser'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; type T = unknown; diff --git a/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts index 8f1743173bec2f..c372f3169ed80d 100644 --- a/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-browser/src/contracts.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-browser'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts index 2d92f4665a236c..daa13393d962eb 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.test.ts @@ -8,8 +8,8 @@ import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal'; import { httpServerMock } from '@kbn/core-http-server-mocks'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SavedObjectsExportTransform } from '@kbn/core-saved-objects-server'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsExportTransform } from '@kbn/core-saved-objects-server'; import { applyExportTransforms } from './apply_export_transforms'; const createObj = ( diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts index 4ee6fcb21aa942..0c871327136f96 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { KibanaRequest } from '@kbn/core-http-server'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsExportTransform, SavedObjectsExportTransformContext, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts index f644f029c60a59..04ea3f984b19b6 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts @@ -7,8 +7,8 @@ */ import { httpServerMock } from '@kbn/core-http-server-mocks'; -import { SavedObject, SavedObjectError } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObject, SavedObjectError } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsExportTransform, SavedObjectsExportablePredicate, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts index 3f4414be8e18dc..6d9adeb8a0d942 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { Logger } from '@kbn/logging'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { +import type { Logger } from '@kbn/logging'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsExportablePredicate, ISavedObjectTypeRegistry, SavedObjectsExportTransform, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts index eda08fa53f2e9d..6ca23c358f9314 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/errors.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts index 065aab92e8ea50..2674b5a62e14a8 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts @@ -7,7 +7,7 @@ */ import { httpServerMock } from '@kbn/core-http-server-mocks'; -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts index cf091ca215c033..c3213eb9108bd0 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts @@ -6,15 +6,15 @@ * Side Public License, v 1. */ -import { Readable } from 'stream'; +import type { Readable } from 'stream'; import { createListStream } from '@kbn/utils'; -import { Logger } from '@kbn/logging'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { +import type { Logger } from '@kbn/logging'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract, SavedObjectsFindResult, } from '@kbn/core-saved-objects-api-server'; -import { +import type { ISavedObjectsExporter, ISavedObjectTypeRegistry, SavedObjectsExportResultDetails, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts index b04ae8abf6638c..27fbb09a370184 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.test.ts @@ -8,7 +8,7 @@ import { range } from 'lodash'; import { sortObjects } from './sort_objects'; -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; describe('sortObjects()', () => { test('should return on empty array', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts index 95743d7084ee90..551ba3989e5271 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/sort_objects.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; const getId = (object: { type: string; id: string }) => `${object.type}:${object.id}`; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts index d1d5091f5ea51e..6088217bd92018 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; import { byIdAscComparator, getPreservedOrderComparator } from './utils'; const createObj = (id: string): SavedObject => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts index ffe0bd3b05b948..dbace21e98ca63 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/utils.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; export type SavedObjectComparator = (a: SavedObject, b: SavedObject) => number; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts index f41e0fc2181254..c0350c7eac7f82 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/errors.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts index c982b77e0a4927..82e5aa4a5d77f1 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.mock.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { collectSavedObjects } from './lib/collect_saved_objects'; -import { checkReferenceOrigins } from './lib/check_reference_origins'; -import { regenerateIds } from './lib/regenerate_ids'; -import { validateReferences } from './lib/validate_references'; -import { checkConflicts } from './lib/check_conflicts'; -import { checkOriginConflicts } from './lib/check_origin_conflicts'; -import { createSavedObjects } from './lib/create_saved_objects'; -import { executeImportHooks } from './lib/execute_import_hooks'; +import type { collectSavedObjects } from './lib/collect_saved_objects'; +import type { checkReferenceOrigins } from './lib/check_reference_origins'; +import type { regenerateIds } from './lib/regenerate_ids'; +import type { validateReferences } from './lib/validate_references'; +import type { checkConflicts } from './lib/check_conflicts'; +import type { checkOriginConflicts } from './lib/check_origin_conflicts'; +import type { createSavedObjects } from './lib/create_saved_objects'; +import type { executeImportHooks } from './lib/execute_import_hooks'; export const mockCollectSavedObjects = jest.fn() as jest.MockedFunction; jest.mock('./lib/collect_saved_objects', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts index 14fba90efe1258..b44020e1774bef 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts @@ -19,13 +19,13 @@ import { import { Readable } from 'stream'; import { v4 as uuidv4 } from 'uuid'; -import { +import type { SavedObject, SavedObjectsImportFailure, SavedObjectsImportWarning, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsType, ISavedObjectTypeRegistry, SavedObjectsImportHook, @@ -33,7 +33,7 @@ import { import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { importSavedObjectsFromStream, ImportSavedObjectsOptions } from './import_saved_objects'; -import { ImportStateMap } from './lib'; +import type { ImportStateMap } from './lib'; describe('#importSavedObjectsFromStream', () => { beforeEach(() => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts index 7a75c05c463799..86379b4dfde2f1 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.ts @@ -7,12 +7,15 @@ */ import { Readable } from 'stream'; -import { +import type { SavedObjectsImportFailure, SavedObjectsImportResponse, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectTypeRegistry, SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { + ISavedObjectTypeRegistry, + SavedObjectsImportHook, +} from '@kbn/core-saved-objects-server'; import { checkReferenceOrigins, validateReferences, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts index 46c026c17158ae..8c58af772b8828 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.test.ts @@ -7,12 +7,12 @@ */ import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; -import { +import type { SavedObject, SavedObjectReference, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { checkConflicts } from './check_conflicts'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts index 4b0c1e0dfffa68..f4f6082ef18abd 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_conflicts.ts @@ -7,14 +7,14 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { +import type { SavedObject, SavedObjectsImportFailure, SavedObjectError, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ImportStateMap } from './types'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { ImportStateMap } from './types'; interface CheckConflictsParams { objects: Array>; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts index 6c1b21ccf3fbb7..8fb5704af9d821 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { createOriginQuery } from './utils'; +import type { createOriginQuery } from './utils'; export const mockCreateOriginQuery = jest.fn() as jest.MockedFunction; jest.mock('./utils', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts index c198c826092cea..225db282b31eef 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.test.ts @@ -8,18 +8,18 @@ import { mockCreateOriginQuery } from './check_reference_origins.test.mock'; -import { +import type { SavedObjectReference, SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { checkOriginConflicts } from './check_origin_conflicts'; -import { ImportStateMap } from './types'; +import type { ImportStateMap } from './types'; jest.mock('uuid', () => ({ v4: () => 'uuidv4', diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts index a6e7907622d667..079c5c22191ed0 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_origin_conflicts.ts @@ -8,15 +8,15 @@ import pMap from 'p-map'; import { v4 as uuidv4 } from 'uuid'; -import { +import type { SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { getObjectKey } from '@kbn/core-saved-objects-base-server-internal'; -import { ImportStateMap } from './types'; +import type { ImportStateMap } from './types'; import { createOriginQuery } from './utils'; interface CheckOriginConflictsParams { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts index 6c1b21ccf3fbb7..8fb5704af9d821 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { createOriginQuery } from './utils'; +import type { createOriginQuery } from './utils'; export const mockCreateOriginQuery = jest.fn() as jest.MockedFunction; jest.mock('./utils', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts index dc191d8774d230..37a9aa96dd52fe 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts @@ -8,15 +8,15 @@ import { mockCreateOriginQuery } from './check_reference_origins.test.mock'; -import { +import type { SavedObjectsFindResult, SavedObjectsClientContract, } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { checkReferenceOrigins, CheckReferenceOriginsParams } from './check_reference_origins'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import { ImportStateMap } from './types'; +import type { ImportStateMap } from './types'; const MULTI_NS_TYPE = 'multi'; const OTHER_TYPE = 'other'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts index 5261fdc29a5de3..65a0f9fd432f21 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.ts @@ -7,10 +7,10 @@ */ import pMap from 'p-map'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { getObjectKey, parseObjectKey } from '@kbn/core-saved-objects-base-server-internal'; -import { ImportStateMap, ImportStateValue } from './types'; +import type { ImportStateMap, ImportStateValue } from './types'; import { createOriginQuery } from './utils'; export interface CheckReferenceOriginsParams { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts index 4e359643164423..e86ff1f70794db 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/collect_saved_objects.ts @@ -14,11 +14,11 @@ import { createPromiseFromStreams, } from '@kbn/utils'; -import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; import { SavedObjectsImportError } from '../errors'; import { getNonUniqueEntries } from './get_non_unique_entries'; import { createLimitStream } from './create_limit_stream'; -import { ImportStateMap } from './types'; +import type { ImportStateMap } from './types'; interface CollectSavedObjectsOptions { readStream: Readable; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts index 870d657ff7e211..4b830e80fa6cb7 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_objects_filter.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; export function createObjectsFilter(retries: SavedObjectsImportRetry[]) { const retryKeys = new Set(retries.map((retry) => `${retry.type}:${retry.id}`)); diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts index 790a2a0f1309a9..907532bcc8fbbe 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts @@ -7,7 +7,7 @@ */ import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; -import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { createSavedObjects } from './create_saved_objects'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts index 36bf5d06683948..31caf8a130cae2 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { CreatedObject } from '@kbn/core-saved-objects-server'; +import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { CreatedObject } from '@kbn/core-saved-objects-server'; import { extractErrors } from './extract_errors'; -import { ImportStateMap } from './types'; +import type { ImportStateMap } from './types'; export interface CreateSavedObjectsParams { objects: Array>; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts index 334bf69690d00a..bebe27492d41a8 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; -import { SavedObjectsImportHookResult } from '@kbn/core-saved-objects-server'; +import type { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsImportHookResult } from '@kbn/core-saved-objects-server'; import { executeImportHooks } from './execute_import_hooks'; const createObject = (type: string, id: string): SavedObject => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts index ca7239d6a9166e..d2e3063b1c49f1 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/execute_import_hooks.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; -import { SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; +import type { SavedObject, SavedObjectsImportWarning } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; export interface ExecuteImportHooksOptions { objects: SavedObject[]; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts index 6e0dc2b5b86496..c4d3d0e4c27220 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.test.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { CreatedObject } from '@kbn/core-saved-objects-server'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { CreatedObject } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { extractErrors } from './extract_errors'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts index 7bc2ebf4ab9acf..97eca4a5d57beb 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/extract_errors.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; -import { CreatedObject } from '@kbn/core-saved-objects-server'; +import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; +import type { CreatedObject } from '@kbn/core-saved-objects-server'; export function extractErrors( // TODO: define saved object type diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts index 44fd21e7273cc5..d598326afa1366 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; import { getImportStateMapForRetries } from './get_import_state_map_for_retries'; describe('#getImportStateMapForRetries', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts index 7b3c8f422e0fda..dd4ed3036fb8c4 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/get_import_state_map_for_retries.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; -import { ImportStateMap } from './types'; +import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import type { ImportStateMap } from './types'; interface GetImportStateMapForRetriesParams { objects: SavedObject[]; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts index 8b3c8bd60f64db..f8a8c502af38a3 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; import { regenerateIds } from './regenerate_ids'; jest.mock('uuid', () => ({ diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts index 066c2c2fdc5cee..1d895c8c7dabf1 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/regenerate_ids.ts @@ -7,8 +7,8 @@ */ import { v4 as uuidv4 } from 'uuid'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { ImportStateMap } from './types'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { ImportStateMap } from './types'; /** * Takes an array of saved objects and returns an importStateMap of randomly-generated new IDs. diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts index e36777c11a5872..818b6e512dcc24 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/split_overwrites.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import type { SavedObject, SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; export function splitOverwrites( savedObjects: Array>, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts index 3f0f4dc5196f6e..ceced695b28708 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.test.ts @@ -7,7 +7,7 @@ */ import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; -import { ValidateReferencesParams } from './validate_references'; +import type { ValidateReferencesParams } from './validate_references'; import { validateReferences } from './validate_references'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts index 6b8061309a243a..8f271908bf48d5 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_references.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { +import type { SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsImportError } from '../errors'; -import { ImportStateMap } from './types'; +import type { ImportStateMap } from './types'; const REF_TYPES_TO_VALIDATE = ['index-pattern', 'search']; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts index 6828762087777c..984068b59c575e 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; import { validateRetries } from './validate_retries'; import { SavedObjectsImportError } from '../errors'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts index 3f3711658258dc..745bb9fd423544 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/validate_retries.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsImportRetry } from '@kbn/core-saved-objects-common'; import { getNonUniqueEntries } from './get_non_unique_entries'; import { SavedObjectsImportError } from '../errors'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts index 6a75540b909622..21eadaaf2231a7 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.mock.ts @@ -6,18 +6,18 @@ * Side Public License, v 1. */ -import { checkReferenceOrigins } from './lib/check_reference_origins'; -import { validateRetries } from './lib/validate_retries'; -import { createObjectsFilter } from './lib/create_objects_filter'; -import { collectSavedObjects } from './lib/collect_saved_objects'; -import { regenerateIds } from './lib/regenerate_ids'; -import { validateReferences } from './lib/validate_references'; -import { checkConflicts } from './lib/check_conflicts'; -import { checkOriginConflicts } from './lib/check_origin_conflicts'; -import { getImportStateMapForRetries } from './lib/get_import_state_map_for_retries'; -import { splitOverwrites } from './lib/split_overwrites'; -import { createSavedObjects } from './lib/create_saved_objects'; -import { executeImportHooks } from './lib/execute_import_hooks'; +import type { checkReferenceOrigins } from './lib/check_reference_origins'; +import type { validateRetries } from './lib/validate_retries'; +import type { createObjectsFilter } from './lib/create_objects_filter'; +import type { collectSavedObjects } from './lib/collect_saved_objects'; +import type { regenerateIds } from './lib/regenerate_ids'; +import type { validateReferences } from './lib/validate_references'; +import type { checkConflicts } from './lib/check_conflicts'; +import type { checkOriginConflicts } from './lib/check_origin_conflicts'; +import type { getImportStateMapForRetries } from './lib/get_import_state_map_for_retries'; +import type { splitOverwrites } from './lib/split_overwrites'; +import type { createSavedObjects } from './lib/create_saved_objects'; +import type { executeImportHooks } from './lib/execute_import_hooks'; export const mockCheckReferenceOrigins = jest.fn() as jest.MockedFunction< typeof checkReferenceOrigins diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts index e6ded82be2fc05..ed93301a458133 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts @@ -23,15 +23,15 @@ import { import { Readable } from 'stream'; import { v4 as uuidv4 } from 'uuid'; -import { +import type { SavedObject, SavedObjectsImportFailure, SavedObjectsImportRetry, SavedObjectReference, SavedObjectsImportWarning, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsType, ISavedObjectTypeRegistry, SavedObjectsImportHook, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts index 3b4172041601be..9d96126265fd1a 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts @@ -7,15 +7,18 @@ */ import { Readable } from 'stream'; -import { +import type { SavedObject, SavedObjectsImportRetry, SavedObjectsImportFailure, SavedObjectsImportResponse, SavedObjectsImportSuccess, } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectTypeRegistry, SavedObjectsImportHook } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { + ISavedObjectTypeRegistry, + SavedObjectsImportHook, +} from '@kbn/core-saved-objects-server'; import { collectSavedObjects, createObjectsFilter, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts index ab2628c26dbc0e..27251b0af2e2a9 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/saved_objects_importer.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { SavedObjectsImportResponse } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { +import type { SavedObjectsImportResponse } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectTypeRegistry, ISavedObjectsImporter, SavedObjectsImportOptions, diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json index 4582562d6c9bb4..3fe98195b374a7 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/tsconfig.json @@ -4,6 +4,8 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", + "rootDir": ".", + "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts index 91217b1b765bfa..520ebce27323a8 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_exporter.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISavedObjectsExporter } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectsExporter } from '@kbn/core-saved-objects-server'; const createExporterMock = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts index 8367822ef90225..22df181d28db57 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-mocks/src/saved_objects_importer.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISavedObjectsImporter } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectsImporter } from '@kbn/core-saved-objects-server'; const createImporterMock = () => { const mock: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts index f24d2c04a0b5b3..39194625e4531f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts @@ -8,17 +8,17 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { errors as esErrors } from '@elastic/elasticsearch'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { isWriteBlockException, isIndexNotFoundException } from './es_errors'; import { WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE } from './constants'; -import { TargetIndexHadWriteBlock, RequestEntityTooLargeException, IndexNotFound } from '.'; +import type { TargetIndexHadWriteBlock, RequestEntityTooLargeException, IndexNotFound } from '.'; /** * Given a document and index, creates a valid body for the Bulk API. diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts index 0b90e7f00a9ea6..d0cf8f85fc4970 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/calculate_exclude_filters.ts @@ -6,13 +6,13 @@ * Side Public License, v 1. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { withTimeout } from '@kbn/std'; import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectTypeExcludeFromUpgradeFilterHook } from '@kbn/core-saved-objects-server'; -import { RetryableEsClientError } from '.'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectTypeExcludeFromUpgradeFilterHook } from '@kbn/core-saved-objects-server'; +import type { RetryableEsClientError } from '.'; import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; export interface CalculateExcludeFiltersParams { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts index 02282768cf8b4a..465d4c8aa7ee84 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.test.ts @@ -9,7 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import { catchRetryableEsClientErrors } from './catch_retryable_es_client_errors'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import { checkForUnknownDocs } from './check_for_unknown_docs'; import { createAggregateTypesSearchResponse } from './check_for_unknown_docs.mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts index 4278f59a66fa33..74dc39bc6fcd41 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/check_for_unknown_docs.ts @@ -9,14 +9,14 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { flatten } from 'lodash'; -import { +import type { AggregationsMultiBucketAggregateBase, Indices, QueryDslQueryContainer, SearchRequest, } from '@elastic/elasticsearch/lib/api/types'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, type RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts index 787260137fcb74..0e07a68c1ec39c 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts @@ -10,12 +10,12 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { pipe } from 'fp-ts/lib/pipeable'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, } from './catch_retryable_es_client_errors'; -import { IndexNotFound, AcknowledgeResponse } from '.'; +import type { IndexNotFound, AcknowledgeResponse } from '.'; import { type IndexNotGreenTimeout, waitForIndexStatus } from './wait_for_index_status'; import { DEFAULT_TIMEOUT, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts index ff0fd172594d3a..a3ea50edccae67 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts index 36e532352d5a83..9353d28e9ffd80 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts @@ -10,8 +10,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { pipe } from 'fp-ts/lib/pipeable'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { AcknowledgeResponse } from '.'; import { catchRetryableEsClientErrors, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts index 957dd92ff26d71..2bf432de032a67 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/es_errors.ts @@ -5,7 +5,7 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; export const isWriteBlockException = (errorCause?: estypes.ErrorCause): boolean => { return ( diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts index 13f629f5c0a6cc..0eb43380a6990e 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts @@ -8,8 +8,8 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Either from 'fp-ts/lib/Either'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index 500294003a9628..2b6d501f787b09 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -66,11 +66,11 @@ export { refreshIndex } from './refresh_index'; export type { ReindexResponse, ReindexParams } from './reindex'; export { reindex } from './reindex'; -import { IncompatibleMappingException } from './wait_for_reindex_task'; +import type { IncompatibleMappingException } from './wait_for_reindex_task'; export { waitForReindexTask } from './wait_for_reindex_task'; -import { AliasNotFound, RemoveIndexNotAConcreteIndex } from './update_aliases'; +import type { AliasNotFound, RemoveIndexNotAConcreteIndex } from './update_aliases'; export type { AliasAction, UpdateAliasesParams } from './update_aliases'; export { updateAliases } from './update_aliases'; @@ -84,8 +84,8 @@ export type { } from './update_and_pickup_mappings'; export { updateAndPickupMappings } from './update_and_pickup_mappings'; -import { UnknownDocsFound } from './check_for_unknown_docs'; -import { IncompatibleClusterRoutingAllocation } from './initialize_action'; +import type { UnknownDocsFound } from './check_for_unknown_docs'; +import type { IncompatibleClusterRoutingAllocation } from './initialize_action'; import { ClusterShardLimitExceeded } from './create_index'; export type { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts index 37fc25416ae645..a1b5e013600187 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts @@ -9,7 +9,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Either from 'fp-ts/lib/Either'; import { pipe } from 'fp-ts/lib/pipeable'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts index 013c0879b7abf8..3966198393c219 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts index 270cb6ad768de0..9cb37facc0b5a2 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts index 16aed0c3c24c6c..19e9ed2072c2bb 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts index 479c8617253b14..7eb5c5d560d4cc 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts @@ -7,7 +7,7 @@ */ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts index 279ff51ccb9905..594822f7247602 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/reindex.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, type RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts index 6807438860a694..35a4dff315277e 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts index 5a77aaaae594bd..ac5508a1400384 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts index f46e170fc3bfbb..a84f976ceb6434 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts @@ -9,7 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts index b142aab0094032..52e79e6ac88c20 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts @@ -7,8 +7,8 @@ */ import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import { TransformRawDocs } from '../types'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { TransformRawDocs } from '../types'; import { DocumentsTransformFailed, DocumentsTransformSuccess } from '../core/migrate_raw_docs'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts index 2b41e2cb4d4729..c896dbf27258f2 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts @@ -9,7 +9,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts index 8bc99d2d4d1252..36a00f1096b1da 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts @@ -9,8 +9,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import { pipe } from 'fp-ts/lib/pipeable'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts index 1568d94b8c379a..7ee63e75838511 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts @@ -8,7 +8,7 @@ import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts index dea8b8b81bc2d7..3eb7b0da718c2b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts @@ -10,7 +10,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; import { flow } from 'fp-ts/lib/function'; import { RetryableEsClientError } from './catch_retryable_es_client_errors'; -import { IndexNotFound, TargetIndexHadWriteBlock } from '.'; +import type { IndexNotFound, TargetIndexHadWriteBlock } from '.'; import { waitForTask, WaitForTaskCompletionTimeout } from './wait_for_task'; import { isWriteBlockException, isIncompatibleMappingException } from './es_errors'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts index 6410960550b8b5..a5762ff10a122e 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts @@ -5,12 +5,12 @@ * in compliance with, at your election, the Elastic License 2.0 or the Server * Side Public License, v 1. */ -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import * as Either from 'fp-ts/lib/Either'; import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, RetryableEsClientError, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts index 1ce1b422331491..7f1542ffc6008a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { IndexMapping, SavedObjectsTypeMappingDefinitions, } from '@kbn/core-saved-objects-base-server-internal'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts index 5f18184a09c35f..f14de6bc72ee02 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_active_mappings.ts @@ -12,8 +12,8 @@ import crypto from 'crypto'; import { cloneDeep, mapValues } from 'lodash'; -import { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; -import { +import type { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; +import type { IndexMapping, SavedObjectsTypeMappingDefinitions, } from '@kbn/core-saved-objects-base-server-internal'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts index ac7b612dedd4fd..ec82b0ac2c92f8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { createIndexMap } from './build_index_map'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts index 89e23e431185a9..225b3bb422925a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/build_index_map.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { SavedObjectsTypeMappingDefinitions } from '@kbn/core-saved-objects-base-server-internal'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsTypeMappingDefinitions } from '@kbn/core-saved-objects-base-server-internal'; export interface CreateIndexMapOptions { kibanaIndexName: string; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts index 6edf15340a95c7..8b1eccbe09d783 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/disable_unknown_type_mapping_fields.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { SavedObjectsMappingProperties } from '@kbn/core-saved-objects-server'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; /** * Merges the active mappings and the source mappings while disabling the diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts index e5274e46ec23aa..a0dd1cfddc3a48 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.test.ts @@ -9,7 +9,7 @@ import { mockGetConvertedObjectId } from './document_migrator.test.mock'; import { set } from '@kbn/safer-lodash-set'; import _ from 'lodash'; -import { SavedObjectUnsanitizedDoc, SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { SavedObjectUnsanitizedDoc, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry, LEGACY_URL_ALIAS_TYPE, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts index bcf1e0a8d2c9c3..1040699a99e9df 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/document_migrator.ts @@ -46,12 +46,12 @@ import uuidv5 from 'uuid/v5'; import { set } from '@kbn/safer-lodash-set'; import _ from 'lodash'; import Semver from 'semver'; -import { Logger } from '@kbn/logging'; -import { +import type { Logger } from '@kbn/logging'; +import type { SavedObjectsMigrationVersion, SavedObjectsNamespaceType, } from '@kbn/core-saved-objects-common'; -import { +import type { SavedObjectUnsanitizedDoc, SavedObjectsType, ISavedObjectTypeRegistry, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts index 5f81cc65627496..d1ca65f1dd1034 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migrate_raw_docs.ts @@ -11,7 +11,7 @@ */ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Either from 'fp-ts/lib/Either'; -import { +import type { SavedObjectSanitizedDoc, SavedObjectsRawDoc, SavedObjectUnsanitizedDoc, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts index 1b5f232ce88ccb..c2ae13b70cbddd 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/migration_logger.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { Logger, LogMeta } from '@kbn/logging'; -import { SavedObjectsMigrationLogger } from '@kbn/core-saved-objects-server'; +import type { Logger, LogMeta } from '@kbn/logging'; +import type { SavedObjectsMigrationLogger } from '@kbn/core-saved-objects-server'; /* * This file provides a helper class for ensuring that all logging diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts index 67f1f83644308c..7dbcf2c270ba47 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/core/unused_types.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; /** * Types that are no longer registered and need to be removed diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts index d42a93d3eea0e8..d1c19aa7d212ef 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.test.ts @@ -8,7 +8,7 @@ import { ByteSizeValue } from '@kbn/config-schema'; import * as Option from 'fp-ts/Option'; -import { DocLinksServiceSetup } from '@kbn/core-doc-links-server'; +import type { DocLinksServiceSetup } from '@kbn/core-doc-links-server'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { type SavedObjectsMigrationConfigType, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts index 00a008a3897073..1843227934bcd0 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/initial_state.ts @@ -7,15 +7,15 @@ */ import * as Option from 'fp-ts/Option'; -import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import { Logger } from '@kbn/logging'; -import { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import type { Logger } from '@kbn/logging'; +import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { IndexMapping, SavedObjectsMigrationConfigType, } from '@kbn/core-saved-objects-base-server-internal'; -import { InitState } from './state'; +import type { InitState } from './state'; import { excludeUnusedTypesQuery } from './core'; /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index 9e6420177bc7ec..5d1cb32eca6d22 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -7,11 +7,11 @@ */ import { take } from 'rxjs/operators'; -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { DocumentMigrator } from './core/document_migrator'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts index 4bb2a5462f0cd2..1d8d766d8ac88a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts @@ -13,11 +13,11 @@ import { BehaviorSubject } from 'rxjs'; import Semver from 'semver'; -import { Logger } from '@kbn/logging'; -import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import { +import type { Logger } from '@kbn/logging'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { SavedObjectUnsanitizedDoc, SavedObjectsRawDoc, ISavedObjectTypeRegistry, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts index d0828138c36a7b..f21651d82f8f9f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts @@ -8,13 +8,13 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import * as Option from 'fp-ts/lib/Option'; -import { Logger, LogMeta } from '@kbn/logging'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { Logger, LogMeta } from '@kbn/logging'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { getErrorMessage, getRequestDebugMeta, } from '@kbn/core-elasticsearch-client-server-internal'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { Model, Next, stateActionMachine } from './state_action_machine'; import { cleanup } from './migrations_state_machine_cleanup'; import { ReindexSourceToTempTransform, ReindexSourceToTempIndexBulk, State } from './state'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts index 98eb4b35716182..8b1f93c327857b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_machine_cleanup.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import * as Actions from './actions'; -import { State } from './state'; +import type { State } from './state'; export async function cleanup(client: ElasticsearchClient, state?: State) { if (!state) return; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts index 551c5336f78bfe..ec9afc31f90d1b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ import * as Either from 'fp-ts/lib/Either'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { createBatches } from './create_batches'; describe('createBatches', () => { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts index 1f81fa98902675..a591505f542c72 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/create_batches.ts @@ -7,7 +7,7 @@ */ import * as Either from 'fp-ts/lib/Either'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { createBulkOperationBody } from '../actions/bulk_overwrite_transformed_documents'; /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts index 9e26fd89390e29..9d00cb41cc07ca 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/extract_errors.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { TransformErrorObjects } from '../core'; -import { DocumentIdAndType } from '../actions'; +import type { TransformErrorObjects } from '../core'; +import type { DocumentIdAndType } from '../actions'; /** * Constructs migration failure message strings from corrupt document ids and document transformation errors diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts index b5eed210b63e89..5f84dc01af008b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/helpers.ts @@ -7,11 +7,14 @@ */ import { gt, valid } from 'semver'; -import { QueryDslBoolQuery, QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { + QueryDslBoolQuery, + QueryDslQueryContainer, +} from '@elastic/elasticsearch/lib/api/types'; import * as Either from 'fp-ts/lib/Either'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import { State } from '../state'; -import { FetchIndexResponse } from '../actions'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { State } from '../state'; +import type { FetchIndexResponse } from '../actions'; /** * A helper function/type for ensuring that all control state's are handled. diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index aa9d37c1b24edb..67b7e40dc5affc 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -8,8 +8,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import { +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { FatalState, State, LegacySetWriteBlockState, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts index 0ef73de1674b19..8eca6d12c4f0ad 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.ts @@ -10,8 +10,8 @@ import * as Either from 'fp-ts/lib/Either'; import * as Option from 'fp-ts/lib/Option'; import { type AliasAction, isTypeof } from '../actions'; -import { AllActionStates, State } from '../state'; -import { ResponseType } from '../next'; +import type { AllActionStates, State } from '../state'; +import type { ResponseType } from '../next'; import { disableUnknownTypeMappingFields } from '../core'; import { createInitialProgress, @@ -27,7 +27,7 @@ import { extractDiscardedUnknownDocs, extractDiscardedCorruptDocs, } from './extract_errors'; -import { ExcludeRetryableEsError } from './types'; +import type { ExcludeRetryableEsError } from './types'; import { addExcludedTypesToBoolQuery, addMustClausesToBoolQuery, @@ -40,7 +40,7 @@ import { throwBadResponse, } from './helpers'; import { createBatches } from './create_batches'; -import { MigrationLog } from '../types'; +import type { MigrationLog } from '../types'; export const FATAL_REASON_REQUEST_ENTITY_TOO_LARGE = `While indexing a batch of saved objects, Elasticsearch returned a 413 Request Entity Too Large exception. Ensure that the Kibana configuration option 'migrations.maxBatchSizeBytes' is set to a value that is lower than or equal to the Elasticsearch 'http.max_content_length' configuration option.`; const CLUSTER_SHARD_LIMIT_EXCEEDED_REASON = `[cluster_shard_limit_exceeded] Upgrading Kibana requires adding a small number of new shards. Ensure that Kibana is able to add 10 more shards by increasing the cluster.max_shards_per_node setting, or removing indices to clear up resources.`; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts index 6de961a6e1b7a0..2086774dd2fb41 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { MigrationLog } from '../types'; +import type { MigrationLog } from '../types'; import { createInitialProgress, incrementProcessedProgress, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts index 5ec21ff06092ac..ef66283fcf6823 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/progress.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { MigrationLog, Progress } from '../types'; +import type { MigrationLog, Progress } from '../types'; /** * Returns an initial state of the progress object (everything undefined) diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts index 59ed9756b23a53..548c6b8c43b51b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { next } from './next'; import { State } from './state'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts index a55f828931faaf..9ac29a3a849ba7 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { AllActionStates, ReindexSourceToTempOpenPit, ReindexSourceToTempRead, @@ -41,7 +41,7 @@ import { CheckUnknownDocumentsState, CalculateExcludeFiltersState, } from './state'; -import { TransformRawDocs } from './types'; +import type { TransformRawDocs } from './types'; import * as Actions from './actions'; type ActionMap = ReturnType; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts index c88e433ee92238..47fe92ad82c54f 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/run_resilient_migrator.ts @@ -6,17 +6,17 @@ * Side Public License, v 1. */ -import { Logger } from '@kbn/logging'; -import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { +import type { Logger } from '@kbn/logging'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsMigrationVersion } from '@kbn/core-saved-objects-common'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { IndexMapping, SavedObjectsMigrationConfigType, MigrationResult, } from '@kbn/core-saved-objects-base-server-internal'; -import { TransformRawDocs } from './types'; +import type { TransformRawDocs } from './types'; import { next } from './next'; import { model } from './model'; import { createInitialState } from './initial_state'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts index a12fd7314e71aa..eb8d2e2fbb05b2 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/state.ts @@ -7,17 +7,17 @@ */ import * as Option from 'fp-ts/lib/Option'; -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; -import { DocLinks } from '@kbn/doc-links'; -import { +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types'; +import type { DocLinks } from '@kbn/doc-links'; +import type { SavedObjectsRawDoc, SavedObjectTypeExcludeFromUpgradeFilterHook, } from '@kbn/core-saved-objects-server'; -import { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import { ControlState } from './state_action_machine'; -import { AliasAction } from './actions'; -import { TransformErrorObjects } from './core'; -import { MigrationLog, Progress } from './types'; +import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; +import type { ControlState } from './state_action_machine'; +import type { AliasAction } from './actions'; +import type { TransformErrorObjects } from './core'; +import type { MigrationLog, Progress } from './types'; export interface BaseState extends ControlState { /** The first part of the index name such as `.kibana` or `.kibana_task_manager` */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts index 7d12eb2d5f5c7b..cf7c74c305f2b8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts @@ -7,7 +7,7 @@ */ import * as TaskEither from 'fp-ts/lib/TaskEither'; -import { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { DocumentsTransformFailed, DocumentsTransformSuccess } from './core'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts index f7fe210f0ef2d3..e7df2a0363a26a 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/kibana_migrator.mock.ts @@ -7,8 +7,8 @@ */ import { BehaviorSubject } from 'rxjs'; -import { SavedObjectsType } from '@kbn/core-saved-objects-server'; -import { +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { IKibanaMigrator, KibanaMigratorStatus, } from '@kbn/core-saved-objects-base-server-internal'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts index 5e4e9eec851581..6cb3cc3d1fd867 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-mocks/src/migration.mocks.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { SavedObjectMigrationContext, SavedObjectsMigrationLogger, } from '@kbn/core-saved-objects-server'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts index 91d33df14471e6..3a363ea5a4c200 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/deprecation_factory.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; -import { RegisterDeprecationsConfig } from '@kbn/core-deprecations-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import type { RegisterDeprecationsConfig } from '@kbn/core-deprecations-server'; import { getUnknownTypesDeprecations } from './unknown_object_types'; interface GetDeprecationProviderOptions { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts index c61c7b98260f1f..2199a7fb32b5e9 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.test.ts @@ -8,11 +8,11 @@ import { getIndexForTypeMock } from './unknown_object_types.test.mocks'; -import { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; +import type { SearchResponse } from '@elastic/elasticsearch/lib/api/types'; import { deleteUnknownTypeObjects, getUnknownTypesDeprecations } from './unknown_object_types'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; -import { SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; const createAggregateTypesSearchResponse = ( typesIds: Record = {} diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts index f40e8ccdec52f3..2149fd6cfaefdf 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/deprecations/unknown_object_types.ts @@ -7,9 +7,9 @@ */ import { i18n } from '@kbn/i18n'; -import { DeprecationsDetails } from '@kbn/core-deprecations-common'; -import { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; -import { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; +import type { DeprecationsDetails } from '@kbn/core-deprecations-common'; +import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server'; +import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import { getIndexForType } from '@kbn/core-saved-objects-base-server-internal'; import { getAggregatedTypesDocuments, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts index 5302e630caa7e4..a004799a46029f 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/internal_types.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server'; -import { ElasticsearchRequestHandlerContext } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsRequestHandlerContext } from '@kbn/core-saved-objects-server'; +import type { IRouter, RequestHandlerContextBase } from '@kbn/core-http-server'; +import type { ElasticsearchRequestHandlerContext } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsRequestHandlerContext } from '@kbn/core-saved-objects-server'; /** * Request handler context used by core's savedObjects routes. diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts index 73bf87307d02c0..db000cffc9e2a8 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/mocks/internal_mocks.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { +import type { DeprecationsServiceSetup, DeprecationRegistryProvider, } from '@kbn/core-deprecations-server'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; export const createDeprecationsSetupMock = () => { const setupContract: jest.Mocked = { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts index 43bc2ba2b6be36..5d31cb1e030771 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/object_types/registration.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; +import type { ISavedObjectTypeRegistry, SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry, LEGACY_URL_ALIAS_TYPE, 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 35f8f8ad2423cf..4192e800e51d23 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 451a219be07592..f435eadebd0669 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 33d60c053b4e68..ec8d7a1c842056 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 8e7b37e6622de3..8947719bd2ab57 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 d6b38777c6f344..dbe60203afddfb 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 fd0f6c6040c0d6..2ef810ebebed73 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 f4d0f8b9966afa..bfef6deb885efc 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts index 040d7c433eb162..5d369949283635 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/deprecations/delete_unknown_types.ts @@ -7,7 +7,7 @@ */ import { catchAndReturnBoomErrors } from '../utils'; -import { InternalSavedObjectRouter } from '../../internal_types'; +import type { InternalSavedObjectRouter } from '../../internal_types'; import { deleteUnknownTypeObjects } from '../../deprecations'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts index e478fb39d6656c..1d8a55b9e3fdcd 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/export.ts @@ -10,15 +10,15 @@ import { schema } from '@kbn/config-schema'; import stringify from 'json-stable-stringify'; import { createPromiseFromStreams, createMapStream, createConcatStream } from '@kbn/utils'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsExportByTypeOptions, SavedObjectsExportByObjectOptions, } from '@kbn/core-saved-objects-server'; -import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsExportError } from '@kbn/core-saved-objects-import-export-server-internal'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { validateTypes, validateObjects, catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 df9fab83ee0c0f..983b31caf7a2b0 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { 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 89cd9614f1f024..dbe2c1a4d9d8d6 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts index 9a1b6f282a6784..8e8722ae588c07 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/import.ts @@ -9,10 +9,10 @@ import { Readable } from 'stream'; import { extname } from 'path'; import { schema } from '@kbn/config-schema'; -import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsImportError } from '@kbn/core-saved-objects-import-export-server-internal'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; interface RouteDependencies { 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 2757439b8055b2..89d5b41dd88856 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 @@ -6,11 +6,14 @@ * Side Public License, v 1. */ -import { Logger } from '@kbn/logging'; -import { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; -import { SavedObjectConfig, IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectsRequestHandlerContext } from '../internal_types'; +import type { Logger } from '@kbn/logging'; +import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import type { + SavedObjectConfig, + IKibanaMigrator, +} from '@kbn/core-saved-objects-base-server-internal'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectsRequestHandlerContext } from '../internal_types'; import { registerGetRoute } from './get'; import { registerResolveRoute } from './resolve'; import { registerCreateRoute } from './create'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts index ac86f8c7f3b638..3ccbee914e8b16 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/export.ts @@ -8,9 +8,9 @@ import moment from 'moment'; import { schema } from '@kbn/config-schema'; -import { Logger } from '@kbn/logging'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../../internal_types'; +import type { Logger } from '@kbn/logging'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../../internal_types'; import { exportDashboards } from './lib'; export const registerLegacyExportRoute = ( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts index 2ace325352d8e8..44ee99aa2c9702 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/import.ts @@ -7,10 +7,10 @@ */ import { schema } from '@kbn/config-schema'; -import { Logger } from '@kbn/logging'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../../internal_types'; +import type { Logger } from '@kbn/logging'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../../internal_types'; import { importDashboards } from './lib'; export const registerLegacyImportRoute = ( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts index d06ed05d0d4b51..4470983fafc620 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject, SavedObjectAttributes } from '@kbn/core-saved-objects-common'; +import type { SavedObject, SavedObjectAttributes } from '@kbn/core-saved-objects-common'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { collectReferencesDeep } from './collect_references_deep'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts index 5cf6cb06715bea..0c193f618624d5 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/collect_references_deep.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; const MAX_BULK_GET_SIZE = 10000; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts index f07c8c57e0c5f8..8b069ee31f91a1 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/export_dashboards.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { collectReferencesDeep } from './collect_references_deep'; export async function exportDashboards( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts index 9b8986198baea7..87452664ac5178 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { importDashboards } from './import_dashboards'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts index 80ce8ad308add0..51a63f23322bf0 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/legacy_import_export/lib/import_dashboards.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; export async function importDashboards( savedObjectsClient: SavedObjectsClientContract, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts index 29e2b9549f8973..2222b17c4e6f99 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/migrate.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { IKibanaMigrator } from '@kbn/core-saved-objects-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; export const registerMigrateRoute = ( 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 6bd635ee194dd5..a1804c1fa31767 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,8 +7,8 @@ */ import { schema } from '@kbn/config-schema'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; interface RouteDependencies { coreUsageData: InternalCoreUsageDataSetup; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts index 656974dc00a2ab..26380bd2aea84d 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/resolve_import_errors.ts @@ -10,10 +10,10 @@ import { extname } from 'path'; import { Readable } from 'stream'; import { chain } from 'lodash'; import { schema } from '@kbn/config-schema'; -import { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; +import type { SavedObjectConfig } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsImportError } from '@kbn/core-saved-objects-import-export-server-internal'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; interface RouteDependencies { 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 e7b2cd3b671d55..463b5465b53a01 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 @@ -7,9 +7,9 @@ */ import { schema } from '@kbn/config-schema'; -import { SavedObjectsUpdateOptions } from '@kbn/core-saved-objects-api-server'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { InternalSavedObjectRouter } from '../internal_types'; +import type { SavedObjectsUpdateOptions } from '@kbn/core-saved-objects-api-server'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { InternalSavedObjectRouter } from '../internal_types'; import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts index ffde86dbff7fb3..ddeff043f2bbab 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.test.ts @@ -11,7 +11,7 @@ import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; import { catchAndReturnBoomErrors } from './utils'; import Boom from '@hapi/boom'; -import { +import type { KibanaRequest, RequestHandler, RequestHandlerContextBase, diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts index faf4d724cbd1d7..463d59baf62072 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/routes/utils.ts @@ -16,9 +16,9 @@ import { createConcatStream, } from '@kbn/utils'; import Boom from '@hapi/boom'; -import { RequestHandlerWrapper } from '@kbn/core-http-server'; -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SavedObjectsExportResultDetails } from '@kbn/core-saved-objects-server'; +import type { RequestHandlerWrapper } from '@kbn/core-http-server'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsExportResultDetails } from '@kbn/core-saved-objects-server'; export async function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { const savedObjects = await createPromiseFromStreams([ diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts index 74c55cb23290fa..495537ab32f63b 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_route_handler_context.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { KibanaRequest } from '@kbn/core-http-server'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsRequestHandlerContext, ISavedObjectTypeRegistry, SavedObjectsClientProviderOptions, } from '@kbn/core-saved-objects-server'; -import { InternalSavedObjectsServiceStart } from './saved_objects_service'; +import type { InternalSavedObjectsServiceStart } from './saved_objects_service'; /** * The {@link SavedObjectsRequestHandlerContext} implementation. diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 264fd8fb5dbc8c..0f0d8a87363721 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -25,9 +25,9 @@ import { getEnvOptions } from '@kbn/config-mocks'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock, httpServerMock } from '@kbn/core-http-server-mocks'; -import { SavedObjectsClientFactoryProvider } from '@kbn/core-saved-objects-server'; +import type { SavedObjectsClientFactoryProvider } from '@kbn/core-saved-objects-server'; import { configServiceMock } from '@kbn/config-mocks'; -import { NodesVersionCompatibility } from '@kbn/core-elasticsearch-server-internal'; +import type { NodesVersionCompatibility } from '@kbn/core-elasticsearch-server-internal'; import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-internal'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; 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 be62e7349d8d4c..a102ac01595f1c 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 @@ -8,18 +8,18 @@ import { Subject, Observable, firstValueFrom } from 'rxjs'; import { filter, take, switchMap } from 'rxjs/operators'; -import { Logger } from '@kbn/logging'; -import { ServiceStatus } from '@kbn/core-status-common'; -import { CoreContext, CoreService } from '@kbn/core-base-server-internal'; -import { DocLinksServiceStart } from '@kbn/core-doc-links-server'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { +import type { Logger } from '@kbn/logging'; +import type { ServiceStatus } from '@kbn/core-status-common'; +import type { CoreContext, CoreService } from '@kbn/core-base-server-internal'; +import type { DocLinksServiceStart } from '@kbn/core-doc-links-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { InternalHttpServiceSetup } from '@kbn/core-http-server-internal'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { InternalElasticsearchServiceSetup, InternalElasticsearchServiceStart, } from '@kbn/core-elasticsearch-server-internal'; -import { +import type { SavedObjectsServiceSetup, SavedObjectsServiceStart, SavedObjectsRepositoryFactory, @@ -49,8 +49,8 @@ import { SavedObjectsExporter, SavedObjectsImporter, } from '@kbn/core-saved-objects-import-export-server-internal'; -import { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; -import { DeprecationRegistryProvider } from '@kbn/core-deprecations-server'; +import type { InternalCoreUsageDataSetup } from '@kbn/core-usage-data-base-server-internal'; +import type { DeprecationRegistryProvider } from '@kbn/core-deprecations-server'; import { registerRoutes } from './routes'; import { calculateStatus$ } from './status'; import { registerCoreObjectTypes } from './object_types'; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts index 3aa81ed1a1ccf7..ab00eeab788885 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/status.ts @@ -9,8 +9,8 @@ import { Observable, combineLatest } from 'rxjs'; import { startWith, map } from 'rxjs/operators'; import { type ServiceStatus, ServiceStatusLevels } from '@kbn/core-status-common'; -import { SavedObjectStatusMeta } from '@kbn/core-saved-objects-server'; -import { KibanaMigratorStatus } from '@kbn/core-saved-objects-base-server-internal'; +import type { SavedObjectStatusMeta } from '@kbn/core-saved-objects-server'; +import type { KibanaMigratorStatus } from '@kbn/core-saved-objects-base-server-internal'; export const calculateStatus$ = ( rawMigratorStatus$: Observable, diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts index d48f82123666f8..2de6d7afcb6a1b 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts @@ -7,14 +7,14 @@ */ import { BehaviorSubject } from 'rxjs'; -import { PublicMethodsOf } from '@kbn/utility-types'; +import type { PublicMethodsOf } from '@kbn/utility-types'; import { ServiceStatusLevels } from '@kbn/core-status-common'; -import { +import type { SavedObjectsServiceSetup, SavedObjectsServiceStart, ISavedObjectTypeRegistry, } from '@kbn/core-saved-objects-server'; -import { +import type { SavedObjectsService, InternalSavedObjectsServiceSetup, InternalSavedObjectsServiceStart, diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json b/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json index 4582562d6c9bb4..3fe98195b374a7 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/tsconfig.json @@ -4,6 +4,8 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", + "rootDir": ".", + "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts index 5b9bc1514601fc..7a3547c598c34f 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts @@ -6,16 +6,16 @@ * Side Public License, v 1. */ -import { KibanaRequest } from '@kbn/core-http-server'; -import { +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectsEncryptionExtension } from './encryption'; -import { SavedObjectsExtensions } from './extensions'; -import { ISavedObjectsSecurityExtension } from './security'; -import { ISavedObjectsSpacesExtension } from './spaces'; -import { ISavedObjectTypeRegistry } from './type_registry'; +import type { ISavedObjectsEncryptionExtension } from './encryption'; +import type { SavedObjectsExtensions } from './extensions'; +import type { ISavedObjectsSecurityExtension } from './security'; +import type { ISavedObjectsSpacesExtension } from './spaces'; +import type { ISavedObjectTypeRegistry } from './type_registry'; /** * Describes the factory used to create instances of the Saved Objects Client. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index 38c19428997df8..a181865271f1f6 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -6,23 +6,23 @@ * Side Public License, v 1. */ -import { KibanaRequest } from '@kbn/core-http-server'; -import { +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectsSerializer } from './serialization'; -import { +import type { ISavedObjectsSerializer } from './serialization'; +import type { SavedObjectsClientFactoryProvider, SavedObjectsClientProviderOptions, SavedObjectsEncryptionExtensionFactory, SavedObjectsSecurityExtensionFactory, SavedObjectsSpacesExtensionFactory, } from './client_factory'; -import { SavedObjectsType } from './saved_objects_type'; -import { ISavedObjectTypeRegistry } from './type_registry'; -import { ISavedObjectsExporter } from './export'; -import { ISavedObjectsImporter } from './import'; +import type { SavedObjectsType } from './saved_objects_type'; +import type { ISavedObjectTypeRegistry } from './type_registry'; +import type { ISavedObjectsExporter } from './export'; +import type { ISavedObjectsImporter } from './import'; import { SavedObjectsExtensions } from './extensions'; /** diff --git a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts index 2a0f3609f17308..124406fc659bb9 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; /** * The EncryptedObjectDescriptor interface contains settings for describing diff --git a/packages/core/saved-objects/core-saved-objects-server/src/export.ts b/packages/core/saved-objects/core-saved-objects-server/src/export.ts index 9b0c1c65f29382..67afd0b011a1dd 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/export.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/export.ts @@ -6,10 +6,10 @@ * Side Public License, v 1. */ -import { Readable } from 'stream'; -import { KibanaRequest } from '@kbn/core-http-server'; -import { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-server'; +import type { Readable } from 'stream'; +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObject, SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsFindOptionsReference } from '@kbn/core-saved-objects-api-server'; /** * Utility class used to export savedObjects. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts index b11bc0e3a7474d..a51184b61a113a 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { ISavedObjectsEncryptionExtension } from './encryption'; -import { ISavedObjectsSecurityExtension } from './security'; -import { ISavedObjectsSpacesExtension } from './spaces'; +import type { ISavedObjectsEncryptionExtension } from './encryption'; +import type { ISavedObjectsSecurityExtension } from './security'; +import type { ISavedObjectsSpacesExtension } from './spaces'; /** * The SavedObjectsExtensions interface contains the intefaces for three diff --git a/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts b/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts index c38c97c6958ca0..80ba357009530b 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/mapping_definition.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { PropertyName as EsPropertyName, MappingProperty as EsMappingProperty, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/migration.ts b/packages/core/saved-objects/core-saved-objects-server/src/migration.ts index fa2f277ec52b34..4634edef3d2bf0 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/migration.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/migration.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { LogMeta } from '@kbn/logging'; -import { SavedObjectUnsanitizedDoc } from './serialization'; +import type { LogMeta } from '@kbn/logging'; +import type { SavedObjectUnsanitizedDoc } from './serialization'; /** * A migration function for a {@link SavedObjectsType | saved object type} diff --git a/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts b/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts index 6c2960f891de40..b5d1c26ac4a93e 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/request_handler_context.ts @@ -6,11 +6,11 @@ * Side Public License, v 1. */ -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { ISavedObjectsExporter } from './export'; -import { ISavedObjectsImporter } from './import'; -import { ISavedObjectTypeRegistry } from './type_registry'; -import { SavedObjectsClientProviderOptions } from './client_factory'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectsExporter } from './export'; +import type { ISavedObjectsImporter } from './import'; +import type { ISavedObjectTypeRegistry } from './type_registry'; +import type { SavedObjectsClientProviderOptions } from './client_factory'; /** * Core's `savedObjects` request handler context. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts index bea39e65fd77d2..7a67d70cbf4d4c 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_management.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; -import { SavedObjectsExportTransform } from './export'; -import { SavedObjectsImportHook } from './import'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsExportTransform } from './export'; +import type { SavedObjectsImportHook } from './import'; /** * Configuration options for the {@link SavedObjectsType | type}'s management section. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts index e849bbaa4cad87..b26be6f8bfb1a2 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/saved_objects_type.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ -import { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { MaybePromise } from '@kbn/utility-types'; -import { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; -import { SavedObjectsNamespaceType } from '@kbn/core-saved-objects-common'; -import { SavedObjectsTypeManagementDefinition } from './saved_objects_management'; -import { SavedObjectsValidationMap } from './validation'; -import { SavedObjectMigrationMap } from './migration'; -import { SavedObjectsTypeMappingDefinition } from './mapping_definition'; +import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; +import type { MaybePromise } from '@kbn/utility-types'; +import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; +import type { SavedObjectsNamespaceType } from '@kbn/core-saved-objects-common'; +import type { SavedObjectsTypeManagementDefinition } from './saved_objects_management'; +import type { SavedObjectsValidationMap } from './validation'; +import type { SavedObjectMigrationMap } from './migration'; +import type { SavedObjectsTypeMappingDefinition } from './mapping_definition'; /** * @public diff --git a/packages/core/saved-objects/core-saved-objects-server/src/security.ts b/packages/core/saved-objects/core-saved-objects-server/src/security.ts index 17e986b0dac202..0623b5efb59d76 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/security.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/security.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; import { EcsEventOutcome } from '@kbn/logging'; /** diff --git a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts index 42f900991559c6..7752d0dd99e901 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/serialization.ts @@ -6,7 +6,10 @@ * Side Public License, v 1. */ -import { SavedObjectsMigrationVersion, SavedObjectReference } from '@kbn/core-saved-objects-common'; +import type { + SavedObjectsMigrationVersion, + SavedObjectReference, +} from '@kbn/core-saved-objects-common'; /** * A serializer that can be used to manually convert {@link SavedObjectsRawDoc | raw} or diff --git a/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts b/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts index 43eaaacdf764b6..ac3647da81f7fe 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/type_registry.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { SavedObjectsType } from './saved_objects_type'; +import type { SavedObjectsType } from './saved_objects_type'; /** * Registry holding information about all the registered {@link SavedObjectsType | saved object types}. diff --git a/packages/core/saved-objects/core-saved-objects-server/src/validation.ts b/packages/core/saved-objects/core-saved-objects-server/src/validation.ts index bf5d8fc355ade7..a60581e3939af9 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/validation.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/validation.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { ObjectType } from '@kbn/config-schema'; +import type { ObjectType } from '@kbn/config-schema'; /** * Allows for validating properties using @kbn/config-schema validations. diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts index bd060f672e264b..28b3ebd3735b4f 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { +import type { SavedObjectMigrationContext, SavedObjectMigrationMap, SavedObjectUnsanitizedDoc, diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts index e5b0f113bf0a80..6d7c1c90d25df1 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/merge_migration_maps.ts @@ -7,7 +7,7 @@ */ import { mergeWith } from 'lodash'; -import { +import type { SavedObjectMigrationContext, SavedObjectMigrationFn, SavedObjectMigrationMap, diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts index 1b89f3064e07f9..717b52ef248ca8 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.test.ts @@ -8,7 +8,7 @@ import { mockUuidv1, mockUuidv5 } from './saved_objects_utils.test.mock'; -import { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsFindOptions } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsUtils } from './saved_objects_utils'; describe('SavedObjectsUtils', () => { diff --git a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts index 215743cb9101a4..240abb53db289a 100644 --- a/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts +++ b/packages/core/saved-objects/core-saved-objects-utils-server/src/saved_objects_utils.ts @@ -8,7 +8,7 @@ import uuidv1 from 'uuid/v1'; import uuidv5 from 'uuid/v5'; -import { +import type { SavedObjectsFindOptions, SavedObjectsFindResponse, } from '@kbn/core-saved-objects-api-server'; From 9ed2d02e78f9d632e489e54da5f5d92ee58baea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 3 Nov 2022 15:26:18 +0100 Subject: [PATCH 30/47] Fixes lost update to point-in-time finder mock. --- .../src/mocks/point_in_time_finder.mock.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts index d6b7e51f78bd15..b9aa349bbb55e1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts @@ -48,7 +48,7 @@ const createPointInTimeFinderMock = ({ const createPointInTimeFinderClientMock = (): jest.Mocked => { return { find: jest.fn(), - openPointInTimeForType: jest.fn(), + openPointInTimeForType: jest.fn().mockResolvedValue({ id: 'some_pit_id' }), closePointInTime: jest.fn(), }; }; From bd0695bdcc4cb76cf8594d35836134208eb1a2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 4 Nov 2022 08:26:36 +0100 Subject: [PATCH 31/47] import type housekeeping --- .../src/apis/resolve.ts | 2 +- ...collect_multi_namespace_references.test.ts | 4 +-- .../lib/collect_multi_namespace_references.ts | 4 +-- .../src/lib/internal_bulk_resolve.test.ts | 8 ++--- .../src/lib/internal_bulk_resolve.ts | 12 +++---- .../src/lib/point_in_time_finder.test.ts | 2 +- .../lib/preflight_check_for_create.test.ts | 4 +-- .../src/lib/preflight_check_for_create.ts | 2 +- .../src/lib/repository.test.ts | 4 +-- .../src/lib/repository.ts | 34 ++++++++----------- .../repository_bulk_delete_internal_types.ts | 6 ++-- .../src/lib/scoped_client_provider.test.ts | 12 +++---- .../src/lib/scoped_client_provider.ts | 14 ++++---- .../src/lib/search_dsl/search_dsl.ts | 2 +- .../src/lib/update_objects_spaces.test.ts | 2 +- .../src/lib/update_objects_spaces.ts | 10 +++--- .../src/mocks/point_in_time_finder.mock.ts | 2 +- .../src/point_in_time_finder.mock.ts | 2 +- .../src/apis/create_point_in_time_finder.ts | 2 +- .../src/mappings/lib/get_property.ts | 2 +- .../lib/get_root_properties_objects.ts | 2 +- .../src/export/apply_export_transforms.ts | 2 +- .../export/collect_exported_objects.test.ts | 2 +- .../src/export/saved_objects_exporter.test.ts | 2 +- .../src/export/saved_objects_exporter.ts | 6 +++- .../src/import/import_saved_objects.test.ts | 5 ++- .../lib/check_reference_origins.test.ts | 2 +- .../import/lib/create_saved_objects.test.ts | 2 +- .../src/import/resolve_import_errors.test.ts | 2 +- .../src/import/resolve_import_errors.ts | 2 +- .../bulk_overwrite_transformed_documents.ts | 2 +- .../src/actions/clone_index.ts | 4 +-- .../src/actions/close_pit.ts | 2 +- .../src/actions/create_index.ts | 4 +-- .../src/actions/fetch_indices.ts | 2 +- .../src/actions/index.ts | 4 +-- .../src/actions/initialize_action.ts | 4 +-- .../src/actions/open_pit.ts | 2 +- .../src/actions/pickup_updated_mappings.ts | 2 +- .../src/actions/read_with_pit.ts | 2 +- .../src/actions/refresh_index.ts | 2 +- .../src/actions/remove_write_block.ts | 2 +- .../actions/search_for_outdated_documents.ts | 2 +- .../src/actions/set_write_block.ts | 2 +- .../src/actions/transform_docs.ts | 2 +- .../src/actions/update_aliases.ts | 2 +- .../src/actions/update_and_pickup_mappings.ts | 2 +- .../src/actions/wait_for_index_status.ts | 2 +- .../src/actions/wait_for_reindex_task.ts | 4 +-- .../src/actions/wait_for_task.ts | 2 +- .../src/kibana_migrator.test.ts | 2 +- .../src/kibana_migrator.ts | 2 +- .../src/migrations_state_action_machine.ts | 4 +-- .../src/model/model.test.ts | 6 ++-- .../src/next.test.ts | 2 +- .../src/types.ts | 2 +- .../src/saved_objects_service.test.ts | 2 +- .../src/contracts.ts | 2 +- .../core-saved-objects-server/src/security.ts | 2 +- 59 files changed, 118 insertions(+), 113 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts index c2383d7cb50d50..d2868ae48ced14 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/apis/resolve.ts @@ -7,7 +7,7 @@ */ import type { SavedObjectsResolveResponse } from '@kbn/core-saved-objects-api-server'; -import { SimpleSavedObject } from '../simple_saved_object'; +import type { SimpleSavedObject } from '../simple_saved_object'; /** * This interface is a very simple wrapper for SavedObjects resolved from the server diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 4bb4606131cb00..24ce2bc5ffbb79 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -22,11 +22,11 @@ import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-inte import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { ALIAS_OR_SHARED_ORIGIN_SEARCH_PER_PAGE, - CollectMultiNamespaceReferencesParams, + type CollectMultiNamespaceReferencesParams, } from './collect_multi_namespace_references'; import { collectMultiNamespaceReferences } from './collect_multi_namespace_references'; import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; -import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; +import { AuditAction, type ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; import { authMap, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts index 0db38392efd55f..42237461c8eeee 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts @@ -16,8 +16,8 @@ import type { } from '@kbn/core-saved-objects-api-server'; import { AuditAction, - ISavedObjectsSecurityExtension, - ISavedObjectTypeRegistry, + type ISavedObjectsSecurityExtension, + type ISavedObjectTypeRegistry, } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers, SavedObjectsUtils } from '@kbn/core-saved-objects-utils-server'; import { diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index b0e9bf5776b1c0..9eeab939f7d7c9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -24,13 +24,13 @@ import { LEGACY_URL_ALIAS_TYPE, } from '@kbn/core-saved-objects-base-server-internal'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; -import { internalBulkResolve, InternalBulkResolveParams } from './internal_bulk_resolve'; +import { internalBulkResolve, type InternalBulkResolveParams } from './internal_bulk_resolve'; import { normalizeNamespace } from './internal_utils'; import { AuditAction, - ISavedObjectsEncryptionExtension, - ISavedObjectsSecurityExtension, - ISavedObjectTypeRegistry, + type ISavedObjectsEncryptionExtension, + type ISavedObjectsSecurityExtension, + type ISavedObjectTypeRegistry, } from '@kbn/core-saved-objects-server'; import { authMap, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts index 6e13671661b95d..7fb26efa835b64 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts @@ -19,10 +19,10 @@ import type { } from '@kbn/core-saved-objects-api-server'; import { AuditAction, - ISavedObjectsEncryptionExtension, - ISavedObjectsSecurityExtension, - ISavedObjectTypeRegistry, - SavedObjectsRawDocSource, + type ISavedObjectsEncryptionExtension, + type ISavedObjectsSecurityExtension, + type ISavedObjectTypeRegistry, + type SavedObjectsRawDocSource, } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers, @@ -45,8 +45,8 @@ import { getSavedObjectFromSource, normalizeNamespace, rawDocExistsInNamespace, - Either, - Right, + type Either, + type Right, isLeft, isRight, } from './internal_utils'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts index a8f9722c09e7a5..fe8b8190ff8403 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/point_in_time_finder.test.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; +import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; import type { SavedObjectsFindResult, SavedObjectsCreatePointInTimeFinderOptions, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts index d23d2cf5e804e8..f2901e4b531878 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.test.ts @@ -23,8 +23,8 @@ import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; import { ALIAS_SEARCH_PER_PAGE, - PreflightCheckForCreateObject, - PreflightCheckForCreateParams, + type PreflightCheckForCreateObject, + type PreflightCheckForCreateParams, } from './preflight_check_for_create'; import { preflightCheckForCreate } from './preflight_check_for_create'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts index 1d1b4839635e8c..1daa7b5f37ea44 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/preflight_check_for_create.ts @@ -24,7 +24,7 @@ import { type SavedObjectsSerializer, } from '@kbn/core-saved-objects-base-server-internal'; import { findLegacyUrlAliases } from './legacy_url_aliases'; -import { Either, rawDocExistsInNamespaces } from './internal_utils'; +import { type Either, rawDocExistsInNamespaces } from './internal_utils'; import { isLeft, isRight } from './internal_utils'; import type { CreatePointInTimeFinderFn } from './point_in_time_finder'; import type { RepositoryEsClient } from './repository_es_client'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts index 36b6cb59135661..e8365293991fed 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.test.ts @@ -69,7 +69,7 @@ import { kibanaMigratorMock } from '../mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import * as esKuery from '@kbn/es-query'; import { errors as EsErrors } from '@elastic/elasticsearch'; -import { InternalBulkResolveError } from './internal_bulk_resolve'; +import type { InternalBulkResolveError } from './internal_bulk_resolve'; import { KIBANA_VERSION, @@ -88,7 +88,7 @@ import { createDocumentMigrator, getMockGetResponse, getMockMgetResponse, - TypeIdTuple, + type TypeIdTuple, createSpySerializer, bulkCreateSuccess, bulkUpdateSuccess, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index e2c20c7485f95f..7d5f412abd5f2b 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -61,17 +61,17 @@ import type { ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; import { - SavedObjectSanitizedDoc, - SavedObjectsRawDoc, - SavedObjectsRawDocSource, - ISavedObjectTypeRegistry, - SavedObjectsExtensions, - ISavedObjectsEncryptionExtension, - ISavedObjectsSecurityExtension, - ISavedObjectsSpacesExtension, + type SavedObjectSanitizedDoc, + type SavedObjectsRawDoc, + type SavedObjectsRawDocSource, + type ISavedObjectTypeRegistry, + type SavedObjectsExtensions, + type ISavedObjectsEncryptionExtension, + type ISavedObjectsSecurityExtension, + type ISavedObjectsSpacesExtension, AuditAction, - CheckAuthorizationResult, - AuthorizationTypeMap, + type CheckAuthorizationResult, + type AuthorizationTypeMap, } from '@kbn/core-saved-objects-server'; import { DEFAULT_NAMESPACE_STRING, @@ -98,12 +98,12 @@ import { } from '@kbn/core-saved-objects-base-server-internal'; import pMap from 'p-map'; import { PointInTimeFinder } from './point_in_time_finder'; -import { createRepositoryEsClient, RepositoryEsClient } from './repository_es_client'; +import { createRepositoryEsClient, type RepositoryEsClient } from './repository_es_client'; import { getSearchDsl } from './search_dsl'; import { includedFields } from './included_fields'; import { internalBulkResolve, - InternalBulkResolveError, + type InternalBulkResolveError, isBulkResolveError, } from './internal_bulk_resolve'; import { validateConvertFilterToKueryNode } from './filter_utils'; @@ -116,7 +116,7 @@ import { normalizeNamespace, rawDocExistsInNamespace, rawDocExistsInNamespaces, - Either, + type Either, isLeft, isRight, } from './internal_utils'; @@ -124,8 +124,8 @@ import { collectMultiNamespaceReferences } from './collect_multi_namespace_refer import { updateObjectsSpaces } from './update_objects_spaces'; import { preflightCheckForCreate, - PreflightCheckForCreateObject, - PreflightCheckForCreateResult, + type PreflightCheckForCreateObject, + type PreflightCheckForCreateResult, } from './preflight_check_for_create'; import { deleteLegacyUrlAliases } from './legacy_url_aliases'; import type { @@ -1494,14 +1494,10 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { // This ensures that the query DSL can filter only for object types that the user is authorized to access for a given space const { authorizedSpaces, isGloballyAuthorized } = entry.find; typeToNamespacesMap.set(objType, isGloballyAuthorized ? namespaces : authorizedSpaces); - // TODO: REMOVE console.log(`AUTHZd: ${authorizedSpaces}`); - // TODO: REMOVE console.log(`REPO MAP: ${JSON.stringify(typeToNamespacesMap.get(objType))}`); } } } - // TODO: REMOVE console.log(`getSearchDsl Mock Check: ${getSearchDsl.mock}`); - const esOptions = { // If `pit` is provided, we drop the `index`, otherwise ES returns 400. index: pit ? undefined : this.getIndicesForTypes(allowedTypes), diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts index 93d4354d8d7e84..6319662e8bb98f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository_bulk_delete_internal_types.ts @@ -6,14 +6,14 @@ * Side Public License, v 1. */ import type { Payload } from '@hapi/boom'; -import { +import type { BulkOperationBase, BulkResponseItem, ErrorCause, } from '@elastic/elasticsearch/lib/api/types'; import type { estypes, TransportResult } from '@elastic/elasticsearch'; -import { Either } from './internal_utils'; -import { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; +import type { Either } from './internal_utils'; +import type { DeleteLegacyUrlAliasesParams } from './legacy_url_aliases'; /** * @internal diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts index 871b38fd89af80..e7d5d6c46459b9 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts @@ -10,16 +10,16 @@ import { httpServerMock } from '@kbn/core-http-server-mocks'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { SavedObjectsClientProvider } from './scoped_client_provider'; import { - ISavedObjectTypeRegistry, - SavedObjectsClientFactory, - SavedObjectsEncryptionExtensionFactory, - SavedObjectsSecurityExtensionFactory, - SavedObjectsSpacesExtensionFactory, + type ISavedObjectTypeRegistry, + type SavedObjectsClientFactory, + type SavedObjectsEncryptionExtensionFactory, + type SavedObjectsSecurityExtensionFactory, + type SavedObjectsSpacesExtensionFactory, ENCRYPTION_EXTENSION_ID, SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID, } from '@kbn/core-saved-objects-server'; -import { KibanaRequest } from '@kbn/core-http-server'; +import type { KibanaRequest } from '@kbn/core-http-server'; import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; /** diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index e0fa0a16d2e8df..2207ba8adf0c11 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -6,9 +6,9 @@ * Side Public License, v 1. */ -import { KibanaRequest } from '@kbn/core-http-server'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; -import { +import type { KibanaRequest } from '@kbn/core-http-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { ISavedObjectTypeRegistry, SavedObjectsClientFactory, SavedObjectsClientProviderOptions, @@ -16,13 +16,15 @@ import { SavedObjectsSecurityExtensionFactory, SavedObjectsSpacesExtensionFactory, SavedObjectsExtensions, - ENCRYPTION_EXTENSION_ID, - SECURITY_EXTENSION_ID, - SPACES_EXTENSION_ID, ISavedObjectsEncryptionExtension, ISavedObjectsSecurityExtension, ISavedObjectsSpacesExtension, } from '@kbn/core-saved-objects-server'; +import { + ENCRYPTION_EXTENSION_ID, + SECURITY_EXTENSION_ID, + SPACES_EXTENSION_ID, +} from '@kbn/core-saved-objects-server'; /** * @internal diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts index 381f20069d25a9..bcc705da1282d2 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/search_dsl/search_dsl.ts @@ -13,7 +13,7 @@ import type { SavedObjectsPitParams } from '@kbn/core-saved-objects-api-server'; import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common'; -import { getQueryParams, SearchOperator } from './query_params'; +import { getQueryParams, type SearchOperator } from './query_params'; import { getPitParams } from './pit_params'; import { getSortingParams } from './sorting_params'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 9d3f8475bc5b1c..38f4bff0800ad7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -25,7 +25,7 @@ import { SavedObjectsSerializer } from '@kbn/core-saved-objects-base-server-inte import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import type { UpdateObjectsSpacesParams } from './update_objects_spaces'; import { updateObjectsSpaces } from './update_objects_spaces'; -import { AuditAction, ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; +import { AuditAction, type ISavedObjectsSecurityExtension } from '@kbn/core-saved-objects-server'; import { authMap, checkAuthError, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts index f5b810ea3f3af5..88ee4027b207b7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts @@ -20,9 +20,9 @@ import type { } from '@kbn/core-saved-objects-api-server'; import { AuditAction, - ISavedObjectsSecurityExtension, - ISavedObjectTypeRegistry, - SavedObjectsRawDocSource, + type ISavedObjectsSecurityExtension, + type ISavedObjectTypeRegistry, + type SavedObjectsRawDocSource, } from '@kbn/core-saved-objects-server'; import { SavedObjectsErrorHelpers, @@ -34,12 +34,12 @@ import type { IndexMapping, SavedObjectsSerializer, } from '@kbn/core-saved-objects-base-server-internal'; -import { SavedObject } from '@kbn/core-saved-objects-common'; +import type { SavedObject } from '@kbn/core-saved-objects-common'; import { getBulkOperationError, getExpectedVersionProperties, rawDocExistsInNamespace, - Either, + type Either, isLeft, isRight, } from './internal_utils'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts index b9aa349bbb55e1..a9365975da1e6e 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/point_in_time_finder.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; +import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; import type { SavedObjectsClientContract, ISavedObjectsRepository, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts index b14715db34dddc..2bbc4920ecab10 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-mocks/src/point_in_time_finder.mock.ts @@ -6,7 +6,7 @@ * Side Public License, v 1. */ -import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; +import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; import type { SavedObjectsClientContract, ISavedObjectsRepository, diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts index b5bd62b75cbdf3..7cbf77467c8e31 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/apis/create_point_in_time_finder.ts @@ -7,7 +7,7 @@ */ import type { SavedObjectsFindOptions, SavedObjectsFindResponse } from './find'; -import { ISavedObjectsRepository } from '../saved_objects_repository'; +import type { ISavedObjectsRepository } from '../saved_objects_repository'; /** * Options for the create point-in-time finder operation diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts index ac1070741af711..d43a78ec454152 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_property.ts @@ -8,7 +8,7 @@ import { toPath } from 'lodash'; import type { SavedObjectsFieldMapping } from '@kbn/core-saved-objects-server'; -import { IndexMapping } from '../types'; +import type { IndexMapping } from '../types'; function getPropertyMappingFromObjectMapping( mapping: IndexMapping | SavedObjectsFieldMapping, diff --git a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts index fb5a7666b9071a..83264d7153cc09 100644 --- a/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts +++ b/packages/core/saved-objects/core-saved-objects-base-server-internal/src/mappings/lib/get_root_properties_objects.ts @@ -10,7 +10,7 @@ import type { SavedObjectsFieldMapping, SavedObjectsMappingProperties, } from '@kbn/core-saved-objects-server'; -import { IndexMapping } from '../types'; +import type { IndexMapping } from '../types'; import { getRootProperties } from './get_root_properties'; /** diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts index 0c871327136f96..4a7b98f5111d06 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/apply_export_transforms.ts @@ -13,7 +13,7 @@ import type { SavedObjectsExportTransformContext, } from '@kbn/core-saved-objects-server'; import { SavedObjectsExportError } from './errors'; -import { getObjKey, SavedObjectComparator } from './utils'; +import { getObjKey, type SavedObjectComparator } from './utils'; interface ApplyExportTransformsOptions { objects: SavedObject[]; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts index 04ea3f984b19b6..6125c9fb7d7399 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/collect_exported_objects.test.ts @@ -16,7 +16,7 @@ import { applyExportTransformsMock } from './collect_exported_objects.test.mocks import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { loggerMock } from '@kbn/logging-mocks'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; -import { collectExportedObjects, ExclusionReason } from './collect_exported_objects'; +import { collectExportedObjects, type ExclusionReason } from './collect_exported_objects'; const createObject = (parts: Partial): SavedObject => ({ id: 'id', diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts index 2674b5a62e14a8..498ddde9fbbaed 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.test.ts @@ -11,7 +11,7 @@ import type { SavedObject } from '@kbn/core-saved-objects-common'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; import { SavedObjectsExporter } from './saved_objects_exporter'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; -import { loggerMock, MockedLogger } from '@kbn/logging-mocks'; +import { loggerMock, type MockedLogger } from '@kbn/logging-mocks'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts index c3213eb9108bd0..bd122dbf8b392a 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/export/saved_objects_exporter.ts @@ -25,7 +25,11 @@ import type { import { sortObjects } from './sort_objects'; import { SavedObjectsExportError } from './errors'; import { collectExportedObjects } from './collect_exported_objects'; -import { byIdAscComparator, getPreservedOrderComparator, SavedObjectComparator } from './utils'; +import { + byIdAscComparator, + getPreservedOrderComparator, + type SavedObjectComparator, +} from './utils'; /** * @internal diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts index b44020e1774bef..02af9ca6dca774 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/import_saved_objects.test.ts @@ -32,7 +32,10 @@ import type { } from '@kbn/core-saved-objects-server'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; -import { importSavedObjectsFromStream, ImportSavedObjectsOptions } from './import_saved_objects'; +import { + importSavedObjectsFromStream, + type ImportSavedObjectsOptions, +} from './import_saved_objects'; import type { ImportStateMap } from './lib'; describe('#importSavedObjectsFromStream', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts index 37a9aa96dd52fe..3925ed932aa116 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/check_reference_origins.test.ts @@ -13,7 +13,7 @@ import type { SavedObjectsClientContract, } from '@kbn/core-saved-objects-api-server'; import type { ISavedObjectTypeRegistry } from '@kbn/core-saved-objects-server'; -import { checkReferenceOrigins, CheckReferenceOriginsParams } from './check_reference_origins'; +import { checkReferenceOrigins, type CheckReferenceOriginsParams } from './check_reference_origins'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import type { ImportStateMap } from './types'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts index 907532bcc8fbbe..146e6650fa4c42 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/lib/create_saved_objects.test.ts @@ -8,7 +8,7 @@ import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import type { SavedObject, SavedObjectsImportFailure } from '@kbn/core-saved-objects-common'; -import { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; +import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server'; import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-utils-server'; import { createSavedObjects } from './create_saved_objects'; import { extractErrors } from './extract_errors'; diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts index ed93301a458133..0bc8a9190202e3 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.test.ts @@ -40,7 +40,7 @@ import { typeRegistryMock } from '@kbn/core-saved-objects-base-server-mocks'; import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks'; import { resolveSavedObjectsImportErrors, - ResolveSavedObjectsImportErrorsOptions, + type ResolveSavedObjectsImportErrorsOptions, } from './resolve_import_errors'; describe('#importSavedObjectsFromStream', () => { diff --git a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts index 9d96126265fd1a..72533ba7c877b2 100644 --- a/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts +++ b/packages/core/saved-objects/core-saved-objects-import-export-server-internal/src/import/resolve_import_errors.ts @@ -32,7 +32,7 @@ import { checkConflicts, executeImportHooks, checkOriginConflicts, - ImportStateMap, + type ImportStateMap, } from './lib'; /** diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts index 39194625e4531f..7a6e8b2d9a5b58 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/bulk_overwrite_transformed_documents.ts @@ -14,7 +14,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { isWriteBlockException, isIndexNotFoundException } from './es_errors'; import { WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE } from './constants'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts index 0e07a68c1ec39c..41830a4af078e0 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/clone_index.ts @@ -13,7 +13,7 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import type { IndexNotFound, AcknowledgeResponse } from '.'; import { type IndexNotGreenTimeout, waitForIndexStatus } from './wait_for_index_status'; @@ -24,7 +24,7 @@ import { WAIT_FOR_ALL_SHARDS_TO_BE_ACTIVE, } from './constants'; import { isClusterShardLimitExceeded } from './es_errors'; -import { ClusterShardLimitExceeded } from './create_index'; +import type { ClusterShardLimitExceeded } from './create_index'; export type CloneIndexResponse = AcknowledgeResponse; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts index a3ea50edccae67..adc2658e00bdf6 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/close_pit.ts @@ -11,7 +11,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts index 9353d28e9ffd80..0e68b0cef14c53 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/create_index.ts @@ -12,10 +12,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; -import { AcknowledgeResponse } from '.'; +import type { AcknowledgeResponse } from '.'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { DEFAULT_TIMEOUT, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts index 0eb43380a6990e..a9be3299978e0c 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/fetch_indices.ts @@ -12,7 +12,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; export type FetchIndexResponse = Record< diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts index 2b6d501f787b09..d2eeaf6c547d5b 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/index.ts @@ -6,8 +6,8 @@ * Side Public License, v 1. */ -import { RetryableEsClientError } from './catch_retryable_es_client_errors'; -import { DocumentsTransformFailed } from '../core/migrate_raw_docs'; +import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; +import type { DocumentsTransformFailed } from '../core/migrate_raw_docs'; export { BATCH_SIZE, diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts index a1b5e013600187..8daa548039eeb8 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/initialize_action.ts @@ -12,10 +12,10 @@ import { pipe } from 'fp-ts/lib/pipeable'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; -import { FetchIndexResponse, fetchIndices } from './fetch_indices'; +import { type FetchIndexResponse, fetchIndices } from './fetch_indices'; const routingAllocationEnable = 'cluster.routing.allocation.enable'; export interface ClusterRoutingAllocationEnabled { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts index 3966198393c219..c2573c78863443 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/open_pit.ts @@ -11,7 +11,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts index 9cb37facc0b5a2..0e34857b4f208d 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/pickup_updated_mappings.ts @@ -11,7 +11,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { BATCH_SIZE } from './constants'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts index 19e9ed2072c2bb..1d0303947c1b64 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/read_with_pit.ts @@ -13,7 +13,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { DEFAULT_PIT_KEEP_ALIVE } from './open_pit'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts index 7eb5c5d560d4cc..64940e6e81f5c2 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/refresh_index.ts @@ -11,7 +11,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts index 35a4dff315277e..86896fa0d75fed 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/remove_write_block.ts @@ -11,7 +11,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { DEFAULT_TIMEOUT } from './constants'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts index ac5508a1400384..2ea1882d4e35f1 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/search_for_outdated_documents.ts @@ -13,7 +13,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { SavedObjectsRawDoc, SavedObjectsRawDocSource } from '@kbn/core-saved-objects-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts index a84f976ceb6434..38c2d73b214705 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/set_write_block.ts @@ -12,7 +12,7 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { DEFAULT_TIMEOUT, IndexNotFound } from '.'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts index 52e79e6ac88c20..682a73de287881 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/transform_docs.ts @@ -9,7 +9,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; import type { TransformRawDocs } from '../types'; -import { DocumentsTransformFailed, DocumentsTransformSuccess } from '../core/migrate_raw_docs'; +import type { DocumentsTransformFailed, DocumentsTransformSuccess } from '../core/migrate_raw_docs'; /** @internal */ export interface TransformDocsParams { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts index c896dbf27258f2..eeaadbb86306f5 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_aliases.ts @@ -12,7 +12,7 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { DEFAULT_TIMEOUT, IndexNotFound } from '.'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts index 36a00f1096b1da..653a90746dea08 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/update_and_pickup_mappings.ts @@ -13,7 +13,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { IndexMapping } from '@kbn/core-saved-objects-base-server-internal'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { pickupUpdatedMappings } from './pickup_updated_mappings'; import { DEFAULT_TIMEOUT } from './constants'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts index 7ee63e75838511..25514ea2827977 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_index_status.ts @@ -11,7 +11,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; import { DEFAULT_TIMEOUT } from './constants'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts index 3eb7b0da718c2b..9d764ae5286e11 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_reindex_task.ts @@ -9,9 +9,9 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import * as Option from 'fp-ts/lib/Option'; import { flow } from 'fp-ts/lib/function'; -import { RetryableEsClientError } from './catch_retryable_es_client_errors'; +import type { RetryableEsClientError } from './catch_retryable_es_client_errors'; import type { IndexNotFound, TargetIndexHadWriteBlock } from '.'; -import { waitForTask, WaitForTaskCompletionTimeout } from './wait_for_task'; +import { waitForTask, type WaitForTaskCompletionTimeout } from './wait_for_task'; import { isWriteBlockException, isIncompatibleMappingException } from './es_errors'; export interface IncompatibleMappingException { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts index a5762ff10a122e..f0556e6b0d8a81 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/actions/wait_for_task.ts @@ -13,7 +13,7 @@ import { errors as EsErrors } from '@elastic/elasticsearch'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { catchRetryableEsClientErrors, - RetryableEsClientError, + type RetryableEsClientError, } from './catch_retryable_es_client_errors'; /** @internal */ diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts index 5d1cb32eca6d22..734aafbef14606 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.test.ts @@ -13,7 +13,7 @@ import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { elasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks'; import type { SavedObjectsType } from '@kbn/core-saved-objects-server'; import { SavedObjectTypeRegistry } from '@kbn/core-saved-objects-base-server-internal'; -import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; +import { type KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator'; import { DocumentMigrator } from './core/document_migrator'; import { ByteSizeValue } from '@kbn/config-schema'; import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts index 1d8d766d8ac88a..4e0588f04b0939 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/kibana_migrator.ts @@ -32,7 +32,7 @@ import { type MigrationResult, } from '@kbn/core-saved-objects-base-server-internal'; import { buildActiveMappings } from './core'; -import { DocumentMigrator, VersionedTransformer } from './core/document_migrator'; +import { DocumentMigrator, type VersionedTransformer } from './core/document_migrator'; import { createIndexMap } from './core/build_index_map'; import { runResilientMigrator } from './run_resilient_migrator'; import { migrateRawDocsSafely } from './core/migrate_raw_docs'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts index f21651d82f8f9f..ef9db961c81128 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/migrations_state_action_machine.ts @@ -15,9 +15,9 @@ import { getRequestDebugMeta, } from '@kbn/core-elasticsearch-client-server-internal'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import { Model, Next, stateActionMachine } from './state_action_machine'; +import { type Model, type Next, stateActionMachine } from './state_action_machine'; import { cleanup } from './migrations_state_machine_cleanup'; -import { ReindexSourceToTempTransform, ReindexSourceToTempIndexBulk, State } from './state'; +import type { ReindexSourceToTempTransform, ReindexSourceToTempIndexBulk, State } from './state'; interface StateTransitionLogMeta extends LogMeta { kibana: { diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts index 67b7e40dc5affc..be6090ef15cf16 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/model/model.test.ts @@ -42,9 +42,9 @@ import type { CheckUnknownDocumentsState, CalculateExcludeFiltersState, } from '../state'; -import { TransformErrorObjects, TransformSavedObjectDocumentError } from '../core'; -import { AliasAction, RetryableEsClientError } from '../actions'; -import { ResponseType } from '../next'; +import { type TransformErrorObjects, TransformSavedObjectDocumentError } from '../core'; +import type { AliasAction, RetryableEsClientError } from '../actions'; +import type { ResponseType } from '../next'; import { createInitialProgress } from './progress'; import { model } from './model'; diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts index 548c6b8c43b51b..cc4ee136739400 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/next.test.ts @@ -8,7 +8,7 @@ import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import { next } from './next'; -import { State } from './state'; +import type { State } from './state'; describe('migrations v2 next', () => { it.todo('when state.retryDelay > 0 delays execution of the next action'); diff --git a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts index cf7c74c305f2b8..13aa26a97f1df3 100644 --- a/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts +++ b/packages/core/saved-objects/core-saved-objects-migration-server-internal/src/types.ts @@ -8,7 +8,7 @@ import * as TaskEither from 'fp-ts/lib/TaskEither'; import type { SavedObjectsRawDoc } from '@kbn/core-saved-objects-server'; -import { DocumentsTransformFailed, DocumentsTransformSuccess } from './core'; +import type { DocumentsTransformFailed, DocumentsTransformSuccess } from './core'; /** @internal */ export type TransformRawDocs = ( diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 0f0d8a87363721..91c3fc10c710ea 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -18,7 +18,7 @@ import { typeRegistryInstanceMock, } from './saved_objects_service.test.mocks'; import { BehaviorSubject } from 'rxjs'; -import { RawPackageInfo, Env } from '@kbn/config'; +import { type RawPackageInfo, Env } from '@kbn/config'; import { ByteSizeValue } from '@kbn/config-schema'; import { REPO_ROOT } from '@kbn/utils'; import { getEnvOptions } from '@kbn/config-mocks'; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index a181865271f1f6..4d04cb5ef27712 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -23,7 +23,7 @@ import type { SavedObjectsType } from './saved_objects_type'; import type { ISavedObjectTypeRegistry } from './type_registry'; import type { ISavedObjectsExporter } from './export'; import type { ISavedObjectsImporter } from './import'; -import { SavedObjectsExtensions } from './extensions'; +import type { SavedObjectsExtensions } from './extensions'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to diff --git a/packages/core/saved-objects/core-saved-objects-server/src/security.ts b/packages/core/saved-objects/core-saved-objects-server/src/security.ts index 0623b5efb59d76..18eaae171d118b 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/security.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/security.ts @@ -7,7 +7,7 @@ */ import type { SavedObject } from '@kbn/core-saved-objects-common'; -import { EcsEventOutcome } from '@kbn/logging'; +import type { EcsEventOutcome } from '@kbn/logging'; /** * The CheckAuthorizationParams interface contains settings for checking From e72c6ad9af798afad8a2f42c94ef76e536d6d88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 4 Nov 2022 09:04:09 +0100 Subject: [PATCH 32/47] Adds exports for extention ID's from core/server to replace static in-line strings. --- src/core/server/index.ts | 6 ++++++ x-pack/plugins/actions/server/plugin.ts | 5 +++-- .../alerting/server/invalidate_pending_api_keys/task.ts | 3 ++- x-pack/plugins/alerting/server/rules_client_factory.test.ts | 6 +++--- x-pack/plugins/alerting/server/rules_client_factory.ts | 3 ++- x-pack/plugins/cases/server/client/factory.ts | 3 ++- .../server/crypto/encryption_key_rotation_service.test.ts | 5 +++-- .../server/crypto/encryption_key_rotation_service.ts | 3 ++- .../upgrade_agent_policy_schema_version.test.ts | 3 ++- .../upgrade_package_install_version.test.ts | 3 ++- x-pack/plugins/fleet/server/plugin.ts | 3 ++- x-pack/plugins/fleet/server/services/app_context.ts | 5 +++-- .../server/endpoint/lib/policy/license_watch.ts | 3 ++- .../endpoint/utils/create_internal_readonly_so_client.ts | 3 ++- .../lib/copy_to_spaces/lib/saved_objects_client_opts.ts | 3 ++- .../spaces/server/routes/api/external/copy_to_space.test.ts | 6 +++--- .../common/fixtures/plugins/alerts/server/routes.ts | 4 +++- 17 files changed, 44 insertions(+), 23 deletions(-) diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 5dc73e0afd2900..82b144afefb9d4 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -513,3 +513,9 @@ export type { PublicHttpServiceSetup as HttpServiceSetup, HttpServiceSetup as BaseHttpServiceSetup, }; + +export { + ENCRYPTION_EXTENSION_ID, + SECURITY_EXTENSION_ID, + SPACES_EXTENSION_ID, +} from '@kbn/core-saved-objects-server'; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 0b40ab4d23558b..275e315f6b4d5f 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -18,6 +18,7 @@ import { ElasticsearchServiceStart, SavedObjectsClientContract, SavedObjectsBulkGetObject, + SECURITY_EXTENSION_ID, } from '@kbn/core/server'; import { @@ -567,7 +568,7 @@ export class ActionsPlugin implements Plugin savedObjects.getScopedClient(request, { - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], includedHiddenTypes, }); @@ -630,7 +631,7 @@ export class ActionsPlugin implements Plugin { factory.create(request, savedObjectsService); expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request, { - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index a6406c3137462d..ddef586e747d8f 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -10,6 +10,7 @@ import { Logger, SavedObjectsServiceStart, PluginInitializerContext, + SECURITY_EXTENSION_ID, } from '@kbn/core/server'; import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; @@ -91,7 +92,7 @@ export class RulesClientFactory { ruleTypeRegistry: this.ruleTypeRegistry, minimumScheduleInterval: this.minimumScheduleInterval, unsecuredSavedObjectsClient: savedObjects.getScopedClient(request, { - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }), authorization: this.authorization.create(request), diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index 6bcbc669d83fdd..b1f57218f56f1d 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -13,6 +13,7 @@ import type { SavedObjectsClientContract, IBasePath, } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import type { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server'; import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; @@ -108,7 +109,7 @@ export class CasesClientFactory { includedHiddenTypes: SAVED_OBJECT_TYPES, // this tells the security plugin to not perform SO authorization and audit logging since we are handling // that manually using our Authorization class and audit logger. - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], }); const services = this.createServices({ diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts index cca1f021ad1b14..e71f89e941a706 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts @@ -10,6 +10,7 @@ import type { SavedObjectsClientContract, SavedObjectsServiceStart, } from '@kbn/core/server'; +import { ENCRYPTION_EXTENSION_ID } from '@kbn/core/server'; import { coreMock, httpServerMock, @@ -70,7 +71,7 @@ beforeEach(() => { mockRetrieveClient.find.mockResolvedValue({ total: 0, saved_objects: [], per_page: 0, page: 0 }); mockUpdateClient = savedObjectsClientMock.create(); mockSavedObjects.getScopedClient.mockImplementation((request, params) => - params?.excludedExtensions?.[0] === 'encryptedSavedObjects' + params?.excludedExtensions?.[0] === ENCRYPTION_EXTENSION_ID ? mockRetrieveClient : mockUpdateClient ); @@ -89,7 +90,7 @@ it('correctly setups Saved Objects clients', async () => { expect(mockSavedObjects.getScopedClient).toHaveBeenCalledTimes(2); expect(mockSavedObjects.getScopedClient).toHaveBeenCalledWith(mockRequest, { includedHiddenTypes: ['type-id-2', 'type-id-4'], - excludedExtensions: ['encryptedSavedObjects'], + excludedExtensions: [ENCRYPTION_EXTENSION_ID], }); expect(mockSavedObjects.getScopedClient).toHaveBeenCalledWith(mockRequest, { includedHiddenTypes: ['type-id-2', 'type-id-4'], diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts index becb5e6103213d..36c014781efc56 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts @@ -13,6 +13,7 @@ import type { SavedObjectsBulkUpdateObject, StartServicesAccessor, } from '@kbn/core/server'; +import { ENCRYPTION_EXTENSION_ID } from '@kbn/core/server'; import type { AuthenticatedUser, SecurityPluginSetup } from '@kbn/security-plugin/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; @@ -107,7 +108,7 @@ export class EncryptionKeyRotationService { const user = this.options.security?.authc.getCurrentUser(request) ?? undefined; const retrieveClient = savedObjects.getScopedClient(request, { includedHiddenTypes: registeredHiddenSavedObjectTypes, - excludedExtensions: ['encryptedSavedObjects'], + excludedExtensions: [ENCRYPTION_EXTENSION_ID], }); const updateClient = savedObjects.getScopedClient(request, { includedHiddenTypes: registeredHiddenSavedObjectTypes, diff --git a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts index d324f3a6fcd1cc..c03880bd178b18 100644 --- a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts @@ -12,6 +12,7 @@ import type { SavedObjectsClientContract, ElasticsearchClient, } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import * as kbnTestServer from '@kbn/core/test_helpers/kbn_server'; import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; @@ -120,7 +121,7 @@ describe('upgrade agent policy schema version', () => { beforeAll(async () => { soClient = kbnServer.coreStart.savedObjects.getScopedClient(fakeRequest, { - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], }); esClient = kbnServer.coreStart.elasticsearch.client.asInternalUser; }); diff --git a/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts b/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts index c6eb6873067c07..b1de8c9dff734e 100644 --- a/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts @@ -8,6 +8,7 @@ import Path from 'path'; import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import { loggerMock } from '@kbn/logging-mocks'; import * as kbnTestServer from '@kbn/core/test_helpers/kbn_server'; @@ -154,7 +155,7 @@ describe('Uprade package install version', () => { beforeAll(async () => { soClient = kbnServer.coreStart.savedObjects.getScopedClient(fakeRequest, { - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], }); const res = await soClient.find({ diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index c4160caa3846de..74bf76c45cf8b2 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -24,6 +24,7 @@ import type { ElasticsearchClient, SavedObjectsClientContract, } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import type { TelemetryPluginSetup, TelemetryPluginStart } from '@kbn/telemetry-plugin/server'; @@ -368,7 +369,7 @@ export class FleetPlugin get internalSoClient() { return appContextService .getSavedObjects() - .getScopedClient(request, { excludedExtensions: ['security'] }); + .getScopedClient(request, { excludedExtensions: [SECURITY_EXTENSION_ID] }); }, }, get spaceId() { diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index 6c241377081e82..a4e2f46902ff7a 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -8,12 +8,13 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; import { kibanaPackageJson } from '@kbn/utils'; -import type { KibanaRequest } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import type { ElasticsearchClient, SavedObjectsServiceStart, HttpServiceSetup, Logger, + KibanaRequest, } from '@kbn/core/server'; import type { PluginStart as DataPluginStart } from '@kbn/data-plugin/server'; @@ -161,7 +162,7 @@ class AppContextService { public getInternalUserSOClient(request: KibanaRequest) { // soClient as kibana internal users, be careful on how you use it, security is not enabled return appContextService.getSavedObjects().getScopedClient(request, { - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], }); } diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts index 98752ed496a0ed..5f48edf81e5f80 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts @@ -15,6 +15,7 @@ import type { SavedObjectsClientContract, SavedObjectsServiceStart, } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; @@ -61,7 +62,7 @@ export class PolicyWatcher { url: { href: {} }, raw: { req: { url: '/' } }, } as unknown as KibanaRequest; - return soStart.getScopedClient(fakeRequest, { excludedExtensions: ['security'] }); + return soStart.getScopedClient(fakeRequest, { excludedExtensions: [SECURITY_EXTENSION_ID] }); } public start(licenseService: LicenseService) { diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts index 9dc2f8341cb303..418a4340307bdd 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts @@ -10,6 +10,7 @@ import type { SavedObjectsClientContract, SavedObjectsServiceStart, } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import { EndpointError } from '../../../common/endpoint/errors'; type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract; @@ -46,7 +47,7 @@ export const createInternalReadonlySoClient = ( } as unknown as KibanaRequest; const internalSoClient = savedObjectsServiceStart.getScopedClient(fakeRequest, { - excludedExtensions: ['security'], + excludedExtensions: [SECURITY_EXTENSION_ID], }); return new Proxy(internalSoClient, { diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts index 6c5d91f075a555..18e186eb335423 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts @@ -6,7 +6,8 @@ */ import type { SavedObjectsClientProviderOptions } from '@kbn/core/server'; +import { SPACES_EXTENSION_ID } from '@kbn/core/server'; export const COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS: SavedObjectsClientProviderOptions = { - excludedExtensions: ['spaces'], + excludedExtensions: [SPACES_EXTENSION_ID], }; diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 5b8c43649de2e7..dabfe658f049ad 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -9,7 +9,7 @@ import * as Rx from 'rxjs'; import type { ObjectType } from '@kbn/config-schema'; import type { RouteValidatorConfig } from '@kbn/core/server'; -import { kibanaResponseFactory } from '@kbn/core/server'; +import { kibanaResponseFactory, SPACES_EXTENSION_ID } from '@kbn/core/server'; import { coreMock, httpServerMock, @@ -163,7 +163,7 @@ describe('copy to space', () => { await copyToSpace.routeHandler(mockRouteContext, request, kibanaResponseFactory); expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledWith(request, { - excludedExtensions: ['spaces'], + excludedExtensions: [SPACES_EXTENSION_ID], }); }); @@ -326,7 +326,7 @@ describe('copy to space', () => { await resolveConflicts.routeHandler(mockRouteContext, request, kibanaResponseFactory); expect(coreStart.savedObjects.getScopedClient).toHaveBeenCalledWith(request, { - excludedExtensions: ['spaces'], + excludedExtensions: [SPACES_EXTENSION_ID], }); }); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts index 3f68a76635f00b..c8f2899a445336 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts @@ -14,6 +14,8 @@ import { IKibanaResponse, Logger, SavedObject, + SECURITY_EXTENSION_ID, + SPACES_EXTENSION_ID, } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { InvalidatePendingApiKey } from '@kbn/alerting-plugin/server/types'; @@ -65,7 +67,7 @@ export function defineRoutes( const savedObjectsWithAlerts = await savedObjects.getScopedClient(req, { // Exclude the security and spaces wrappers to get around the safeguards those have in place to prevent // us from doing what we want to do - brute force replace the ApiKey - excludedExtensions: ['security', 'spaces'], + excludedExtensions: [SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID], includedHiddenTypes: ['alert'], }); From bca7189e9e7f62fafbc897c9b249fbd61fc32d38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 4 Nov 2022 09:17:58 +0100 Subject: [PATCH 33/47] Renames add_Extension functions to set_Extension --- .../src/plugin_context.ts | 6 +++--- .../src/saved_objects_service.ts | 12 ++++++------ .../src/saved_objects_service.mock.ts | 6 +++--- .../core-saved-objects-server/src/contracts.ts | 12 ++++++------ .../server/saved_objects/index.ts | 2 +- .../plugins/security/server/saved_objects/index.ts | 2 +- .../saved_objects/saved_objects_service.test.ts | 2 +- .../server/saved_objects/saved_objects_service.ts | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts index af74b45977dada..b937508094a84a 100644 --- a/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts +++ b/packages/core/plugins/core-plugins-server-internal/src/plugin_context.ts @@ -234,9 +234,9 @@ export function createPluginSetupContext( }, savedObjects: { setClientFactoryProvider: deps.savedObjects.setClientFactoryProvider, - addEncryptionExtension: deps.savedObjects.addEncryptionExtension, - addSecurityExtension: deps.savedObjects.addSecurityExtension, - addSpacesExtension: deps.savedObjects.addSpacesExtension, + setEncryptionExtension: deps.savedObjects.setEncryptionExtension, + setSecurityExtension: deps.savedObjects.setSecurityExtension, + setSpacesExtension: deps.savedObjects.setSpacesExtension, registerType: deps.savedObjects.registerType, getKibanaIndex: deps.savedObjects.getKibanaIndex, }, 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 a102ac01595f1c..b0c2694a75f990 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 @@ -159,27 +159,27 @@ export class SavedObjectsService } this.clientFactoryProvider = provider; }, - addEncryptionExtension: (factory) => { + setEncryptionExtension: (factory) => { if (this.started) { - throw new Error('cannot call `addEncryptionExtension` after service startup.'); + throw new Error('cannot call `setEncryptionExtension` after service startup.'); } if (this.encryptionExtensionFactory) { throw new Error('encryption extension is already set, and can only be set once'); } this.encryptionExtensionFactory = factory; }, - addSecurityExtension: (factory) => { + setSecurityExtension: (factory) => { if (this.started) { - throw new Error('cannot call `addSecurityExtension` after service startup.'); + throw new Error('cannot call `setSecurityExtension` after service startup.'); } if (this.securityExtensionFactory) { throw new Error('security extension is already set, and can only be set once'); } this.securityExtensionFactory = factory; }, - addSpacesExtension: (factory) => { + setSpacesExtension: (factory) => { if (this.started) { - throw new Error('cannot call `addSpacesExtension` after service startup.'); + throw new Error('cannot call `setSpacesExtension` after service startup.'); } if (this.spacesExtensionFactory) { throw new Error('spaces extension is already set, and can only be set once'); diff --git a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts index 2de6d7afcb6a1b..eccdb9ca16c54f 100644 --- a/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts +++ b/packages/core/saved-objects/core-saved-objects-server-mocks/src/saved_objects_service.mock.ts @@ -63,9 +63,9 @@ const createInternalStartContractMock = (typeRegistry?: jest.Mocked { const setupContract: jest.Mocked = { setClientFactoryProvider: jest.fn(), - addEncryptionExtension: jest.fn(), - addSecurityExtension: jest.fn(), - addSpacesExtension: jest.fn(), + setEncryptionExtension: jest.fn(), + setSecurityExtension: jest.fn(), + setSpacesExtension: jest.fn(), registerType: jest.fn(), getKibanaIndex: jest.fn(), }; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index 4d04cb5ef27712..b7ea286e905d96 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -69,19 +69,19 @@ export interface SavedObjectsServiceSetup { setClientFactoryProvider: (clientFactoryProvider: SavedObjectsClientFactoryProvider) => void; /** - * Add a {@link SavedObjectsEncryptionExtensionFactory encryption extension factory}. + * Sets the {@link SavedObjectsEncryptionExtensionFactory encryption extension factory}. */ - addEncryptionExtension: (factory: SavedObjectsEncryptionExtensionFactory) => void; + setEncryptionExtension: (factory: SavedObjectsEncryptionExtensionFactory) => void; /** - * Add a {@link SavedObjectsSecurityExtensionFactory security extension factory}. + * Sets the {@link SavedObjectsSecurityExtensionFactory security extension factory}. */ - addSecurityExtension: (factory: SavedObjectsSecurityExtensionFactory) => void; + setSecurityExtension: (factory: SavedObjectsSecurityExtensionFactory) => void; /** - * Add a {@link SavedObjectsSpacesExtensionFactory spaces extension factory}. + * Sets the {@link SavedObjectsSpacesExtensionFactory spaces extension factory}. */ - addSpacesExtension: (factory: SavedObjectsSpacesExtensionFactory) => void; + setSpacesExtension: (factory: SavedObjectsSpacesExtensionFactory) => void; /** * Register a {@link SavedObjectsType | savedObjects type} definition. diff --git a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts index b6d7621101d28a..6be6fae9f5d317 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/saved_objects/index.ts @@ -83,7 +83,7 @@ export function setupSavedObjects({ }: SetupSavedObjectsParams): ClientInstanciator { // Register custom saved object extension that will encrypt, decrypt and strip saved object // attributes where appropriate for any saved object repository request. - savedObjects.addEncryptionExtension(({ typeRegistry: baseTypeRegistry, request }) => { + savedObjects.setEncryptionExtension(({ typeRegistry: baseTypeRegistry, request }) => { return new SavedObjectsEncryptionExtension({ baseTypeRegistry, service, diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index a4599c7737b915..67e6d2294cc5c3 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -36,7 +36,7 @@ export function setupSavedObjects({ audit, authz, savedObjects }: SetupSavedObje } ); - savedObjects.addSecurityExtension(({ request }) => { + savedObjects.setSecurityExtension(({ request }) => { return authz.mode.useRbacForRequest(request) ? new SavedObjectsSecurityExtension({ actions: authz.actions, diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts index 475ee038e72c10..ef5f606da373c3 100644 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.test.ts @@ -38,7 +38,7 @@ describe('SpacesSavedObjectsService', () => { const service = new SpacesSavedObjectsService(); service.setup({ core, getSpacesService: () => spacesService }); - expect(core.savedObjects.addSpacesExtension).toHaveBeenCalledTimes(1); + expect(core.savedObjects.setSpacesExtension).toHaveBeenCalledTimes(1); }); }); }); diff --git a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts index 236dff48888b4e..75372491031863 100644 --- a/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts +++ b/x-pack/plugins/spaces/server/saved_objects/saved_objects_service.ts @@ -40,7 +40,7 @@ export class SpacesSavedObjectsService { }, }); - core.savedObjects.addSpacesExtension(({ request }) => { + core.savedObjects.setSpacesExtension(({ request }) => { const spacesService = getSpacesService(); const spacesClient = spacesService.createSpacesClient(request); const activeSpaceId = spacesService.getSpaceId(request); From 8627b0f7453d16933f31ccea93d53cd209282c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 4 Nov 2022 11:10:38 +0100 Subject: [PATCH 34/47] Adds base extension factory and undefined consistency to extension factories. --- .../src/lib/scoped_client_provider.test.ts | 6 ++-- .../src/lib/scoped_client_provider.ts | 18 +++++------ .../src/saved_objects_service.ts | 6 ++-- .../src/client_factory.ts | 30 ++++++++++++------- .../src/contracts.ts | 2 +- 5 files changed, 35 insertions(+), 27 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts index e7d5d6c46459b9..d691f3cf160311 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts @@ -28,9 +28,9 @@ import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-m interface Params { defaultClientFactory: SavedObjectsClientFactory; typeRegistry: ISavedObjectTypeRegistry; - encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory | undefined; - securityExtensionFactory: SavedObjectsSecurityExtensionFactory | undefined; - spacesExtensionFactory: SavedObjectsSpacesExtensionFactory | undefined; + encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory; + securityExtensionFactory: SavedObjectsSecurityExtensionFactory; + spacesExtensionFactory: SavedObjectsSpacesExtensionFactory; } function createClientProvider( diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index 2207ba8adf0c11..8b9dab7f2b65a0 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -42,9 +42,9 @@ export type ISavedObjectsClientProvider = Pick< export class SavedObjectsClientProvider { private _clientFactory: SavedObjectsClientFactory; private readonly _originalClientFactory: SavedObjectsClientFactory; - private readonly encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory | undefined; - private readonly securityExtensionFactory: SavedObjectsSecurityExtensionFactory | undefined; - private readonly spacesExtensionFactory: SavedObjectsSpacesExtensionFactory | undefined; + private readonly encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory; + private readonly securityExtensionFactory: SavedObjectsSecurityExtensionFactory; + private readonly spacesExtensionFactory: SavedObjectsSpacesExtensionFactory; private readonly _typeRegistry: ISavedObjectTypeRegistry; constructor({ @@ -56,9 +56,9 @@ export class SavedObjectsClientProvider { }: { defaultClientFactory: SavedObjectsClientFactory; typeRegistry: ISavedObjectTypeRegistry; - encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory | undefined; - securityExtensionFactory: SavedObjectsSecurityExtensionFactory | undefined; - spacesExtensionFactory: SavedObjectsSpacesExtensionFactory | undefined; + encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory; + securityExtensionFactory: SavedObjectsSecurityExtensionFactory; + spacesExtensionFactory: SavedObjectsSpacesExtensionFactory; }) { this._originalClientFactory = this._clientFactory = defaultClientFactory; this._typeRegistry = typeRegistry; @@ -100,9 +100,9 @@ export class SavedObjectsClientProvider { : undefined; }); - const encryptionExtension = extensions[0] as ISavedObjectsEncryptionExtension | undefined; - const securityExtension = extensions[1] as ISavedObjectsSecurityExtension | undefined; - const spacesExtension = extensions[2] as ISavedObjectsSpacesExtension | undefined; + const encryptionExtension = extensions[0] as ISavedObjectsEncryptionExtension; + const securityExtension = extensions[1] as ISavedObjectsSecurityExtension; + const spacesExtension = extensions[2] as ISavedObjectsSpacesExtension; return { encryptionExtension, 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 b0c2694a75f990..888a28ed03933e 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 @@ -93,9 +93,9 @@ export class SavedObjectsService private setupDeps?: SavedObjectsSetupDeps; private config?: SavedObjectConfig; private clientFactoryProvider?: SavedObjectsClientFactoryProvider; - private encryptionExtensionFactory?: SavedObjectsEncryptionExtensionFactory; - private securityExtensionFactory?: SavedObjectsSecurityExtensionFactory; - private spacesExtensionFactory?: SavedObjectsSpacesExtensionFactory; + private encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory; + private securityExtensionFactory: SavedObjectsSecurityExtensionFactory; + private spacesExtensionFactory: SavedObjectsSpacesExtensionFactory; private migrator$ = new Subject(); private typeRegistry = new SavedObjectTypeRegistry(); diff --git a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts index 7a3547c598c34f..4bb331551af68b 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts @@ -32,31 +32,37 @@ export type SavedObjectsClientFactory = ({ }) => SavedObjectsClientContract; /** - * Describes the factory used to create instances of the Saved Objects Encryption Extension. + * Describes the base Saved Objects Extension factory. * @public */ -export type SavedObjectsEncryptionExtensionFactory = (params: { +export type SavedObjectsExtensionFactory = (params: { typeRegistry: ISavedObjectTypeRegistry; request: KibanaRequest; -}) => ISavedObjectsEncryptionExtension; +}) => T; + +/** + * Describes the factory used to create instances of the Saved Objects Encryption Extension. + * @public + */ +export type SavedObjectsEncryptionExtensionFactory = + | SavedObjectsExtensionFactory + | undefined; /** * Describes the factory used to create instances of the Saved Objects Security Extension. * @public */ -export type SavedObjectsSecurityExtensionFactory = (params: { - typeRegistry: ISavedObjectTypeRegistry; - request: KibanaRequest; -}) => ISavedObjectsSecurityExtension | undefined; // May be undefined if RBAC is disabled +export type SavedObjectsSecurityExtensionFactory = + | SavedObjectsExtensionFactory + | undefined; // May be undefined if RBAC is disabled /** * Describes the factory used to create instances of the Saved Objects Spaces Extension. * @public */ -export type SavedObjectsSpacesExtensionFactory = (params: { - typeRegistry: ISavedObjectTypeRegistry; - request: KibanaRequest; -}) => ISavedObjectsSpacesExtension; +export type SavedObjectsSpacesExtensionFactory = + | SavedObjectsExtensionFactory + | undefined; /** * Provider to invoke to retrieve a {@link SavedObjectsClientFactory}. @@ -71,7 +77,9 @@ export type SavedObjectsClientFactoryProvider = ( * @public */ export interface SavedObjectsClientProviderOptions { + /** Array of hidden types to include */ includedHiddenTypes?: string[]; + /** array of extensions to exclude (ENCRYPTION_EXTENSION_ID | SECURITY_EXTENSION_ID | SPACES_EXTENSION_ID) */ excludedExtensions?: string[]; } diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index b7ea286e905d96..c5c0334ec773d9 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -158,7 +158,7 @@ export interface SavedObjectsServiceStart { */ getScopedClient: ( req: KibanaRequest, - options?: SavedObjectsClientProviderOptions // check these + options?: SavedObjectsClientProviderOptions ) => SavedObjectsClientContract; /** * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that From 1df2e3149afdf523b9f3710e03061fec3a7d0039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 4 Nov 2022 15:02:11 +0100 Subject: [PATCH 35/47] Adds enxtensions folder. Adds duplicate local extensions mock. General housekeeping. --- ...collect_multi_namespace_references.test.ts | 2 +- .../src/lib/internal_bulk_resolve.test.ts | 2 +- .../repository.encryption_extension.test.ts | 2 +- .../lib/repository.security_extension.test.ts | 2 +- .../lib/repository.spaces_extension.test.ts | 2 +- .../src/lib/repository.ts | 6 +-- .../src/lib/scoped_client_provider.test.ts | 2 +- .../src/lib/update_objects_spaces.test.ts | 2 +- .../mocks/saved_objects_extensions.mock.ts | 45 +++++++++++++++++++ .../tsconfig.json | 2 - .../core-saved-objects-server/index.ts | 12 ++--- .../src/client_factory.ts | 8 ++-- .../src/contracts.ts | 2 +- .../src/{ => extensions}/encryption.ts | 18 ++++---- .../src/{ => extensions}/extensions.ts | 6 +-- .../src/{ => extensions}/security.ts | 0 .../src/{ => extensions}/spaces.ts | 0 17 files changed, 77 insertions(+), 36 deletions(-) create mode 100644 packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts rename packages/core/saved-objects/core-saved-objects-server/src/{ => extensions}/encryption.ts (82%) rename packages/core/saved-objects/core-saved-objects-server/src/{ => extensions}/extensions.ts (84%) rename packages/core/saved-objects/core-saved-objects-server/src/{ => extensions}/security.ts (100%) rename packages/core/saved-objects/core-saved-objects-server/src/{ => extensions}/spaces.ts (100%) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index 24ce2bc5ffbb79..a9203b4d1d43df 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -39,7 +39,7 @@ import { setupEnforceSuccess, setupRedactPassthrough, } from '../test_helpers/repository.test.common'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; const SPACES = ['default', 'another-space']; const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 }; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index 9eeab939f7d7c9..8aa61c08907dc7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -43,7 +43,7 @@ import { setupEnforceSuccess, setupRedactPassthrough, } from '../test_helpers/repository.test.common'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; const VERSION_PROPS = { _seq_no: 1, _primary_term: 1 }; const OBJ_TYPE = 'obj-type'; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts index 2a15bdd39568d2..a297d6b5a2b7e3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.encryption_extension.test.ts @@ -44,7 +44,7 @@ import { TypeIdTuple, updateSuccess, } from '../test_helpers/repository.test.common'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index f347eab609af12..1f8b41a45a50b2 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -67,7 +67,7 @@ import { createBulkDeleteSuccessStatus, namespaceMapsAreEqual, } from '../test_helpers/repository.test.common'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts index 8484dba10ca565..c5c90b048dc317 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.spaces_extension.test.ts @@ -57,7 +57,7 @@ import { generateIndexPatternSearchResults, bulkDeleteSuccess, } from '../test_helpers/repository.test.common'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 7d5f412abd5f2b..9e34ba195a30d2 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -207,9 +207,9 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { private _registry: ISavedObjectTypeRegistry; private _allowedTypes: string[]; private readonly client: RepositoryEsClient; - private readonly _encryptionExtension: ISavedObjectsEncryptionExtension | undefined; - private readonly _securityExtension: ISavedObjectsSecurityExtension | undefined; - private readonly _spacesExtension: ISavedObjectsSpacesExtension | undefined; + private readonly _encryptionExtension?: ISavedObjectsEncryptionExtension; + private readonly _securityExtension?: ISavedObjectsSecurityExtension; + private readonly _spacesExtension?: ISavedObjectsSpacesExtension; private _serializer: SavedObjectsSerializer; private _logger: Logger; diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts index d691f3cf160311..03a554ae02fb3a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.test.ts @@ -20,7 +20,7 @@ import { SPACES_EXTENSION_ID, } from '@kbn/core-saved-objects-server'; import type { KibanaRequest } from '@kbn/core-http-server'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; /** * @internal only used for unit tests diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 38f4bff0800ad7..52f7a7346e3ab7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -38,7 +38,7 @@ import { setupEnforceSuccess, setupRedactPassthrough, } from '../test_helpers/repository.test.common'; -import { savedObjectsExtensionsMock } from '@kbn/core-saved-objects-api-server-mocks'; +import { savedObjectsExtensionsMock } from '../mocks/saved_objects_extensions.mock'; type SetupParams = Partial< Pick diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts new file mode 100644 index 00000000000000..f4308ee6254c7c --- /dev/null +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/mocks/saved_objects_extensions.mock.ts @@ -0,0 +1,45 @@ +/* + * 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 type { + ISavedObjectsEncryptionExtension, + ISavedObjectsSecurityExtension, + ISavedObjectsSpacesExtension, + SavedObjectsExtensions, +} from '@kbn/core-saved-objects-server'; + +const createEncryptionExtension = (): jest.Mocked => ({ + isEncryptableType: jest.fn(), + decryptOrStripResponseAttributes: jest.fn(), + encryptAttributes: jest.fn(), +}); + +const createSecurityExtension = (): jest.Mocked => ({ + checkAuthorization: jest.fn(), + enforceAuthorization: jest.fn(), + addAuditEvent: jest.fn(), + redactNamespaces: jest.fn(), +}); + +const createSpacesExtension = (): jest.Mocked => ({ + getCurrentNamespace: jest.fn(), + getSearchableNamespaces: jest.fn(), +}); + +const create = (): jest.Mocked => ({ + encryptionExtension: createEncryptionExtension(), + securityExtension: createSecurityExtension(), + spacesExtension: createSpacesExtension(), +}); + +export const savedObjectsExtensionsMock = { + create, + createEncryptionExtension, + createSecurityExtension, + createSpacesExtension, +}; diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json b/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json index 3fe98195b374a7..4582562d6c9bb4 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json +++ b/packages/core/saved-objects/core-saved-objects-server-internal/tsconfig.json @@ -4,8 +4,6 @@ "declaration": true, "emitDeclarationOnly": true, "outDir": "target_types", - "rootDir": ".", - "stripInternal": false, "types": [ "jest", "node" diff --git a/packages/core/saved-objects/core-saved-objects-server/index.ts b/packages/core/saved-objects/core-saved-objects-server/index.ts index 6e7e9791ce5793..265cf4549c6638 100644 --- a/packages/core/saved-objects/core-saved-objects-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server/index.ts @@ -65,7 +65,7 @@ export type { } from './src/serialization'; export type { ISavedObjectTypeRegistry } from './src/type_registry'; export type { SavedObjectsValidationMap, SavedObjectsValidationSpec } from './src/validation'; -export type { ISavedObjectsEncryptionExtension, EncryptedObjectDescriptor } from './src/encryption'; +export type { ISavedObjectsEncryptionExtension, EncryptedObjectDescriptor } from './src/extensions/encryption'; export type { CheckAuthorizationParams, AuthorizationTypeEntry, @@ -75,12 +75,12 @@ export type { AddAuditEventParams, RedactNamespacesParams, ISavedObjectsSecurityExtension, -} from './src/security'; -export { AuditAction } from './src/security'; -export type { ISavedObjectsSpacesExtension } from './src/spaces'; -export type { SavedObjectsExtensions } from './src/extensions'; +} from './src/extensions/security'; +export { AuditAction } from './src/extensions/security'; +export type { ISavedObjectsSpacesExtension } from './src/extensions/spaces'; +export type { SavedObjectsExtensions } from './src/extensions/extensions'; export { ENCRYPTION_EXTENSION_ID, SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID, -} from './src/extensions'; +} from './src/extensions/extensions'; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts index 4bb331551af68b..d63ded2ab46f7c 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts @@ -11,10 +11,10 @@ import type { SavedObjectsClientContract, ISavedObjectsRepository, } from '@kbn/core-saved-objects-api-server'; -import type { ISavedObjectsEncryptionExtension } from './encryption'; -import type { SavedObjectsExtensions } from './extensions'; -import type { ISavedObjectsSecurityExtension } from './security'; -import type { ISavedObjectsSpacesExtension } from './spaces'; +import type { ISavedObjectsEncryptionExtension } from './extensions/encryption'; +import type { SavedObjectsExtensions } from './extensions/extensions'; +import type { ISavedObjectsSecurityExtension } from './extensions/security'; +import type { ISavedObjectsSpacesExtension } from './extensions/spaces'; import type { ISavedObjectTypeRegistry } from './type_registry'; /** diff --git a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts index c5c0334ec773d9..6c4dcbc3f6b40d 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/contracts.ts @@ -23,7 +23,7 @@ import type { SavedObjectsType } from './saved_objects_type'; import type { ISavedObjectTypeRegistry } from './type_registry'; import type { ISavedObjectsExporter } from './export'; import type { ISavedObjectsImporter } from './import'; -import type { SavedObjectsExtensions } from './extensions'; +import type { SavedObjectsExtensions } from './extensions/extensions'; /** * Saved Objects is Kibana's data persistence mechanism allowing plugins to diff --git a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions/encryption.ts similarity index 82% rename from packages/core/saved-objects/core-saved-objects-server/src/encryption.ts rename to packages/core/saved-objects/core-saved-objects-server/src/extensions/encryption.ts index 124406fc659bb9..6dcdac347faca1 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/encryption.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/extensions/encryption.ts @@ -13,17 +13,15 @@ import type { SavedObject } from '@kbn/core-saved-objects-common'; * an object to be encrypted or decrpyted. */ export interface EncryptedObjectDescriptor { - /** - * The Saved Object type - */ + /** The Saved Object type */ type: string; - /** - * The Saved Object ID - */ + /** The Saved Object ID */ id: string; - /** - * The namespace where the Saved Object resides - */ + /** Namespace for use in index migration... + * If the object is being decrypted during index migration, the object was previously + * encrypted with its namespace in the descriptor portion of the AAD; on the other hand, + * if the object is being decrypted during object migration, the object was never encrypted + * with its namespace in the descriptor portion of the AAD. */ namespace?: string; } @@ -61,7 +59,7 @@ export interface ISavedObjectsEncryptionExtension { * @returns T, encrypted attributes */ encryptAttributes: >( - descriptor: { id: string; type: string; namespace: string | undefined }, + descriptor: EncryptedObjectDescriptor, attributes: T ) => Promise; } diff --git a/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions/extensions.ts similarity index 84% rename from packages/core/saved-objects/core-saved-objects-server/src/extensions.ts rename to packages/core/saved-objects/core-saved-objects-server/src/extensions/extensions.ts index a51184b61a113a..f94af85d4ae26b 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/extensions.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/extensions/extensions.ts @@ -17,9 +17,9 @@ import type { ISavedObjectsSpacesExtension } from './spaces'; * security, and spaces features. */ export interface SavedObjectsExtensions { - encryptionExtension: ISavedObjectsEncryptionExtension | undefined; - securityExtension: ISavedObjectsSecurityExtension | undefined; - spacesExtension: ISavedObjectsSpacesExtension | undefined; + encryptionExtension?: ISavedObjectsEncryptionExtension; + securityExtension?: ISavedObjectsSecurityExtension; + spacesExtension?: ISavedObjectsSpacesExtension; } export const ENCRYPTION_EXTENSION_ID = 'encryptedSavedObjects' as const; diff --git a/packages/core/saved-objects/core-saved-objects-server/src/security.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions/security.ts similarity index 100% rename from packages/core/saved-objects/core-saved-objects-server/src/security.ts rename to packages/core/saved-objects/core-saved-objects-server/src/extensions/security.ts diff --git a/packages/core/saved-objects/core-saved-objects-server/src/spaces.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions/spaces.ts similarity index 100% rename from packages/core/saved-objects/core-saved-objects-server/src/spaces.ts rename to packages/core/saved-objects/core-saved-objects-server/src/extensions/spaces.ts From eb39e382ffc1b3009562edef7fc50298ee0b9e19 Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:12:39 +0000 Subject: [PATCH 36/47] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../core/saved-objects/core-saved-objects-server/index.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/saved-objects/core-saved-objects-server/index.ts b/packages/core/saved-objects/core-saved-objects-server/index.ts index 265cf4549c6638..9018cc5b8b17f9 100644 --- a/packages/core/saved-objects/core-saved-objects-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server/index.ts @@ -65,7 +65,10 @@ export type { } from './src/serialization'; export type { ISavedObjectTypeRegistry } from './src/type_registry'; export type { SavedObjectsValidationMap, SavedObjectsValidationSpec } from './src/validation'; -export type { ISavedObjectsEncryptionExtension, EncryptedObjectDescriptor } from './src/extensions/encryption'; +export type { + ISavedObjectsEncryptionExtension, + EncryptedObjectDescriptor, +} from './src/extensions/encryption'; export type { CheckAuthorizationParams, AuthorizationTypeEntry, From f7dfda09354a09b667e18ec1b6dcee72c4bff2aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Fri, 4 Nov 2022 15:53:17 +0100 Subject: [PATCH 37/47] Fixes factory definitions. --- .../src/lib/scoped_client_provider.ts | 12 ++++++------ .../src/saved_objects_service.ts | 6 +++--- .../core-saved-objects-server/index.ts | 5 ++++- .../src/client_factory.ts | 18 +++++++++--------- 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index 8b9dab7f2b65a0..0a248ffa72549c 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -42,9 +42,9 @@ export type ISavedObjectsClientProvider = Pick< export class SavedObjectsClientProvider { private _clientFactory: SavedObjectsClientFactory; private readonly _originalClientFactory: SavedObjectsClientFactory; - private readonly encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory; - private readonly securityExtensionFactory: SavedObjectsSecurityExtensionFactory; - private readonly spacesExtensionFactory: SavedObjectsSpacesExtensionFactory; + private readonly encryptionExtensionFactory?: SavedObjectsEncryptionExtensionFactory; + private readonly securityExtensionFactory?: SavedObjectsSecurityExtensionFactory; + private readonly spacesExtensionFactory?: SavedObjectsSpacesExtensionFactory; private readonly _typeRegistry: ISavedObjectTypeRegistry; constructor({ @@ -56,9 +56,9 @@ export class SavedObjectsClientProvider { }: { defaultClientFactory: SavedObjectsClientFactory; typeRegistry: ISavedObjectTypeRegistry; - encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory; - securityExtensionFactory: SavedObjectsSecurityExtensionFactory; - spacesExtensionFactory: SavedObjectsSpacesExtensionFactory; + encryptionExtensionFactory?: SavedObjectsEncryptionExtensionFactory; + securityExtensionFactory?: SavedObjectsSecurityExtensionFactory; + spacesExtensionFactory?: SavedObjectsSpacesExtensionFactory; }) { this._originalClientFactory = this._clientFactory = defaultClientFactory; this._typeRegistry = typeRegistry; 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 888a28ed03933e..b0c2694a75f990 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 @@ -93,9 +93,9 @@ export class SavedObjectsService private setupDeps?: SavedObjectsSetupDeps; private config?: SavedObjectConfig; private clientFactoryProvider?: SavedObjectsClientFactoryProvider; - private encryptionExtensionFactory: SavedObjectsEncryptionExtensionFactory; - private securityExtensionFactory: SavedObjectsSecurityExtensionFactory; - private spacesExtensionFactory: SavedObjectsSpacesExtensionFactory; + private encryptionExtensionFactory?: SavedObjectsEncryptionExtensionFactory; + private securityExtensionFactory?: SavedObjectsSecurityExtensionFactory; + private spacesExtensionFactory?: SavedObjectsSpacesExtensionFactory; private migrator$ = new Subject(); private typeRegistry = new SavedObjectTypeRegistry(); diff --git a/packages/core/saved-objects/core-saved-objects-server/index.ts b/packages/core/saved-objects/core-saved-objects-server/index.ts index 265cf4549c6638..9018cc5b8b17f9 100644 --- a/packages/core/saved-objects/core-saved-objects-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server/index.ts @@ -65,7 +65,10 @@ export type { } from './src/serialization'; export type { ISavedObjectTypeRegistry } from './src/type_registry'; export type { SavedObjectsValidationMap, SavedObjectsValidationSpec } from './src/validation'; -export type { ISavedObjectsEncryptionExtension, EncryptedObjectDescriptor } from './src/extensions/encryption'; +export type { + ISavedObjectsEncryptionExtension, + EncryptedObjectDescriptor, +} from './src/extensions/encryption'; export type { CheckAuthorizationParams, AuthorizationTypeEntry, diff --git a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts index d63ded2ab46f7c..c77d5b6f60327b 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/client_factory.ts @@ -44,25 +44,25 @@ export type SavedObjectsExtensionFactory = (params: { * Describes the factory used to create instances of the Saved Objects Encryption Extension. * @public */ -export type SavedObjectsEncryptionExtensionFactory = - | SavedObjectsExtensionFactory - | undefined; +export type SavedObjectsEncryptionExtensionFactory = SavedObjectsExtensionFactory< + ISavedObjectsEncryptionExtension | undefined +>; /** * Describes the factory used to create instances of the Saved Objects Security Extension. * @public */ -export type SavedObjectsSecurityExtensionFactory = - | SavedObjectsExtensionFactory - | undefined; // May be undefined if RBAC is disabled +export type SavedObjectsSecurityExtensionFactory = SavedObjectsExtensionFactory< + ISavedObjectsSecurityExtension | undefined +>; // May be undefined if RBAC is disabled /** * Describes the factory used to create instances of the Saved Objects Spaces Extension. * @public */ -export type SavedObjectsSpacesExtensionFactory = - | SavedObjectsExtensionFactory - | undefined; +export type SavedObjectsSpacesExtensionFactory = SavedObjectsExtensionFactory< + ISavedObjectsSpacesExtension | undefined +>; /** * Provider to invoke to retrieve a {@link SavedObjectsClientFactory}. From 1af74163550cde8ff8dd2a20467632d661cdfbb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Mon, 7 Nov 2022 16:53:54 +0100 Subject: [PATCH 38/47] Changes imports for extension ID's to exports from packages. Removes re-export of extension ID's. --- .../src/simple_saved_object.ts | 2 +- src/core/server/index.ts | 6 ------ x-pack/plugins/actions/server/plugin.ts | 3 +-- .../alerting/server/invalidate_pending_api_keys/task.ts | 2 +- x-pack/plugins/alerting/server/rules_client_factory.test.ts | 3 ++- x-pack/plugins/alerting/server/rules_client_factory.ts | 2 +- x-pack/plugins/cases/server/client/factory.ts | 2 +- .../server/crypto/encryption_key_rotation_service.test.ts | 2 +- .../server/crypto/encryption_key_rotation_service.ts | 2 +- .../upgrade_agent_policy_schema_version.test.ts | 3 ++- .../upgrade_package_install_version.test.ts | 3 ++- x-pack/plugins/fleet/server/plugin.ts | 3 ++- x-pack/plugins/fleet/server/services/app_context.ts | 3 ++- .../server/endpoint/lib/policy/license_watch.ts | 2 +- .../endpoint/utils/create_internal_readonly_so_client.ts | 2 +- .../lib/copy_to_spaces/lib/saved_objects_client_opts.ts | 2 +- .../spaces/server/routes/api/external/copy_to_space.test.ts | 3 ++- .../common/fixtures/plugins/alerts/server/routes.ts | 3 +-- 18 files changed, 23 insertions(+), 25 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts index 8edb1347d6a889..2932d4f5675a85 100644 --- a/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts +++ b/packages/core/saved-objects/core-saved-objects-api-browser/src/simple_saved_object.ts @@ -22,7 +22,7 @@ export interface SimpleSavedObject { attributes: T; /** version of the saved object */ _version?: SavedObjectType['version']; - /** ID of the saved object, unique per type */ + /** ID of the saved object */ id: SavedObjectType['id']; /** Type of the saved object */ type: SavedObjectType['type']; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 82b144afefb9d4..5dc73e0afd2900 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -513,9 +513,3 @@ export type { PublicHttpServiceSetup as HttpServiceSetup, HttpServiceSetup as BaseHttpServiceSetup, }; - -export { - ENCRYPTION_EXTENSION_ID, - SECURITY_EXTENSION_ID, - SPACES_EXTENSION_ID, -} from '@kbn/core-saved-objects-server'; diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 275e315f6b4d5f..c2e52061e35d23 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -18,9 +18,8 @@ import { ElasticsearchServiceStart, SavedObjectsClientContract, SavedObjectsBulkGetObject, - SECURITY_EXTENSION_ID, } from '@kbn/core/server'; - +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { EncryptedSavedObjectsClient, EncryptedSavedObjectsPluginSetup, diff --git a/x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts b/x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts index 8e3cef7a644ad9..f1e97c9fd93d02 100644 --- a/x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts +++ b/x-pack/plugins/alerting/server/invalidate_pending_api_keys/task.ts @@ -11,7 +11,6 @@ import { SavedObjectsFindResponse, KibanaRequest, SavedObjectsClientContract, - SECURITY_EXTENSION_ID, } from '@kbn/core/server'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { InvalidateAPIKeysParams, SecurityPluginStart } from '@kbn/security-plugin/server'; @@ -20,6 +19,7 @@ import { TaskManagerSetupContract, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { InvalidateAPIKeyResult } from '../rules_client'; import { AlertingConfig } from '../config'; import { timePeriodBeforeDate } from '../lib/get_cadence'; diff --git a/x-pack/plugins/alerting/server/rules_client_factory.test.ts b/x-pack/plugins/alerting/server/rules_client_factory.test.ts index df1990e23f805d..14363fcabf4407 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.test.ts @@ -9,7 +9,7 @@ import { Request } from '@hapi/hapi'; import { RulesClientFactory, RulesClientFactoryOpts } from './rules_client_factory'; import { ruleTypeRegistryMock } from './rule_type_registry.mock'; import { taskManagerMock } from '@kbn/task-manager-plugin/server/mocks'; -import { CoreKibanaRequest, SECURITY_EXTENSION_ID } from '@kbn/core/server'; +import { CoreKibanaRequest } from '@kbn/core/server'; import { savedObjectsClientMock, savedObjectsServiceMock, @@ -25,6 +25,7 @@ import { alertingAuthorizationMock } from './authorization/alerting_authorizatio import { alertingAuthorizationClientFactoryMock } from './alerting_authorization_client_factory.mock'; import { AlertingAuthorization } from './authorization'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; jest.mock('./rules_client'); jest.mock('./authorization/alerting_authorization'); diff --git a/x-pack/plugins/alerting/server/rules_client_factory.ts b/x-pack/plugins/alerting/server/rules_client_factory.ts index ddef586e747d8f..e6d77b618f0b00 100644 --- a/x-pack/plugins/alerting/server/rules_client_factory.ts +++ b/x-pack/plugins/alerting/server/rules_client_factory.ts @@ -10,13 +10,13 @@ import { Logger, SavedObjectsServiceStart, PluginInitializerContext, - SECURITY_EXTENSION_ID, } from '@kbn/core/server'; import { PluginStartContract as ActionsPluginStartContract } from '@kbn/actions-plugin/server'; import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import { EncryptedSavedObjectsClient } from '@kbn/encrypted-saved-objects-plugin/server'; import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; import { IEventLogClientService, IEventLogger } from '@kbn/event-log-plugin/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { RuleTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { RulesClient } from './rules_client'; import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; diff --git a/x-pack/plugins/cases/server/client/factory.ts b/x-pack/plugins/cases/server/client/factory.ts index b1f57218f56f1d..fa263d6baefb5b 100644 --- a/x-pack/plugins/cases/server/client/factory.ts +++ b/x-pack/plugins/cases/server/client/factory.ts @@ -13,7 +13,7 @@ import type { SavedObjectsClientContract, IBasePath, } from '@kbn/core/server'; -import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/server'; import type { PluginStartContract as FeaturesPluginStart } from '@kbn/features-plugin/server'; import type { PluginStartContract as ActionsPluginStart } from '@kbn/actions-plugin/server'; diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts index e71f89e941a706..acd5c20fd1efd0 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.test.ts @@ -5,12 +5,12 @@ * 2.0. */ +import { ENCRYPTION_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { SavedObject, SavedObjectsClientContract, SavedObjectsServiceStart, } from '@kbn/core/server'; -import { ENCRYPTION_EXTENSION_ID } from '@kbn/core/server'; import { coreMock, httpServerMock, diff --git a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts index 36c014781efc56..9de649a8cf5e78 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/crypto/encryption_key_rotation_service.ts @@ -5,6 +5,7 @@ * 2.0. */ +import { ENCRYPTION_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { ISavedObjectTypeRegistry, KibanaRequest, @@ -13,7 +14,6 @@ import type { SavedObjectsBulkUpdateObject, StartServicesAccessor, } from '@kbn/core/server'; -import { ENCRYPTION_EXTENSION_ID } from '@kbn/core/server'; import type { AuthenticatedUser, SecurityPluginSetup } from '@kbn/security-plugin/server'; import type { PublicMethodsOf } from '@kbn/utility-types'; diff --git a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts index c03880bd178b18..52367311cd48a4 100644 --- a/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/upgrade_agent_policy_schema_version.test.ts @@ -12,10 +12,11 @@ import type { SavedObjectsClientContract, ElasticsearchClient, } from '@kbn/core/server'; -import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import * as kbnTestServer from '@kbn/core/test_helpers/kbn_server'; import type { SearchTotalHits } from '@elastic/elasticsearch/lib/api/types'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; + import { AGENT_POLICY_SAVED_OBJECT_TYPE, FLEET_AGENT_POLICIES_SCHEMA_VERSION } from '../constants'; import { upgradeAgentPolicySchemaVersion } from '../services/setup/upgrade_agent_policy_schema_version'; import { AGENT_POLICY_INDEX } from '../../common'; diff --git a/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts b/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts index b1de8c9dff734e..8c9cbeb27fc750 100644 --- a/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts +++ b/x-pack/plugins/fleet/server/integration_tests/upgrade_package_install_version.test.ts @@ -8,11 +8,12 @@ import Path from 'path'; import type { KibanaRequest, SavedObjectsClientContract } from '@kbn/core/server'; -import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import { loggerMock } from '@kbn/logging-mocks'; import * as kbnTestServer from '@kbn/core/test_helpers/kbn_server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; + import { upgradePackageInstallVersion } from '../services/setup/upgrade_package_install_version'; import { FLEET_INSTALL_FORMAT_VERSION, diff --git a/x-pack/plugins/fleet/server/plugin.ts b/x-pack/plugins/fleet/server/plugin.ts index 74bf76c45cf8b2..4beb4e32253cbc 100644 --- a/x-pack/plugins/fleet/server/plugin.ts +++ b/x-pack/plugins/fleet/server/plugin.ts @@ -24,7 +24,6 @@ import type { ElasticsearchClient, SavedObjectsClientContract, } from '@kbn/core/server'; -import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import type { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server'; import type { TelemetryPluginSetup, TelemetryPluginStart } from '@kbn/telemetry-plugin/server'; @@ -49,6 +48,8 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/server'; import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; + import type { FleetConfigType } from '../common/types'; import type { FleetAuthz } from '../common'; import type { ExperimentalFeatures } from '../common/experimental_features'; diff --git a/x-pack/plugins/fleet/server/services/app_context.ts b/x-pack/plugins/fleet/server/services/app_context.ts index a4e2f46902ff7a..adcee896052745 100644 --- a/x-pack/plugins/fleet/server/services/app_context.ts +++ b/x-pack/plugins/fleet/server/services/app_context.ts @@ -8,7 +8,6 @@ import type { Observable } from 'rxjs'; import { BehaviorSubject } from 'rxjs'; import { kibanaPackageJson } from '@kbn/utils'; -import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import type { ElasticsearchClient, SavedObjectsServiceStart, @@ -29,6 +28,8 @@ import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; + import type { FleetConfigType } from '../../common/types'; import type { ExperimentalFeatures } from '../../common/experimental_features'; import type { diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts index 5f48edf81e5f80..195c8509e60d5e 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.ts @@ -15,11 +15,11 @@ import type { SavedObjectsClientContract, SavedObjectsServiceStart, } from '@kbn/core/server'; -import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import type { PackagePolicy } from '@kbn/fleet-plugin/common'; import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '@kbn/fleet-plugin/common'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; import type { ILicense } from '@kbn/licensing-plugin/common/types'; +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { isEndpointPolicyValidForLicense, unsetPolicyFeaturesAccordingToLicenseLevel, diff --git a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts index 418a4340307bdd..d8bf7badec846b 100644 --- a/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts +++ b/x-pack/plugins/security_solution/server/endpoint/utils/create_internal_readonly_so_client.ts @@ -5,12 +5,12 @@ * 2.0. */ +import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { KibanaRequest, SavedObjectsClientContract, SavedObjectsServiceStart, } from '@kbn/core/server'; -import { SECURITY_EXTENSION_ID } from '@kbn/core/server'; import { EndpointError } from '../../../common/endpoint/errors'; type SavedObjectsClientContractKeys = keyof SavedObjectsClientContract; diff --git a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts index 18e186eb335423..122d1dbd182b45 100644 --- a/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts +++ b/x-pack/plugins/spaces/server/lib/copy_to_spaces/lib/saved_objects_client_opts.ts @@ -5,8 +5,8 @@ * 2.0. */ +import { SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { SavedObjectsClientProviderOptions } from '@kbn/core/server'; -import { SPACES_EXTENSION_ID } from '@kbn/core/server'; export const COPY_TO_SPACES_SAVED_OBJECTS_CLIENT_OPTS: SavedObjectsClientProviderOptions = { excludedExtensions: [SPACES_EXTENSION_ID], diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index dabfe658f049ad..f912ca209f3637 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -8,8 +8,9 @@ import * as Rx from 'rxjs'; import type { ObjectType } from '@kbn/config-schema'; +import { SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import type { RouteValidatorConfig } from '@kbn/core/server'; -import { kibanaResponseFactory, SPACES_EXTENSION_ID } from '@kbn/core/server'; +import { kibanaResponseFactory } from '@kbn/core/server'; import { coreMock, httpServerMock, diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts index c8f2899a445336..c93401b3bdae8c 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/server/routes.ts @@ -14,8 +14,6 @@ import { IKibanaResponse, Logger, SavedObject, - SECURITY_EXTENSION_ID, - SPACES_EXTENSION_ID, } from '@kbn/core/server'; import { schema } from '@kbn/config-schema'; import { InvalidatePendingApiKey } from '@kbn/alerting-plugin/server/types'; @@ -25,6 +23,7 @@ import { TaskInstance, TaskManagerStartContract, } from '@kbn/task-manager-plugin/server'; +import { SECURITY_EXTENSION_ID, SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server'; import { FixtureStartDeps } from './plugin'; import { retryIfConflicts } from './lib/retry_if_conflicts'; From bd1a1a525a5f9361098112e1ea1c83b0746eea97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Mon, 14 Nov 2022 12:03:40 +0100 Subject: [PATCH 39/47] Adds unit tests for extension set methods of saved objects service. --- .../src/saved_objects_service.test.ts | 133 +++++++++++++++++- 1 file changed, 131 insertions(+), 2 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 83b0759cc4a05e..9f37c3dbf2e86d 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -26,10 +26,19 @@ import { docLinksServiceMock } from '@kbn/core-doc-links-server-mocks'; import { nodeServiceMock } from '@kbn/core-node-server-mocks'; import { mockCoreContext } from '@kbn/core-base-server-mocks'; import { httpServiceMock, httpServerMock } from '@kbn/core-http-server-mocks'; -import type { SavedObjectsClientFactoryProvider } from '@kbn/core-saved-objects-server'; +import { + SavedObjectsClientFactoryProvider, + SavedObjectsEncryptionExtensionFactory, + SavedObjectsSecurityExtensionFactory, + SavedObjectsSpacesExtensionFactory, +} from '@kbn/core-saved-objects-server'; import { configServiceMock } from '@kbn/config-mocks'; import type { NodesVersionCompatibility } from '@kbn/core-elasticsearch-server-internal'; -import { SavedObjectsRepository } from '@kbn/core-saved-objects-api-server-internal'; +import { + ISavedObjectsClientProvider, + SavedObjectsClientProvider, + SavedObjectsRepository, +} from '@kbn/core-saved-objects-api-server-internal'; import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks'; import { SavedObjectsService } from './saved_objects_service'; @@ -42,6 +51,7 @@ import { import { registerCoreObjectTypes } from './object_types'; import { getSavedObjectsDeprecationsProvider } from './deprecations'; +import exp from 'constants'; jest.mock('./object_types'); jest.mock('./deprecations'); @@ -192,6 +202,125 @@ describe('SavedObjectsService', () => { }); }); + describe('#extensions', () => { + it('registers the encryption extension to the clientProvider', async () => { + const coreContext = createCoreContext(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + const encryptionExtension: jest.Mocked = jest.fn(); + setup.setEncryptionExtension(encryptionExtension); + + await soService.start(createStartDeps()); + + expect(SavedObjectsClientProvider).toHaveBeenCalledTimes(1); + expect(SavedObjectsClientProvider).toHaveBeenCalledWith( + expect.objectContaining({ + encryptionExtensionFactory: encryptionExtension, + securityExtensionFactory: undefined, + spacesExtensionFactory: undefined, + }) + ); + }); + + it('registers the security extension to the clientProvider', async () => { + const coreContext = createCoreContext(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + const securityExtension: jest.Mocked = jest.fn(); + setup.setSecurityExtension(securityExtension); + + await soService.start(createStartDeps()); + + expect(SavedObjectsClientProvider).toHaveBeenCalledTimes(1); + expect(SavedObjectsClientProvider).toHaveBeenCalledWith( + expect.objectContaining({ + encryptionExtensionFactory: undefined, + securityExtensionFactory: securityExtension, + spacesExtensionFactory: undefined, + }) + ); + }); + + it('registers the spaces extension to the clientProvider', async () => { + const coreContext = createCoreContext(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + const spacesExtension: jest.Mocked = jest.fn(); + setup.setSpacesExtension(spacesExtension); + + await soService.start(createStartDeps()); + + expect(SavedObjectsClientProvider).toHaveBeenCalledTimes(1); + expect(SavedObjectsClientProvider).toHaveBeenCalledWith( + expect.objectContaining({ + encryptionExtensionFactory: undefined, + securityExtensionFactory: undefined, + spacesExtensionFactory: spacesExtension, + }) + ); + }); + + it('registers a combination of extensions to the clientProvider', async () => { + const coreContext = createCoreContext(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + const encryptionExtension: jest.Mocked = jest.fn(); + const spacesExtension: jest.Mocked = jest.fn(); + setup.setEncryptionExtension(encryptionExtension); + setup.setSpacesExtension(spacesExtension); + + await soService.start(createStartDeps()); + + expect(SavedObjectsClientProvider).toHaveBeenCalledTimes(1); + expect(SavedObjectsClientProvider).toHaveBeenCalledWith( + expect.objectContaining({ + encryptionExtensionFactory: encryptionExtension, + securityExtensionFactory: undefined, + spacesExtensionFactory: spacesExtension, + }) + ); + }); + + it('registers all three extensions to the clientProvider', async () => { + const coreContext = createCoreContext(); + const soService = new SavedObjectsService(coreContext); + const setup = await soService.setup(createSetupDeps()); + const encryptionExtension: jest.Mocked = jest.fn(); + const securityExtension: jest.Mocked = jest.fn(); + const spacesExtension: jest.Mocked = jest.fn(); + setup.setEncryptionExtension(encryptionExtension); + setup.setSecurityExtension(securityExtension); + setup.setSpacesExtension(spacesExtension); + + await soService.start(createStartDeps()); + + expect(SavedObjectsClientProvider).toHaveBeenCalledTimes(1); + expect(SavedObjectsClientProvider).toHaveBeenCalledWith( + expect.objectContaining({ + encryptionExtensionFactory: encryptionExtension, + securityExtensionFactory: securityExtension, + spacesExtensionFactory: spacesExtension, + }) + ); + }); + + it('registers no extensions to the clientProvider', async () => { + const coreContext = createCoreContext(); + const soService = new SavedObjectsService(coreContext); + await soService.setup(createSetupDeps()); + await soService.start(createStartDeps()); + + expect(SavedObjectsClientProvider).toHaveBeenCalledTimes(1); + expect(SavedObjectsClientProvider).toHaveBeenCalledWith( + expect.objectContaining({ + encryptionExtensionFactory: undefined, + securityExtensionFactory: undefined, + spacesExtensionFactory: undefined, + }) + ); + }); + }); + describe('#registerType', () => { it('registers the type to the internal typeRegistry', async () => { // we mocked registerCoreObjectTypes above, so this test case only reflects direct calls to the registerType method From f146f2aa78e658287cb1276a32d86a531312a53a Mon Sep 17 00:00:00 2001 From: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Date: Mon, 14 Nov 2022 11:10:47 +0000 Subject: [PATCH 40/47] [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' --- .../src/saved_objects_service.test.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 9f37c3dbf2e86d..6b58566f4cc145 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -35,7 +35,6 @@ import { import { configServiceMock } from '@kbn/config-mocks'; import type { NodesVersionCompatibility } from '@kbn/core-elasticsearch-server-internal'; import { - ISavedObjectsClientProvider, SavedObjectsClientProvider, SavedObjectsRepository, } from '@kbn/core-saved-objects-api-server-internal'; @@ -51,7 +50,6 @@ import { import { registerCoreObjectTypes } from './object_types'; import { getSavedObjectsDeprecationsProvider } from './deprecations'; -import exp from 'constants'; jest.mock('./object_types'); jest.mock('./deprecations'); From 7e4d8c2ce9bdd1f636077d425eb9e597e5a352ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Mon, 14 Nov 2022 17:48:55 +0100 Subject: [PATCH 41/47] Updates checkConflicts comment, removes unused imports in service unit tests. --- .../src/lib/repository.ts | 4 ++-- .../src/saved_objects_service.test.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 9e34ba195a30d2..7447ec289d48d7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -788,8 +788,8 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { action: 'bulk_create', typeMap: authorizationResult.typeMap, // auditCallback is intentionally omitted, this function in the previous Security SOC wrapper implementation - // did not have audit logging. This is primarily because it is only used internally during imports, it is not - // exposed in a public HTTP API + // did not have audit logging. This is primarily because it is only used by Kibana and is not exposed in a + // public HTTP API }); } diff --git a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts index 9f37c3dbf2e86d..6b58566f4cc145 100644 --- a/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts +++ b/packages/core/saved-objects/core-saved-objects-server-internal/src/saved_objects_service.test.ts @@ -35,7 +35,6 @@ import { import { configServiceMock } from '@kbn/config-mocks'; import type { NodesVersionCompatibility } from '@kbn/core-elasticsearch-server-internal'; import { - ISavedObjectsClientProvider, SavedObjectsClientProvider, SavedObjectsRepository, } from '@kbn/core-saved-objects-api-server-internal'; @@ -51,7 +50,6 @@ import { import { registerCoreObjectTypes } from './object_types'; import { getSavedObjectsDeprecationsProvider } from './deprecations'; -import exp from 'constants'; jest.mock('./object_types'); jest.mock('./deprecations'); From 3229e46ba125f8ac26de3a4ad15a95063b3883c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Tue, 15 Nov 2022 10:01:12 +0100 Subject: [PATCH 42/47] Refactor getAvailableSpaces in bulkGet to remove redundant null check. --- .../src/lib/repository.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index 7447ec289d48d7..cbb390e096bdc3 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -1612,22 +1612,22 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { } let availableSpacesPromise: Promise | undefined; - const getAvailableSpaces = async () => { + const getAvailableSpaces = async (spacesExtension: ISavedObjectsSpacesExtension) => { if (!availableSpacesPromise) { - // This function is only called below if the spaces extension is enabled, so we can safely use the non-null assertion operator here. - availableSpacesPromise = this._spacesExtension!.getSearchableNamespaces([ - ALL_NAMESPACES_STRING, - ]).catch((err) => { - if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { - // the user doesn't have access to any spaces; return the current space ID and allow the SOR authZ check to fail - return [SavedObjectsUtils.namespaceIdToString(namespace)]; - } else { - throw err; - } - }); + availableSpacesPromise = spacesExtension + .getSearchableNamespaces([ALL_NAMESPACES_STRING]) + .catch((err) => { + if (Boom.isBoom(err) && err.output.payload.statusCode === 403) { + // the user doesn't have access to any spaces; return the current space ID and allow the SOR authZ check to fail + return [SavedObjectsUtils.namespaceIdToString(namespace)]; + } else { + throw err; + } + }); } return availableSpacesPromise; }; + let bulkGetRequestIndexCounter = 0; type ExpectedBulkGetResult = Either< { type: string; id: string; error: Payload }, @@ -1657,7 +1657,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { let namespaces = object.namespaces; if (this._spacesExtension && namespaces?.includes(ALL_NAMESPACES_STRING)) { - namespaces = await getAvailableSpaces(); + namespaces = await getAvailableSpaces(this._spacesExtension); } return { tag: 'Right', From 7f4131645b6bb89c35c0f020c858f518a959ff54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Tue, 15 Nov 2022 10:41:54 +0100 Subject: [PATCH 43/47] Adds public types to core entry point exports. --- src/core/server/index.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 5dc73e0afd2900..c839b1f8074b55 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -245,6 +245,7 @@ export type { export type { PluginName, DiscoveredPlugin } from '@kbn/core-base-common'; +export type { SavedObjectsStart } from '@kbn/core-saved-objects-browser'; export type { SavedObject, SavedObjectAttribute, @@ -265,6 +266,7 @@ export type { SavedObjectsImportSimpleWarning, SavedObjectsImportActionRequiredWarning, SavedObjectsImportWarning, + SavedObjectTypeIdTuple, } from '@kbn/core-saved-objects-common'; export type { SavedObjectsBulkCreateObject, @@ -314,6 +316,8 @@ export type { SavedObjectsBulkDeleteObject, SavedObjectsBulkDeleteOptions, SavedObjectsBulkDeleteResponse, + SavedObjectsPointInTimeFinderClient, + SavedObjectsBulkDeleteStatus, } from '@kbn/core-saved-objects-api-server'; export type { SavedObjectsServiceSetup, @@ -359,6 +363,23 @@ export type { SavedObjectsValidationSpec, ISavedObjectsSerializer, SavedObjectsRequestHandlerContext, + EncryptedObjectDescriptor, + ISavedObjectsEncryptionExtension, + CheckAuthorizationParams, + AuthorizationTypeEntry, + AuthorizationTypeMap, + CheckAuthorizationResult, + EnforceAuthorizationParams, + AddAuditEventParams, + RedactNamespacesParams, + ISavedObjectsSecurityExtension, + ISavedObjectsSpacesExtension, + SavedObjectsExtensions, +} from '@kbn/core-saved-objects-server'; +export { + ENCRYPTION_EXTENSION_ID, + SECURITY_EXTENSION_ID, + SPACES_EXTENSION_ID, } from '@kbn/core-saved-objects-server'; export { SavedObjectsErrorHelpers, From 31b85320c96ac32023f4268a98ede665c86085c5 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Thu, 17 Nov 2022 14:25:49 +0100 Subject: [PATCH 44/47] Update packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts Co-authored-by: Aleh Zasypkin --- .../core-saved-objects-api-server/src/saved_objects_client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts index 9fd83a603cb6b8..de7fcfd19fc4b7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server/src/saved_objects_client.ts @@ -380,7 +380,7 @@ export interface SavedObjectsClientContract { * * @param objects - array of objects to collect references for (contains ID and type) * @param options {@link SavedObjectsCollectMultiNamespaceReferencesOptions} - options for the collect multi namespace references operation - * @retuns the {@link SavedObjectsCollectMultiNamespaceReferencesResponse} + * @returns the {@link SavedObjectsCollectMultiNamespaceReferencesResponse} */ collectMultiNamespaceReferences( objects: SavedObjectsCollectMultiNamespaceReferencesObject[], From f05c8b6ae68af7d59275a2aee7101ec86ab4e9f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 17 Nov 2022 15:21:10 +0100 Subject: [PATCH 45/47] Refactors getExtensions of the scoped client provider, and CheckAuthorizationParams interface to use Sets for actions. --- .../lib/collect_multi_namespace_references.ts | 2 +- .../src/lib/internal_bulk_resolve.ts | 2 +- .../src/lib/repository.ts | 26 ++++++++-------- .../src/lib/scoped_client_provider.ts | 31 ++++++------------- .../src/lib/update_objects_spaces.ts | 2 +- .../core-saved-objects-server/index.ts | 1 + .../src/extensions/security.ts | 4 +-- src/core/server/index.ts | 1 + .../saved_objects_security_extension.test.ts | 4 +-- .../saved_objects_security_extension.ts | 7 +++-- .../secure_spaces_client_wrapper.test.ts | 2 +- .../spaces/secure_spaces_client_wrapper.ts | 2 +- 12 files changed, 38 insertions(+), 46 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts index 42237461c8eeee..ffe3950394dd88 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.ts @@ -255,7 +255,7 @@ async function optionallyUseSecurity( const { typeMap } = await securityExtension.checkAuthorization({ types: typesToAuthorize, spaces: spacesToAuthorize, - actions: [action], + actions: new Set([action]), }); // Enforce authorization based on all *requested* object types and the current space diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts index 7fb26efa835b64..634d49c83732f2 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.ts @@ -327,7 +327,7 @@ async function authorizeAuditAndRedact( const authorizationResult = await securityExtension.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: spacesToAuthorize, - actions: ['bulk_get'], + actions: new Set(['bulk_get']), }); securityExtension.enforceAuthorization({ typesAndSpaces, diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts index cbb390e096bdc3..65794ff6492fa7 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.ts @@ -358,7 +358,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set([type]), spaces: new Set([...spacesToEnforce, ...spacesToAuthorize]), // existing namespaces are included so we can later redact if necessary - actions: ['create'], + actions: new Set(['create']), // If a user tries to create an object with `initialNamespaces: ['*']`, they need to have 'create' privileges for the Global Resource // (e.g., All privileges for All Spaces). // Inversely, if a user tries to overwrite an object that already exists in '*', they don't need to 'create' privileges for the Global @@ -551,7 +551,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: spacesToAuthorize, - actions: ['bulk_create'], + actions: new Set(['bulk_create']), // If a user tries to create an object with `initialNamespaces: ['*']`, they need to have 'bulk_create' privileges for the Global // Resource (e.g., All privileges for All Spaces). // Inversely, if a user tries to overwrite an object that already exists in '*', they don't need to have 'bulk_create' privileges for the Global @@ -780,7 +780,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: new Set([namespaceString]), // Always check authZ for the active space - actions: ['bulk_create'], + actions: new Set(['bulk_create']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -863,7 +863,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set([type]), spaces: new Set([namespaceString]), // Always check authZ for the active space - actions: ['delete'], + actions: new Set(['delete']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -1168,7 +1168,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: spacesToAuthorize, - actions: ['bulk_delete'], + actions: new Set(['bulk_delete']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -1474,7 +1474,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { preAuthorizationResult = await this._securityExtension.checkAuthorization({ types: new Set(types), spaces: spacesToPreauthorize, - actions: ['find'], + actions: new Set(['find']), }); if (preAuthorizationResult.status === 'unauthorized') { // If the user is unauthorized to find *anything* they requested, return an empty response @@ -1588,7 +1588,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { await this._securityExtension?.checkAuthorization({ types: new Set(types), spaces: spacesToAuthorize, - actions: ['find'], + actions: new Set(['find']), }) : undefined; @@ -1757,7 +1757,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: spacesToAuthorize, - actions: ['bulk_get'], + actions: new Set(['bulk_get']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -1846,7 +1846,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set([type]), spaces: new Set([...spacesToEnforce, ...existingNamespaces]), // existing namespaces are included so we can later redact if necessary - actions: ['get'], + actions: new Set(['get']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -1953,7 +1953,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set([type]), spaces: new Set([...spacesToEnforce, ...existingNamespaces]), // existing namespaces are included so we can later redact if necessary - actions: ['update'], + actions: new Set(['update']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -2240,7 +2240,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: spacesToAuthorize, - actions: ['bulk_update'], + actions: new Set(['bulk_update']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -2420,7 +2420,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const authorizationResult = await this._securityExtension?.checkAuthorization({ types: new Set([type]), spaces, - actions: ['delete'], + actions: new Set(['delete']), }); if (authorizationResult) { this._securityExtension!.enforceAuthorization({ @@ -2711,7 +2711,7 @@ export class SavedObjectsRepository implements ISavedObjectsRepository { const preAuthorizationResult = await this._securityExtension.checkAuthorization({ types: new Set(types), spaces, - actions: ['open_point_in_time'], + actions: new Set(['open_point_in_time']), }); if (preAuthorizationResult.status === 'unauthorized') { // If the user is unauthorized to find *anything* they requested, return an empty response diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts index 0a248ffa72549c..3988df33d218e1 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/scoped_client_provider.ts @@ -16,9 +16,7 @@ import type { SavedObjectsSecurityExtensionFactory, SavedObjectsSpacesExtensionFactory, SavedObjectsExtensions, - ISavedObjectsEncryptionExtension, - ISavedObjectsSecurityExtension, - ISavedObjectsSpacesExtension, + SavedObjectsExtensionFactory, } from '@kbn/core-saved-objects-server'; import { ENCRYPTION_EXTENSION_ID, @@ -87,27 +85,18 @@ export class SavedObjectsClientProvider { } getExtensions(request: KibanaRequest, excludedExtensions: string[]): SavedObjectsExtensions { - const extensionInfo = [ - { id: ENCRYPTION_EXTENSION_ID, factory: this.encryptionExtensionFactory }, - { id: SECURITY_EXTENSION_ID, factory: this.securityExtensionFactory }, - { id: SPACES_EXTENSION_ID, factory: this.spacesExtensionFactory }, - ]; - - const extensions = extensionInfo.map((extension) => { - const isIncluded = !excludedExtensions.includes(extension.id) && !!extension.factory; - return isIncluded - ? extension.factory?.({ typeRegistry: this._typeRegistry, request }) + const createExt = ( + extensionId: string, + extensionFactory?: SavedObjectsExtensionFactory + ): T | undefined => + !excludedExtensions.includes(extensionId) && !!extensionFactory + ? extensionFactory?.({ typeRegistry: this._typeRegistry, request }) : undefined; - }); - - const encryptionExtension = extensions[0] as ISavedObjectsEncryptionExtension; - const securityExtension = extensions[1] as ISavedObjectsSecurityExtension; - const spacesExtension = extensions[2] as ISavedObjectsSpacesExtension; return { - encryptionExtension, - securityExtension, - spacesExtension, + encryptionExtension: createExt(ENCRYPTION_EXTENSION_ID, this.encryptionExtensionFactory), + securityExtension: createExt(SECURITY_EXTENSION_ID, this.securityExtensionFactory), + spacesExtension: createExt(SPACES_EXTENSION_ID, this.spacesExtensionFactory), }; } } diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts index 88ee4027b207b7..f4afe7cf969616 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.ts @@ -207,7 +207,7 @@ export async function updateObjectsSpaces({ const authorizationResult = await securityExtension?.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: spacesToAuthorize, - actions: ['share_to_space'], + actions: new Set(['share_to_space']), // If a user tries to share/unshare an object to/from '*', they need to have 'share_to_space' privileges for the Global Resource (e.g., // All privileges for All Spaces). options: { allowGlobalResource: true }, diff --git a/packages/core/saved-objects/core-saved-objects-server/index.ts b/packages/core/saved-objects/core-saved-objects-server/index.ts index 9018cc5b8b17f9..c96d40ca7a72fe 100644 --- a/packages/core/saved-objects/core-saved-objects-server/index.ts +++ b/packages/core/saved-objects/core-saved-objects-server/index.ts @@ -14,6 +14,7 @@ export type { SavedObjectsEncryptionExtensionFactory, SavedObjectsSecurityExtensionFactory, SavedObjectsSpacesExtensionFactory, + SavedObjectsExtensionFactory, } from './src/client_factory'; export type { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './src/contracts'; export type { diff --git a/packages/core/saved-objects/core-saved-objects-server/src/extensions/security.ts b/packages/core/saved-objects/core-saved-objects-server/src/extensions/security.ts index 18eaae171d118b..2eadd954e6ad42 100644 --- a/packages/core/saved-objects/core-saved-objects-server/src/extensions/security.ts +++ b/packages/core/saved-objects/core-saved-objects-server/src/extensions/security.ts @@ -23,9 +23,9 @@ export interface CheckAuthorizationParams { */ spaces: Set; /** - * An array of actions to check. + * An set of actions to check. */ - actions: A[]; + actions: Set; /** * Authorization options - whether or not to allow global resources, false if options are undefined */ diff --git a/src/core/server/index.ts b/src/core/server/index.ts index c839b1f8074b55..59a9c52345f833 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -328,6 +328,7 @@ export type { SavedObjectsEncryptionExtensionFactory, SavedObjectsSecurityExtensionFactory, SavedObjectsSpacesExtensionFactory, + SavedObjectsExtensionFactory, SavedObjectTypeExcludeFromUpgradeFilterHook, SavedObjectsExportResultDetails, SavedObjectsExportExcludedObject, diff --git a/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts index 89d5a0945accef..3c6ff01aea9207 100644 --- a/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts +++ b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.test.ts @@ -38,7 +38,7 @@ describe('#checkAuthorization', () => { // These arguments are used for all unit tests below const types = new Set(['a', 'b', 'c']); const spaces = new Set(['x', 'y']); - const actions = ['foo', 'bar']; + const actions = new Set(['foo', 'bar']); const fullyAuthorizedCheckPrivilegesResponse = { hasAllRequested: true, @@ -100,7 +100,7 @@ describe('#checkAuthorization', () => { const { securityExtension, checkPrivileges } = setup(); await expect( - securityExtension.checkAuthorization({ types, spaces, actions: [] }) + securityExtension.checkAuthorization({ types, spaces, actions: new Set([]) }) ).rejects.toThrowError('No actions specified for authorization check'); expect(checkPrivileges).not.toHaveBeenCalled(); }); diff --git a/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts index 739b5938abdc27..07c720c0aa59bc 100644 --- a/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts +++ b/x-pack/plugins/security/server/saved_objects/saved_objects_security_extension.ts @@ -56,13 +56,14 @@ export class SavedObjectsSecurityExtension implements ISavedObjectsSecurityExten if (spaces.size === 0) { throw new Error('No spaces specified for authorization check'); } - if (actions.length === 0) { + if (actions.size === 0) { throw new Error('No actions specified for authorization check'); } const typesArray = [...types]; + const actionsArray = [...actions]; const privilegeActionsMap = new Map( typesArray.flatMap((type) => - actions.map((action) => [this.actions.savedObject.get(type, action), { type, action }]) + actionsArray.map((action) => [this.actions.savedObject.get(type, action), { type, action }]) ) ); const privilegeActions = [...privilegeActionsMap.keys(), this.actions.login]; // Always check login action, we will need it later for redacting namespaces @@ -127,7 +128,7 @@ export class SavedObjectsSecurityExtension implements ISavedObjectsSecurityExten } else if (typeMap.size > 0) { for (const entry of typeMap.values()) { const typeActions = Object.keys(entry); - if (actions.some((a) => typeActions.includes(a))) { + if (actionsArray.some((a) => typeActions.includes(a))) { // Only return 'partially_authorized' if the user is actually authorized for one of the actions they requested // (e.g., not just the 'login:' action) return { typeMap, status: 'partially_authorized' }; diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts index d3b89e2eafbc74..caa45118e2879c 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.test.ts @@ -739,7 +739,7 @@ describe('SecureSpacesClientWrapper', () => { expect(securityExtension!.checkAuthorization).toHaveBeenCalledWith({ types: new Set(targetTypes), // unique types of the alias targets spaces: new Set(targetSpaces), // unique spaces of the alias targets - actions: ['bulk_update'], + actions: new Set(['bulk_update']), }); } diff --git a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts index 1d92a31736660a..7e8adf5cb8ec95 100644 --- a/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts +++ b/x-pack/plugins/security/server/spaces/secure_spaces_client_wrapper.ts @@ -325,7 +325,7 @@ export class SecureSpacesClientWrapper implements ISpacesClient { const { typeMap } = await this.securityExtension.checkAuthorization({ types: new Set(typesAndSpaces.keys()), spaces: uniqueSpaces, - actions: ['bulk_update'], + actions: new Set(['bulk_update']), }); let error: Error | undefined; try { From c9b25b061e100fd940601ca2dd226405086cde71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 17 Nov 2022 16:33:23 +0100 Subject: [PATCH 46/47] Fixes unit tests for check auth expected actions. --- .../src/lib/internal_bulk_resolve.test.ts | 4 +- .../lib/repository.security_extension.test.ts | 60 +++++++++---------- .../src/lib/update_objects_spaces.test.ts | 12 ++-- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts index 8aa61c08907dc7..2b2c2986aa6749 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/internal_bulk_resolve.test.ts @@ -504,7 +504,7 @@ describe('internalBulkResolve', () => { await internalBulkResolve(params); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_get']; + const expectedActions = new Set(['bulk_get']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([objects[0].type]); @@ -514,7 +514,7 @@ describe('internalBulkResolve', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts index 1f8b41a45a50b2..eea97945b4dda8 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/repository.security_extension.test.ts @@ -182,7 +182,7 @@ describe('SavedObjectsRepository Security Extension', () => { ); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['get']; + const expectedActions = new Set(['get']); const expectedSpaces = new Set(multiNamespaceObjNamespaces); const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); @@ -192,7 +192,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -323,7 +323,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['update']; + const expectedActions = new Set(['update']); const expectedSpaces = new Set(multiNamespaceObjNamespaces); const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); @@ -333,7 +333,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -472,7 +472,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['create']; + const expectedActions = new Set(['create']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); @@ -482,7 +482,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -494,7 +494,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['create']; + const expectedActions = new Set(['create']); const expectedSpaces = new Set(multiNamespaceObjNamespaces); const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); @@ -504,7 +504,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -648,7 +648,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['delete']; + const expectedActions = new Set(['delete']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([MULTI_NAMESPACE_CUSTOM_INDEX_TYPE]); @@ -658,7 +658,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -759,7 +759,7 @@ describe('SavedObjectsRepository Security Extension', () => { await removeReferencesToSuccess(client, repository, type, id, { namespace }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['delete']; + const expectedActions = new Set(['delete']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([type]); @@ -769,7 +769,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -896,7 +896,7 @@ describe('SavedObjectsRepository Security Extension', () => { await checkConflictsSuccess(client, repository, registry, [obj1, obj2], { namespace }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_create']; + const expectedActions = new Set(['bulk_create']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([type]); @@ -906,7 +906,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -989,7 +989,7 @@ describe('SavedObjectsRepository Security Extension', () => { await repository.openPointInTimeForType(type, { namespaces: [namespace] }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['open_point_in_time']; + const expectedActions = new Set(['open_point_in_time']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([type]); @@ -999,7 +999,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -1157,7 +1157,7 @@ describe('SavedObjectsRepository Security Extension', () => { await findSuccess(client, repository, { type, namespaces: [namespace] }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(2); - const expectedActions = ['find']; + const expectedActions = new Set(['find']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([type]); @@ -1167,7 +1167,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -1338,7 +1338,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_get']; + const expectedActions = new Set(['bulk_get']); const expectedSpaces = new Set([optionsNamespace, ...objA.namespaces, ...objB.namespaces]); const expectedTypes = new Set([objA.type, objB.type]); @@ -1348,7 +1348,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -1517,7 +1517,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_create']; + const expectedActions = new Set(['bulk_create']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([obj1.type, obj2.type]); @@ -1527,7 +1527,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -1553,7 +1553,7 @@ describe('SavedObjectsRepository Security Extension', () => { expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_create']; + const expectedActions = new Set(['bulk_create']); const expectedSpaces = new Set([ optionsNamespace, ...objA.initialNamespaces, @@ -1567,7 +1567,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -1728,7 +1728,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_update']; + const expectedActions = new Set(['bulk_update']); const expectedSpaces = new Set([namespace]); const expectedTypes = new Set([obj1.type, obj2.type]); @@ -1738,7 +1738,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -1760,7 +1760,7 @@ describe('SavedObjectsRepository Security Extension', () => { }); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_update']; + const expectedActions = new Set(['bulk_update']); const expectedSpaces = new Set([namespace, objA.namespace, objB.namespace]); const expectedTypes = new Set([objA.type, objB.type]); @@ -1770,7 +1770,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -1955,7 +1955,7 @@ describe('SavedObjectsRepository Security Extension', () => { await bulkDeleteSuccess(client, repository, registry, testObjs, options, internalOptions); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['bulk_delete']; + const expectedActions = new Set(['bulk_delete']); const expectedSpaces = new Set(internalOptions.mockMGetResponseObjects[1].initialNamespaces); const expectedTypes = new Set([obj1.type, obj2.type]); @@ -1965,7 +1965,7 @@ describe('SavedObjectsRepository Security Extension', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts index 52f7a7346e3ab7..9c3113d142c68a 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/update_objects_spaces.test.ts @@ -771,7 +771,7 @@ describe('#updateObjectsSpaces', () => { expect(client.bulk).toHaveBeenCalledTimes(1); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['share_to_space']; + const expectedActions = new Set(['share_to_space']); const expectedSpaces = new Set([defaultSpace, otherSpace, EXISTING_SPACE]); const expectedTypes = new Set([SHAREABLE_OBJ_TYPE]); @@ -781,7 +781,7 @@ describe('#updateObjectsSpaces', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -857,7 +857,7 @@ describe('#updateObjectsSpaces', () => { expect(client.bulk).toHaveBeenCalledTimes(1); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['share_to_space']; + const expectedActions = new Set(['share_to_space']); const expectedSpaces = new Set(['*', defaultSpace, otherSpace, EXISTING_SPACE]); const expectedTypes = new Set([SHAREABLE_OBJ_TYPE]); @@ -867,7 +867,7 @@ describe('#updateObjectsSpaces', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); @@ -880,7 +880,7 @@ describe('#updateObjectsSpaces', () => { expect(client.bulk).toHaveBeenCalledTimes(1); expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); - const expectedActions = ['share_to_space']; + const expectedActions = new Set(['share_to_space']); const expectedSpaces = new Set(['*', defaultSpace, otherSpace, EXISTING_SPACE]); const expectedTypes = new Set([SHAREABLE_OBJ_TYPE]); @@ -890,7 +890,7 @@ describe('#updateObjectsSpaces', () => { types: actualTypes, } = mockSecurityExt.checkAuthorization.mock.calls[0][0]; - expect(actualActions).toEqual(expectedActions); + expect(setsAreEqual(actualActions, expectedActions)).toBeTruthy(); expect(setsAreEqual(actualSpaces, expectedSpaces)).toBeTruthy(); expect(setsAreEqual(actualTypes, expectedTypes)).toBeTruthy(); }); From ae02c50f81766f45853e19ae49d1c5212ac22958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Cjeramysoucy=E2=80=9D?= Date: Thu, 17 Nov 2022 17:26:23 +0100 Subject: [PATCH 47/47] Fixes collect multi-namespace refs unit tests. --- .../src/lib/collect_multi_namespace_references.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts index a9203b4d1d43df..55080f8e9e4e2f 100644 --- a/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts +++ b/packages/core/saved-objects/core-saved-objects-api-server-internal/src/lib/collect_multi_namespace_references.test.ts @@ -580,7 +580,7 @@ describe('collectMultiNamespaceReferences', () => { expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); expect(mockSecurityExt.checkAuthorization).toBeCalledWith( expect.objectContaining({ - actions: ['bulk_get'], + actions: new Set(['bulk_get']), }) ); expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1); @@ -600,7 +600,7 @@ describe('collectMultiNamespaceReferences', () => { expect(mockSecurityExt.checkAuthorization).toHaveBeenCalledTimes(1); expect(mockSecurityExt.checkAuthorization).toBeCalledWith( expect.objectContaining({ - actions: ['share_to_space'], + actions: new Set(['share_to_space']), }) ); expect(mockSecurityExt.enforceAuthorization).toHaveBeenCalledTimes(1);