From dfa22d17b92423773fe9e4a4cd37a649188de2d6 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Wed, 20 May 2020 09:55:02 +0100 Subject: [PATCH] [Saved Objects] adds support for including hidden types in saved objects client (#66879) As part of the work needed for RBAC & Feature Controls support in Alerting (https://github.com/elastic/kibana/issues/43994) we've identified a need to make the Alert Saved Object type a _hidden_ type. As we still need support for Security and Spaces, we wish to use the standard SavedObjectsClient and its middleware, but currently this isn't possible with _hidden_ types. To address that, this PR adds support for creating a client which includes hidden types. --- ...n-core-server.savedobjectsclientfactory.md | 3 +- ...ientprovideroptions.includedhiddentypes.md | 11 + ...erver.savedobjectsclientprovideroptions.md | 1 + ...ositoryfactory.createinternalrepository.md | 2 +- ...epositoryfactory.createscopedrepository.md | 2 +- ...re-server.savedobjectsrepositoryfactory.md | 4 +- ...tsservicestart.createinternalrepository.md | 2 +- ...ectsservicestart.createscopedrepository.md | 2 +- ...in-core-server.savedobjectsservicestart.md | 4 +- .../saved_objects_service.test.ts | 85 +++++++ .../saved_objects/saved_objects_service.ts | 38 +-- .../saved_objects/service/lib/repository.ts | 6 +- .../lib/scoped_client_provider.test.js | 20 ++ .../service/lib/scoped_client_provider.ts | 8 +- src/core/server/server.api.md | 15 +- .../saved_objects/saved_objects_mixin.js | 6 +- .../server/lib/action_executor.test.ts | 20 +- .../actions/server/lib/action_executor.ts | 12 +- .../server/lib/task_runner_factory.test.ts | 22 +- .../actions/server/lib/task_runner_factory.ts | 8 +- x-pack/plugins/actions/server/plugin.ts | 6 +- .../alerting/server/alerts_client.test.ts | 4 +- .../plugins/alerting/server/alerts_client.ts | 24 +- .../server/alerts_client_factory.test.ts | 4 +- .../alerting/server/alerts_client_factory.ts | 10 +- x-pack/plugins/alerting/server/plugin.test.ts | 2 + x-pack/plugins/alerting/server/plugin.ts | 6 +- .../server/task_runner/task_runner.test.ts | 28 +-- .../server/task_runner/task_runner.ts | 2 +- .../task_runner/task_runner_factory.test.ts | 2 +- .../server/task_runner/task_runner_factory.ts | 4 +- .../encrypted_saved_objects/server/index.ts | 1 + .../encrypted_saved_objects/server/mocks.ts | 10 +- .../server/plugin.test.ts | 12 +- .../encrypted_saved_objects/server/plugin.ts | 15 +- .../server/saved_objects/index.test.ts | 34 ++- .../server/saved_objects/index.ts | 61 ++--- .../server/services/agents/acks.test.ts | 8 +- .../server/services/app_context.ts | 6 +- .../security/server/saved_objects/index.ts | 6 +- .../fixtures/plugins/aad/server/plugin.ts | 8 +- .../server/hidden_saved_object_routes.ts | 235 ++++++++++++++++++ .../api_consumer_plugin/server/index.ts | 30 ++- .../tests/encrypted_saved_objects_api.ts | 11 +- 44 files changed, 615 insertions(+), 185 deletions(-) create mode 100644 docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md create mode 100644 x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/hidden_saved_object_routes.ts diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientfactory.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientfactory.md index 09c6d63f03dd79..724c1ebbeadf44 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientfactory.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientfactory.md @@ -9,7 +9,8 @@ Describes the factory used to create instances of the Saved Objects Client. Signature: ```typescript -export declare type SavedObjectsClientFactory = ({ request, }: { +export declare type SavedObjectsClientFactory = ({ request, includedHiddenTypes, }: { request: KibanaRequest; + includedHiddenTypes?: string[]; }) => SavedObjectsClientContract; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md new file mode 100644 index 00000000000000..a9483e34b38ced --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsClientProviderOptions](./kibana-plugin-core-server.savedobjectsclientprovideroptions.md) > [includedHiddenTypes](./kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md) + +## SavedObjectsClientProviderOptions.includedHiddenTypes property + +Signature: + +```typescript +includedHiddenTypes?: string[]; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.md index 4291de765fd440..be1f73f0648439 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsclientprovideroptions.md @@ -17,4 +17,5 @@ export interface SavedObjectsClientProviderOptions | Property | Type | Description | | --- | --- | --- | | [excludedWrappers](./kibana-plugin-core-server.savedobjectsclientprovideroptions.excludedwrappers.md) | string[] | | +| [includedHiddenTypes](./kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md) | string[] | | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createinternalrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createinternalrepository.md index c4b19ca15910f3..e39ce020b930cc 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createinternalrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createinternalrepository.md @@ -9,5 +9,5 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre Signature: ```typescript -createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createscopedrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createscopedrepository.md index b9007d16d0234f..9cd0df90942777 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createscopedrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.createscopedrepository.md @@ -9,5 +9,5 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre Signature: ```typescript -createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.md index 35b29918edced3..dec768b68cd3af 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsrepositoryfactory.md @@ -16,6 +16,6 @@ export interface SavedObjectsRepositoryFactory | Property | Type | Description | | --- | --- | --- | -| [createInternalRepository](./kibana-plugin-core-server.savedobjectsrepositoryfactory.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | -| [createScopedRepository](./kibana-plugin-core-server.savedobjectsrepositoryfactory.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [createInternalRepository](./kibana-plugin-core-server.savedobjectsrepositoryfactory.createinternalrepository.md) | (includedHiddenTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-core-server.savedobjectsrepositoryfactory.createscopedrepository.md) | (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md index 4467dd23d87b6b..d03e9ca223c530 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md @@ -9,5 +9,5 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre Signature: ```typescript -createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; +createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md index 2840a377026e7c..762f77b98e74d3 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md @@ -9,7 +9,7 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre Signature: ```typescript -createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; +createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository; ``` ## Remarks diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.md index 5f592adf7acd99..17655bb4878a7d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectsservicestart.md @@ -16,8 +16,8 @@ export interface SavedObjectsServiceStart | Property | Type | Description | | --- | --- | --- | -| [createInternalRepository](./kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | -| [createScopedRepository](./kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [createInternalRepository](./kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md) | (includedHiddenTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-core-server.savedobjectsservicestart.createscopedrepository.md) | (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | | [createSerializer](./kibana-plugin-core-server.savedobjectsservicestart.createserializer.md) | () => SavedObjectsSerializer | Creates a [serializer](./kibana-plugin-core-server.savedobjectsserializer.md) that is aware of all registered types. | | [getScopedClient](./kibana-plugin-core-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract | Creates a [Saved Objects client](./kibana-plugin-core-server.savedobjectsclientcontract.md) 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.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-core-server.requesthandlercontext.md). | | [getTypeRegistry](./kibana-plugin-core-server.savedobjectsservicestart.gettyperegistry.md) | () => ISavedObjectTypeRegistry | Returns the [registry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) containing all registered [saved object types](./kibana-plugin-core-server.savedobjectstype.md) | diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts index 819d79803f371e..7dea7a017a47d6 100644 --- a/src/core/server/saved_objects/saved_objects_service.test.ts +++ b/src/core/server/saved_objects/saved_objects_service.test.ts @@ -35,6 +35,10 @@ import { legacyServiceMock } from '../legacy/legacy_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; import { SavedObjectsClientFactoryProvider } from './service/lib'; import { NodesVersionCompatibility } from '../elasticsearch/version_check/ensure_es_version'; +import { SavedObjectsRepository } from './service/lib/repository'; +import { KibanaRequest } from '../http'; + +jest.mock('./service/lib/repository'); describe('SavedObjectsService', () => { const createCoreContext = ({ @@ -269,5 +273,86 @@ describe('SavedObjectsService', () => { expect(getTypeRegistry()).toBe(typeRegistryInstanceMock); }); }); + + describe('#createScopedRepository', () => { + it('creates a respository scoped to the user', async () => { + const coreContext = createCoreContext({ skipMigration: false }); + const soService = new SavedObjectsService(coreContext); + const coreSetup = createSetupDeps(); + await soService.setup(coreSetup); + const { createScopedRepository } = await soService.start({}); + + const req = {} as KibanaRequest; + createScopedRepository(req); + + expect(coreSetup.elasticsearch.adminClient.asScoped).toHaveBeenCalledWith(req); + + const [ + { + value: { callAsCurrentUser }, + }, + ] = coreSetup.elasticsearch.adminClient.asScoped.mock.results; + + const [ + [, , , callCluster, includedHiddenTypes], + ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; + + expect(callCluster).toBe(callAsCurrentUser); + expect(includedHiddenTypes).toEqual([]); + }); + + it('creates a respository including hidden types when specified', async () => { + const coreContext = createCoreContext({ skipMigration: false }); + const soService = new SavedObjectsService(coreContext); + const coreSetup = createSetupDeps(); + await soService.setup(coreSetup); + const { createScopedRepository } = await soService.start({}); + + const req = {} as KibanaRequest; + createScopedRepository(req, ['someHiddenType']); + + const [ + [, , , , includedHiddenTypes], + ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; + + expect(includedHiddenTypes).toEqual(['someHiddenType']); + }); + }); + + describe('#createInternalRepository', () => { + it('creates a respository using the admin user', async () => { + const coreContext = createCoreContext({ skipMigration: false }); + const soService = new SavedObjectsService(coreContext); + const coreSetup = createSetupDeps(); + await soService.setup(coreSetup); + const { createInternalRepository } = await soService.start({}); + + createInternalRepository(); + + const [ + [, , , callCluster, includedHiddenTypes], + ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; + + expect(coreSetup.elasticsearch.adminClient.callAsInternalUser).toBe(callCluster); + expect(callCluster).toBe(coreSetup.elasticsearch.adminClient.callAsInternalUser); + expect(includedHiddenTypes).toEqual([]); + }); + + it('creates a respository including hidden types when specified', async () => { + const coreContext = createCoreContext({ skipMigration: false }); + const soService = new SavedObjectsService(coreContext); + const coreSetup = createSetupDeps(); + await soService.setup(coreSetup); + const { createInternalRepository } = await soService.start({}); + + createInternalRepository(['someHiddenType']); + + const [ + [, , , , includedHiddenTypes], + ] = (SavedObjectsRepository.createRepository as jest.Mocked).mock.calls; + + expect(includedHiddenTypes).toEqual(['someHiddenType']); + }); + }); }); }); diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts index ed4ffef5729ab2..373b8bd1d2bc60 100644 --- a/src/core/server/saved_objects/saved_objects_service.ts +++ b/src/core/server/saved_objects/saved_objects_service.ts @@ -198,20 +198,23 @@ export interface SavedObjectsServiceStart { * Elasticsearch. * * @param req - The request to create the scoped repository from. - * @param extraTypes - A list of additional hidden types the repository should have access to. + * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. * * @remarks * Prefer using `getScopedClient`. This should only be used when using methods * not exposed on {@link SavedObjectsClientContract} */ - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: ( + req: KibanaRequest, + includedHiddenTypes?: string[] + ) => ISavedObjectsRepository; /** * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that * uses the internal Kibana user for authenticating with Elasticsearch. * - * @param extraTypes - A list of additional hidden types the repository should have access to. + * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. */ - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; /** * Creates a {@link SavedObjectsSerializer | serializer} that is aware of all registered types. */ @@ -246,16 +249,19 @@ export interface SavedObjectsRepositoryFactory { * uses the credentials from the passed in request to authenticate with * Elasticsearch. * - * @param extraTypes - A list of additional hidden types the repository should have access to. + * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. */ - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: ( + req: KibanaRequest, + includedHiddenTypes?: string[] + ) => ISavedObjectsRepository; /** * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that * uses the internal Kibana user for authenticating with Elasticsearch. * - * @param extraTypes - A list of additional hidden types the repository should have access to. + * @param includedHiddenTypes - A list of additional hidden types the repository should have access to. */ - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; + createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; } /** @internal */ @@ -417,26 +423,26 @@ export class SavedObjectsService await migrator.runMigrations(); } - const createRepository = (callCluster: APICaller, extraTypes: string[] = []) => { + const createRepository = (callCluster: APICaller, includedHiddenTypes: string[] = []) => { return SavedObjectsRepository.createRepository( migrator, this.typeRegistry, kibanaConfig.index, callCluster, - extraTypes + includedHiddenTypes ); }; const repositoryFactory: SavedObjectsRepositoryFactory = { - createInternalRepository: (extraTypes?: string[]) => - createRepository(adminClient.callAsInternalUser, extraTypes), - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => - createRepository(adminClient.asScoped(req).callAsCurrentUser, extraTypes), + createInternalRepository: (includedHiddenTypes?: string[]) => + createRepository(adminClient.callAsInternalUser, includedHiddenTypes), + createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => + createRepository(adminClient.asScoped(req).callAsCurrentUser, includedHiddenTypes), }; const clientProvider = new SavedObjectsClientProvider({ - defaultClientFactory({ request }) { - const repository = repositoryFactory.createScopedRepository(request); + defaultClientFactory({ request, includedHiddenTypes }) { + const repository = repositoryFactory.createScopedRepository(request, includedHiddenTypes); return new SavedObjectsClient(repository); }, typeRegistry: this.typeRegistry, diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index 61027130e0eb73..f76b05c4af1b92 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -132,7 +132,7 @@ export class SavedObjectsRepository { typeRegistry: SavedObjectTypeRegistry, indexName: string, callCluster: APICaller, - extraTypes: string[] = [], + includedHiddenTypes: string[] = [], injectedConstructor: any = SavedObjectsRepository ): ISavedObjectsRepository { const mappings = migrator.getActiveMappings(); @@ -140,14 +140,14 @@ export class SavedObjectsRepository { const serializer = new SavedObjectsSerializer(typeRegistry); const visibleTypes = allTypes.filter(type => !typeRegistry.isHidden(type)); - const missingTypeMappings = extraTypes.filter(type => !allTypes.includes(type)); + const missingTypeMappings = includedHiddenTypes.filter(type => !allTypes.includes(type)); if (missingTypeMappings.length > 0) { throw new Error( `Missing mappings for saved objects types: '${missingTypeMappings.join(', ')}'` ); } - const allowedTypes = [...new Set(visibleTypes.concat(extraTypes))]; + const allowedTypes = [...new Set(visibleTypes.concat(includedHiddenTypes))]; return new injectedConstructor({ index: indexName, diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js b/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js index aa9448e61009dc..a0e1530ed2e26b 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.test.js @@ -167,3 +167,23 @@ test(`allows all wrappers to be excluded`, () => { 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({ + defaultClientFactory: defaultClientFactoryMock, + typeRegistry: typeRegistryMock.create(), + }); + const request = Symbol(); + + const actualClient = clientProvider.getClient(request, { + includedHiddenTypes: ['task'], + }); + + expect(actualClient).toBe(defaultClient); + expect(defaultClientFactoryMock).toHaveBeenCalledWith({ + request, + includedHiddenTypes: ['task'], + }); +}); diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts index 24813cd8d9ab8d..3250737e1287d5 100644 --- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts +++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts @@ -46,8 +46,10 @@ export type SavedObjectsClientWrapperFactory = ( */ export type SavedObjectsClientFactory = ({ request, + includedHiddenTypes, }: { request: KibanaRequest; + includedHiddenTypes?: string[]; }) => SavedObjectsClientContract; /** @@ -64,6 +66,7 @@ export type SavedObjectsClientFactoryProvider = ( */ export interface SavedObjectsClientProviderOptions { excludedWrappers?: string[]; + includedHiddenTypes?: string[]; } /** @@ -121,14 +124,13 @@ export class SavedObjectsClientProvider { getClient( request: KibanaRequest, - options: SavedObjectsClientProviderOptions = {} + { includedHiddenTypes, excludedWrappers = [] }: SavedObjectsClientProviderOptions = {} ): SavedObjectsClientContract { const client = this._clientFactory({ request, + includedHiddenTypes, }); - const excludedWrappers = options.excludedWrappers || []; - return this._wrapperFactories .toPrioritizedArray() .reduceRight((clientToWrap, { id, factory }) => { diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index bb8ee1d8e7a318..fcf9a9e2dedc25 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1840,8 +1840,9 @@ export class SavedObjectsClient { export type SavedObjectsClientContract = Pick; // @public -export type SavedObjectsClientFactory = ({ request, }: { +export type SavedObjectsClientFactory = ({ request, includedHiddenTypes, }: { request: KibanaRequest; + includedHiddenTypes?: string[]; }) => SavedObjectsClientContract; // @public @@ -1851,6 +1852,8 @@ export type SavedObjectsClientFactoryProvider = (repositoryFactory: SavedObjects export interface SavedObjectsClientProviderOptions { // (undocumented) excludedWrappers?: string[]; + // (undocumented) + includedHiddenTypes?: string[]; } // @public @@ -2213,7 +2216,7 @@ export class SavedObjectsRepository { // Warning: (ae-forgotten-export) The symbol "KibanaMigrator" needs to be exported by the entry point index.d.ts // // @internal - static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, callCluster: APICaller, extraTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; + static createRepository(migrator: KibanaMigrator, typeRegistry: SavedObjectTypeRegistry, indexName: string, callCluster: APICaller, includedHiddenTypes?: string[], injectedConstructor?: any): ISavedObjectsRepository; delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>; deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise; deleteFromNamespaces(type: string, id: string, namespaces: string[], options?: SavedObjectsDeleteFromNamespacesOptions): Promise<{}>; @@ -2233,8 +2236,8 @@ export class SavedObjectsRepository { // @public export interface SavedObjectsRepositoryFactory { - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository; } // @public @@ -2285,8 +2288,8 @@ export interface SavedObjectsServiceSetup { // @public export interface SavedObjectsServiceStart { - createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository; - createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository; + createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository; + createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository; createSerializer: () => SavedObjectsSerializer; getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract; getTypeRegistry: () => ISavedObjectTypeRegistry; diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.js b/src/legacy/server/saved_objects/saved_objects_mixin.js index 3e71e1989ae7a3..26fecc68fda4bd 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.js @@ -50,17 +50,17 @@ export function savedObjectsMixin(kbnServer, server) { const serializer = kbnServer.newPlatform.start.core.savedObjects.createSerializer(); - const createRepository = (callCluster, extraTypes = []) => { + const createRepository = (callCluster, includedHiddenTypes = []) => { if (typeof callCluster !== 'function') { throw new TypeError('Repository requires a "callCluster" function to be provided.'); } // throw an exception if an extraType is not defined. - extraTypes.forEach(type => { + includedHiddenTypes.forEach(type => { if (!allTypes.includes(type)) { throw new Error(`Missing mappings for saved objects type '${type}'`); } }); - const combinedTypes = visibleTypes.concat(extraTypes); + const combinedTypes = visibleTypes.concat(includedHiddenTypes); const allowedTypes = [...new Set(combinedTypes)]; const config = server.config(); diff --git a/x-pack/plugins/actions/server/lib/action_executor.test.ts b/x-pack/plugins/actions/server/lib/action_executor.test.ts index 4594fc1ddf6d9a..f1e5a10e5bbd2b 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.test.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.test.ts @@ -18,7 +18,7 @@ import { actionsMock } from '../mocks'; const actionExecutor = new ActionExecutor({ isESOUsingEphemeralEncryptionKey: false }); const services = actionsMock.createServices(); const savedObjectsClient = services.savedObjectsClient; -const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); +const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient(); const actionTypeRegistry = actionTypeRegistryMock.create(); const executeParams = { @@ -35,7 +35,7 @@ actionExecutor.initialize({ spaces: spacesMock, getServices: () => services, actionTypeRegistry, - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient, eventLogger: eventLoggerMock.create(), preconfiguredActions: [], }); @@ -67,11 +67,11 @@ test('successfully executes', async () => { references: [], }; savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); actionTypeRegistry.get.mockReturnValueOnce(actionType); await actionExecutor.execute(executeParams); - expect(encryptedSavedObjectsPlugin.getDecryptedAsInternalUser).toHaveBeenCalledWith( + expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).toHaveBeenCalledWith( 'action', '1', { namespace: 'some-namespace' } @@ -108,7 +108,7 @@ test('provides empty config when config and / or secrets is empty', async () => references: [], }; savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); actionTypeRegistry.get.mockReturnValueOnce(actionType); await actionExecutor.execute(executeParams); @@ -138,7 +138,7 @@ test('throws an error when config is invalid', async () => { references: [], }; savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); actionTypeRegistry.get.mockReturnValueOnce(actionType); const result = await actionExecutor.execute(executeParams); @@ -171,7 +171,7 @@ test('throws an error when params is invalid', async () => { references: [], }; savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); actionTypeRegistry.get.mockReturnValueOnce(actionType); const result = await actionExecutor.execute(executeParams); @@ -206,7 +206,7 @@ test('throws an error if actionType is not enabled', async () => { references: [], }; savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); actionTypeRegistry.get.mockReturnValueOnce(actionType); actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => { throw new Error('not enabled for test'); @@ -240,7 +240,7 @@ test('should not throws an error if actionType is preconfigured', async () => { references: [], }; savedObjectsClient.get.mockResolvedValueOnce(actionSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject); actionTypeRegistry.get.mockReturnValueOnce(actionType); actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => { throw new Error('not enabled for test'); @@ -269,7 +269,7 @@ test('throws an error when passing isESOUsingEphemeralEncryptionKey with value o spaces: spacesMock, getServices: () => services, actionTypeRegistry, - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient, eventLogger: eventLoggerMock.create(), preconfiguredActions: [], }); diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 3e9262c05efac4..aad93a04248ebb 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -14,7 +14,7 @@ import { PreConfiguredAction, Services, } from '../types'; -import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; +import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; import { SpacesServiceSetup } from '../../../spaces/server'; import { EVENT_LOG_ACTIONS } from '../plugin'; import { IEvent, IEventLogger, SAVED_OBJECT_REL_PRIMARY } from '../../../event_log/server'; @@ -23,7 +23,7 @@ export interface ActionExecutorContext { logger: Logger; spaces?: SpacesServiceSetup; getServices: GetServicesFunction; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + encryptedSavedObjectsClient: EncryptedSavedObjectsClient; actionTypeRegistry: ActionTypeRegistryContract; eventLogger: IEventLogger; preconfiguredActions: PreConfiguredAction[]; @@ -72,7 +72,7 @@ export class ActionExecutor { const { spaces, getServices, - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient, actionTypeRegistry, eventLogger, preconfiguredActions, @@ -84,7 +84,7 @@ export class ActionExecutor { const { actionTypeId, name, config, secrets } = await getActionInfo( services, - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient, preconfiguredActions, actionId, namespace.namespace @@ -196,7 +196,7 @@ interface ActionInfo { async function getActionInfo( services: Services, - encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart, + encryptedSavedObjectsClient: EncryptedSavedObjectsClient, preconfiguredActions: PreConfiguredAction[], actionId: string, namespace: string | undefined @@ -222,7 +222,7 @@ async function getActionInfo( const { attributes: { secrets }, - } = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser('action', actionId, { + } = await encryptedSavedObjectsClient.getDecryptedAsInternalUser('action', actionId, { namespace: namespace === 'default' ? undefined : namespace, }); diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts index f070f714ee508d..42ccf5a33ebaa4 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.test.ts @@ -18,7 +18,7 @@ import { ActionTypeDisabledError } from './errors'; const spaceIdToNamespace = jest.fn(); const actionTypeRegistry = actionTypeRegistryMock.create(); -const mockedEncryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); +const mockedEncryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient(); const mockedActionExecutor = actionExecutorMock.create(); let fakeTimer: sinon.SinonFakeTimers; @@ -59,7 +59,7 @@ const actionExecutorInitializerParams = { logger: loggingServiceMock.create().get(), getServices: jest.fn().mockReturnValue(services), actionTypeRegistry, - encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin, + encryptedSavedObjectsClient: mockedEncryptedSavedObjectsClient, eventLogger: eventLoggerMock.create(), preconfiguredActions: [], }; @@ -67,7 +67,7 @@ const taskRunnerFactoryInitializerParams = { spaceIdToNamespace, actionTypeRegistry, logger: loggingServiceMock.create().get(), - encryptedSavedObjectsPlugin: mockedEncryptedSavedObjectsPlugin, + encryptedSavedObjectsClient: mockedEncryptedSavedObjectsClient, getBasePath: jest.fn().mockReturnValue(undefined), getScopedSavedObjectsClient: jest.fn().mockReturnValue(services.savedObjectsClient), }; @@ -106,7 +106,7 @@ test('executes the task by calling the executor with proper parameters', async ( mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', type: 'action_task_params', attributes: { @@ -122,7 +122,7 @@ test('executes the task by calling the executor with proper parameters', async ( expect(runnerResult).toBeUndefined(); expect(spaceIdToNamespace).toHaveBeenCalledWith('test'); expect( - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser ).toHaveBeenCalledWith('action_task_params', '3', { namespace: 'namespace-test' }); expect(mockedActionExecutor.execute).toHaveBeenCalledWith({ actionId: '2', @@ -154,7 +154,7 @@ test('cleans up action_task_params object', async () => { mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', type: 'action_task_params', attributes: { @@ -177,7 +177,7 @@ test('runs successfully when cleanup fails and logs the error', async () => { mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', type: 'action_task_params', attributes: { @@ -202,7 +202,7 @@ test('throws an error with suggested retry logic when return status is error', a taskInstance: mockedTaskInstance, }); - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', type: 'action_task_params', attributes: { @@ -237,7 +237,7 @@ test('uses API key when provided', async () => { mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', type: 'action_task_params', attributes: { @@ -280,7 +280,7 @@ test(`doesn't use API key when not provided`, async () => { mockedActionExecutor.execute.mockResolvedValueOnce({ status: 'ok', actionId: '2' }); spaceIdToNamespace.mockReturnValueOnce('namespace-test'); - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', type: 'action_task_params', attributes: { @@ -317,7 +317,7 @@ test(`throws an error when license doesn't support the action type`, async () => taskInstance: mockedTaskInstance, }); - mockedEncryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + mockedEncryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '3', type: 'action_task_params', attributes: { diff --git a/x-pack/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/plugins/actions/server/lib/task_runner_factory.ts index 08c5b90edbcb78..a962497f906a9a 100644 --- a/x-pack/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/plugins/actions/server/lib/task_runner_factory.ts @@ -8,7 +8,7 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; import { Logger, CoreStart, KibanaRequest } from '../../../../../src/core/server'; import { RunContext } from '../../../task_manager/server'; -import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; +import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; import { ActionTypeDisabledError } from './errors'; import { ActionTaskParams, @@ -21,7 +21,7 @@ import { export interface TaskRunnerContext { logger: Logger; actionTypeRegistry: ActionTypeRegistryContract; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + encryptedSavedObjectsClient: EncryptedSavedObjectsClient; spaceIdToNamespace: SpaceIdToNamespaceFunction; getBasePath: GetBasePathFunction; getScopedSavedObjectsClient: CoreStart['savedObjects']['getScopedClient']; @@ -52,7 +52,7 @@ export class TaskRunnerFactory { const { actionExecutor } = this; const { logger, - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient, spaceIdToNamespace, getBasePath, getScopedSavedObjectsClient, @@ -65,7 +65,7 @@ export class TaskRunnerFactory { const { attributes: { actionId, params, apiKey }, - } = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser( + } = await encryptedSavedObjectsClient.getDecryptedAsInternalUser( 'action_task_params', actionTaskParamsId, { namespace } diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index bc7440c8bee4de..75e15815d0787f 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -232,12 +232,14 @@ export class ActionsPlugin implements Plugin, Plugi preconfiguredActions, } = this; + const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient(); + actionExecutor!.initialize({ logger, eventLogger: this.eventLogger!, spaces: this.spaces, getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch), - encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, + encryptedSavedObjectsClient, actionTypeRegistry: actionTypeRegistry!, preconfiguredActions, }); @@ -245,7 +247,7 @@ export class ActionsPlugin implements Plugin, Plugi taskRunnerFactory!.initialize({ logger, actionTypeRegistry: actionTypeRegistry!, - encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, + encryptedSavedObjectsClient, getBasePath: this.getBasePath, spaceIdToNamespace: this.spaceIdToNamespace, getScopedSavedObjectsClient: core.savedObjects.getScopedClient, diff --git a/x-pack/plugins/alerting/server/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client.test.ts index 6601ccc4f5a73b..93b98f6a0fe037 100644 --- a/x-pack/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client.test.ts @@ -17,7 +17,7 @@ import { encryptedSavedObjectsMock } from '../../../plugins/encrypted_saved_obje const taskManager = taskManagerMock.start(); const alertTypeRegistry = alertTypeRegistryMock.create(); const savedObjectsClient = savedObjectsClientMock.create(); -const encryptedSavedObjects = encryptedSavedObjectsMock.createStart(); +const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); const alertsClientParams = { taskManager, @@ -29,7 +29,7 @@ const alertsClientParams = { createAPIKey: jest.fn(), invalidateAPIKey: jest.fn(), logger: loggingServiceMock.create().get(), - encryptedSavedObjectsPlugin: encryptedSavedObjects, + encryptedSavedObjectsClient: encryptedSavedObjects, preconfiguredActions: [], }; diff --git a/x-pack/plugins/alerting/server/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client.ts index ff501055ba9fe9..01687f33f631d4 100644 --- a/x-pack/plugins/alerting/server/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client.ts @@ -32,7 +32,7 @@ import { GrantAPIKeyResult as SecurityPluginGrantAPIKeyResult, InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, } from '../../../plugins/security/server'; -import { EncryptedSavedObjectsPluginStart } from '../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsClient } from '../../../plugins/encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; import { taskInstanceToAlertTaskInstance } from './task_runner/alert_task_instance'; import { deleteTaskIfItExists } from './lib/delete_task_if_it_exists'; @@ -50,7 +50,7 @@ interface ConstructorOptions { taskManager: TaskManagerStartContract; savedObjectsClient: SavedObjectsClientContract; alertTypeRegistry: AlertTypeRegistry; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + encryptedSavedObjectsClient: EncryptedSavedObjectsClient; spaceId?: string; namespace?: string; getUserName: () => Promise; @@ -128,7 +128,7 @@ export class AlertsClient { params: InvalidateAPIKeyParams ) => Promise; private preconfiguredActions: PreConfiguredAction[]; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + encryptedSavedObjectsClient: EncryptedSavedObjectsClient; constructor({ alertTypeRegistry, @@ -140,7 +140,7 @@ export class AlertsClient { getUserName, createAPIKey, invalidateAPIKey, - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient, preconfiguredActions, }: ConstructorOptions) { this.logger = logger; @@ -152,7 +152,7 @@ export class AlertsClient { this.savedObjectsClient = savedObjectsClient; this.createAPIKey = createAPIKey; this.invalidateAPIKey = invalidateAPIKey; - this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin; + this.encryptedSavedObjectsClient = encryptedSavedObjectsClient; this.preconfiguredActions = preconfiguredActions; } @@ -252,7 +252,7 @@ export class AlertsClient { let apiKeyToInvalidate: string | null = null; try { - const decryptedAlert = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + const decryptedAlert = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser< RawAlert >('alert', id, { namespace: this.namespace }); apiKeyToInvalidate = decryptedAlert.attributes.apiKey; @@ -281,7 +281,7 @@ export class AlertsClient { let alertSavedObject: SavedObject; try { - alertSavedObject = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + alertSavedObject = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser< RawAlert >('alert', id, { namespace: this.namespace }); } catch (e) { @@ -377,7 +377,7 @@ export class AlertsClient { let version: string | undefined; try { - const decryptedAlert = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + const decryptedAlert = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser< RawAlert >('alert', id, { namespace: this.namespace }); apiKeyToInvalidate = decryptedAlert.attributes.apiKey; @@ -435,7 +435,7 @@ export class AlertsClient { let version: string | undefined; try { - const decryptedAlert = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + const decryptedAlert = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser< RawAlert >('alert', id, { namespace: this.namespace }); apiKeyToInvalidate = decryptedAlert.attributes.apiKey; @@ -479,7 +479,7 @@ export class AlertsClient { let version: string | undefined; try { - const decryptedAlert = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + const decryptedAlert = await this.encryptedSavedObjectsClient.getDecryptedAsInternalUser< RawAlert >('alert', id, { namespace: this.namespace }); apiKeyToInvalidate = decryptedAlert.attributes.apiKey; @@ -543,7 +543,7 @@ export class AlertsClient { alertId: string; alertInstanceId: string; }) { - const { attributes, version } = await this.savedObjectsClient.get('alert', alertId); + const { attributes, version } = await this.savedObjectsClient.get('alert', alertId); const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && !mutedInstanceIds.includes(alertInstanceId)) { mutedInstanceIds.push(alertInstanceId); @@ -566,7 +566,7 @@ export class AlertsClient { alertId: string; alertInstanceId: string; }) { - const { attributes, version } = await this.savedObjectsClient.get('alert', alertId); + const { attributes, version } = await this.savedObjectsClient.get('alert', alertId); const mutedInstanceIds = attributes.mutedInstanceIds || []; if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) { await this.savedObjectsClient.update( diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts index e5aa0a674eccf4..cc792d11c890dd 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts @@ -24,7 +24,7 @@ const alertsClientFactoryParams: jest.Mocked = { alertTypeRegistry: alertTypeRegistryMock.create(), getSpaceId: jest.fn(), spaceIdToNamespace: jest.fn(), - encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.createStart(), + encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(), preconfiguredActions: [], }; const fakeRequest = ({ @@ -64,7 +64,7 @@ test('creates an alerts client with proper constructor arguments', async () => { getUserName: expect.any(Function), createAPIKey: expect.any(Function), invalidateAPIKey: expect.any(Function), - encryptedSavedObjectsPlugin: alertsClientFactoryParams.encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient: alertsClientFactoryParams.encryptedSavedObjectsClient, preconfiguredActions: [], }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.ts b/x-pack/plugins/alerting/server/alerts_client_factory.ts index 734417e72733e3..913b4e2e81fe14 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.ts @@ -9,7 +9,7 @@ import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { KibanaRequest, Logger, SavedObjectsClientContract } from '../../../../src/core/server'; import { InvalidateAPIKeyParams, SecurityPluginSetup } from '../../../plugins/security/server'; -import { EncryptedSavedObjectsPluginStart } from '../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsClient } from '../../../plugins/encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../../plugins/task_manager/server'; export interface AlertsClientFactoryOpts { @@ -19,7 +19,7 @@ export interface AlertsClientFactoryOpts { securityPluginSetup?: SecurityPluginSetup; getSpaceId: (request: KibanaRequest) => string | undefined; spaceIdToNamespace: SpaceIdToNamespaceFunction; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + encryptedSavedObjectsClient: EncryptedSavedObjectsClient; preconfiguredActions: PreConfiguredAction[]; } @@ -31,7 +31,7 @@ export class AlertsClientFactory { private securityPluginSetup?: SecurityPluginSetup; private getSpaceId!: (request: KibanaRequest) => string | undefined; private spaceIdToNamespace!: SpaceIdToNamespaceFunction; - private encryptedSavedObjectsPlugin!: EncryptedSavedObjectsPluginStart; + private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient; private preconfiguredActions!: PreConfiguredAction[]; public initialize(options: AlertsClientFactoryOpts) { @@ -45,7 +45,7 @@ export class AlertsClientFactory { this.alertTypeRegistry = options.alertTypeRegistry; this.securityPluginSetup = options.securityPluginSetup; this.spaceIdToNamespace = options.spaceIdToNamespace; - this.encryptedSavedObjectsPlugin = options.encryptedSavedObjectsPlugin; + this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient; this.preconfiguredActions = options.preconfiguredActions; } @@ -62,7 +62,7 @@ export class AlertsClientFactory { alertTypeRegistry: this.alertTypeRegistry, savedObjectsClient, namespace: this.spaceIdToNamespace(spaceId), - encryptedSavedObjectsPlugin: this.encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient: this.encryptedSavedObjectsClient, async getUserName() { if (!securityPluginSetup) { return null; diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 267e68930a5d7a..0411899290ab2d 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -81,6 +81,7 @@ describe('Alerting Plugin', () => { execute: jest.fn(), getActionsClientWithRequest: jest.fn(), }, + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), } as unknown) as AlertingPluginsStart ); @@ -125,6 +126,7 @@ describe('Alerting Plugin', () => { getActionsClientWithRequest: jest.fn(), }, spaces: () => null, + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), } as unknown) as AlertingPluginsStart ); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 7bd515616a3c11..08353656359905 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -201,12 +201,14 @@ export class AlertingPlugin { security, } = this; + const encryptedSavedObjectsClient = plugins.encryptedSavedObjects.getClient(); + alertsClientFactory.initialize({ alertTypeRegistry: alertTypeRegistry!, logger, taskManager: plugins.taskManager, securityPluginSetup: security, - encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, + encryptedSavedObjectsClient, spaceIdToNamespace: this.spaceIdToNamespace, getSpaceId(request: KibanaRequest) { return spaces?.getSpaceId(request); @@ -219,7 +221,7 @@ export class AlertingPlugin { getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch), spaceIdToNamespace: this.spaceIdToNamespace, actionsPlugin: plugins.actions, - encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, + encryptedSavedObjectsClient, getBasePath: this.getBasePath, eventLogger: this.eventLogger!, }); diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts index e5ec8f587b9d7f..98824b8cf4e1a4 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.test.ts @@ -54,7 +54,7 @@ describe('Task Runner', () => { afterAll(() => fakeTimer.restore()); - const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); + const encryptedSavedObjectsClient = encryptedSavedObjectsMock.createClient(); const services = alertsMock.createAlertServices(); const savedObjectsClient = services.savedObjectsClient; @@ -64,7 +64,7 @@ describe('Task Runner', () => { } = { getServices: jest.fn().mockReturnValue(services), actionsPlugin: actionsMock.createStart(), - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient, logger: loggingServiceMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), @@ -123,7 +123,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -197,7 +197,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -316,7 +316,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -403,7 +403,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -434,7 +434,7 @@ describe('Task Runner', () => { ...mockedAlertTypeSavedObject, references: [], }); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -462,7 +462,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -498,7 +498,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: {}, @@ -537,7 +537,7 @@ describe('Task Runner', () => { ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -588,7 +588,7 @@ describe('Task Runner', () => { }); test('recovers gracefully when the Alert Task Runner throws an exception when fetching the encrypted attributes', async () => { - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockImplementation(() => { + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockImplementation(() => { throw new Error('OMG'); }); @@ -624,7 +624,7 @@ describe('Task Runner', () => { ); savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -656,7 +656,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -688,7 +688,7 @@ describe('Task Runner', () => { taskRunnerFactoryInitializerParams ); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/plugins/alerting/server/task_runner/task_runner.ts index bf005301adc07c..a36152fa17544f 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner.ts @@ -62,7 +62,7 @@ export class TaskRunner { // scoped with the API key to fetch the remaining data. const { attributes: { apiKey }, - } = await this.context.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser( + } = await this.context.encryptedSavedObjectsClient.getDecryptedAsInternalUser( 'alert', alertId, { namespace } diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index 96d89bebcc66f3..c1318bac48dfbc 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -56,7 +56,7 @@ describe('Task Runner Factory', () => { const taskRunnerFactoryInitializerParams: jest.Mocked = { getServices: jest.fn().mockReturnValue(services), actionsPlugin: actionsMock.createStart(), - encryptedSavedObjectsPlugin, + encryptedSavedObjectsClient: encryptedSavedObjectsPlugin.getClient(), logger: loggingServiceMock.create().get(), spaceIdToNamespace: jest.fn().mockReturnValue(undefined), getBasePath: jest.fn().mockReturnValue(undefined), diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts index b58db8c74f7bba..c50e288d2e5203 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -5,7 +5,7 @@ */ import { Logger } from '../../../../../src/core/server'; import { RunContext } from '../../../../plugins/task_manager/server'; -import { EncryptedSavedObjectsPluginStart } from '../../../../plugins/encrypted_saved_objects/server'; +import { EncryptedSavedObjectsClient } from '../../../../plugins/encrypted_saved_objects/server'; import { PluginStartContract as ActionsPluginStartContract } from '../../../../plugins/actions/server'; import { AlertType, @@ -21,7 +21,7 @@ export interface TaskRunnerContext { getServices: GetServicesFunction; actionsPlugin: ActionsPluginStartContract; eventLogger: IEventLogger; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsPluginStart; + encryptedSavedObjectsClient: EncryptedSavedObjectsClient; spaceIdToNamespace: SpaceIdToNamespaceFunction; getBasePath: GetBasePathFunction; } diff --git a/x-pack/plugins/encrypted_saved_objects/server/index.ts b/x-pack/plugins/encrypted_saved_objects/server/index.ts index 3b4b91de355c74..c8f7acf952c222 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/index.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/index.ts @@ -10,6 +10,7 @@ import { Plugin } from './plugin'; export { EncryptedSavedObjectTypeRegistration, EncryptionError } from './crypto'; export { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; +export { EncryptedSavedObjectsClient } from './saved_objects'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) => diff --git a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts index 13d7127db78353..bbc3eb1540562b 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/mocks.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/mocks.ts @@ -5,6 +5,7 @@ */ import { EncryptedSavedObjectsPluginSetup, EncryptedSavedObjectsPluginStart } from './plugin'; +import { EncryptedSavedObjectsClient } from './saved_objects'; function createEncryptedSavedObjectsSetupMock() { return { @@ -17,11 +18,18 @@ function createEncryptedSavedObjectsSetupMock() { function createEncryptedSavedObjectsStartMock() { return { isEncryptionError: jest.fn(), - getDecryptedAsInternalUser: jest.fn(), + getClient: jest.fn(() => createEncryptedSavedObjectsClienttMock()), } as jest.Mocked; } +function createEncryptedSavedObjectsClienttMock() { + return { + getDecryptedAsInternalUser: jest.fn(), + } as jest.Mocked; +} + export const encryptedSavedObjectsMock = { createSetup: createEncryptedSavedObjectsSetupMock, createStart: createEncryptedSavedObjectsStartMock, + createClient: createEncryptedSavedObjectsClienttMock, }; diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts index 117adba5794d7f..e8568e9964c2f0 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.test.ts @@ -30,12 +30,20 @@ describe('EncryptedSavedObjects Plugin', () => { it('exposes proper contract', async () => { const plugin = new Plugin(coreMock.createPluginInitializerContext()); await plugin.setup(coreMock.createSetup(), { security: securityMock.createSetup() }); - await expect(plugin.start()).toMatchInlineSnapshot(` + + const startContract = plugin.start(); + await expect(startContract).toMatchInlineSnapshot(` Object { - "getDecryptedAsInternalUser": [Function], + "getClient": [Function], "isEncryptionError": [Function], } `); + + expect(startContract.getClient()).toMatchInlineSnapshot(` + Object { + "getDecryptedAsInternalUser": [Function], + } + `); }); }); }); diff --git a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts index 02212f271cf839..948cb94512f2c0 100644 --- a/x-pack/plugins/encrypted_saved_objects/server/plugin.ts +++ b/x-pack/plugins/encrypted_saved_objects/server/plugin.ts @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Logger, - SavedObjectsBaseOptions, - PluginInitializerContext, - CoreSetup, -} from 'src/core/server'; +import { Logger, PluginInitializerContext, CoreSetup } from 'src/core/server'; import { first } from 'rxjs/operators'; import { SecurityPluginSetup } from '../../security/server'; import { createConfig$ } from './config'; @@ -31,8 +26,9 @@ export interface EncryptedSavedObjectsPluginSetup { usingEphemeralEncryptionKey: boolean; } -export interface EncryptedSavedObjectsPluginStart extends SavedObjectsSetup { +export interface EncryptedSavedObjectsPluginStart { isEncryptionError: (error: Error) => boolean; + getClient: SavedObjectsSetup; } /** @@ -97,12 +93,9 @@ export class Plugin { public start() { this.logger.debug('Starting plugin'); - return { isEncryptionError: (error: Error) => error instanceof EncryptionError, - getDecryptedAsInternalUser: (type: string, id: string, options?: SavedObjectsBaseOptions) => { - return this.savedObjectsSetup.getDecryptedAsInternalUser(type, id, options); - }, + getClient: (includedHiddenTypes?: string[]) => this.savedObjectsSetup(includedHiddenTypes), }; } 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 c11f6a2b2afa8b..8f0eb855676adf 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 @@ -25,12 +25,13 @@ import { EncryptedSavedObjectsService } from '../crypto'; describe('#setupSavedObjects', () => { let setupContract: SavedObjectsSetup; + let coreStartMock: ReturnType; let coreSetupMock: ReturnType; let mockSavedObjectsRepository: jest.Mocked; let mockSavedObjectTypeRegistry: jest.Mocked; let mockEncryptedSavedObjectsService: jest.Mocked; beforeEach(() => { - const coreStartMock = coreMock.createStart(); + coreStartMock = coreMock.createStart(); mockSavedObjectsRepository = savedObjectsRepositoryMock.create(); coreStartMock.savedObjects.createInternalRepository.mockReturnValue(mockSavedObjectsRepository); @@ -70,6 +71,33 @@ describe('#setupSavedObjects', () => { ).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(['hiddenType']); + expect(coreStartMock.savedObjects.createInternalRepository).toHaveBeenCalledWith([ + 'hiddenType', + ]); + }); + }); + describe('#getDecryptedAsInternalUser', () => { it('includes `namespace` for single-namespace saved objects', async () => { const mockSavedObject: SavedObject = { @@ -82,7 +110,7 @@ describe('#setupSavedObjects', () => { mockSavedObjectTypeRegistry.isSingleNamespace.mockReturnValue(true); await expect( - setupContract.getDecryptedAsInternalUser(mockSavedObject.type, mockSavedObject.id, { + setupContract().getDecryptedAsInternalUser(mockSavedObject.type, mockSavedObject.id, { namespace: 'some-ns', }) ).resolves.toEqual({ @@ -115,7 +143,7 @@ describe('#setupSavedObjects', () => { mockSavedObjectTypeRegistry.isSingleNamespace.mockReturnValue(false); await expect( - setupContract.getDecryptedAsInternalUser(mockSavedObject.type, mockSavedObject.id, { + setupContract().getDecryptedAsInternalUser(mockSavedObject.type, mockSavedObject.id, { namespace: 'some-ns', }) ).resolves.toEqual({ 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 9eca93ffd0b9eb..67bbaab75425ab 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 @@ -23,7 +23,9 @@ interface SetupSavedObjectsParams { getStartServices: StartServicesAccessor; } -export interface SavedObjectsSetup { +export type SavedObjectsSetup = (includedHiddenTypes?: string[]) => EncryptedSavedObjectsClient; + +export interface EncryptedSavedObjectsClient { getDecryptedAsInternalUser: ( type: string, id: string, @@ -54,33 +56,34 @@ export function setupSavedObjects({ }) ); - const internalRepositoryAndTypeRegistryPromise = getStartServices().then( - ([core]) => - [core.savedObjects.createInternalRepository(), core.savedObjects.getTypeRegistry()] as [ - ISavedObjectsRepository, - ISavedObjectTypeRegistry - ] - ); - - return { - getDecryptedAsInternalUser: async ( - type: string, - id: string, - options?: SavedObjectsBaseOptions - ): Promise> => { - const [internalRepository, typeRegistry] = await internalRepositoryAndTypeRegistryPromise; - const savedObject = await internalRepository.get(type, id, options); - return { - ...savedObject, - attributes: (await service.decryptAttributes( - { - type, - id, - namespace: typeRegistry.isSingleNamespace(type) ? options?.namespace : undefined, - }, - savedObject.attributes as Record - )) as T, - }; - }, + return (includedHiddenTypes?: string[]) => { + const internalRepositoryAndTypeRegistryPromise = getStartServices().then( + ([core]) => + [ + core.savedObjects.createInternalRepository(includedHiddenTypes), + core.savedObjects.getTypeRegistry(), + ] as [ISavedObjectsRepository, ISavedObjectTypeRegistry] + ); + return { + getDecryptedAsInternalUser: async ( + type: string, + id: string, + options?: SavedObjectsBaseOptions + ): Promise> => { + const [internalRepository, typeRegistry] = await internalRepositoryAndTypeRegistryPromise; + const savedObject = await internalRepository.get(type, id, options); + return { + ...savedObject, + attributes: (await service.decryptAttributes( + { + type, + id, + namespace: typeRegistry.isSingleNamespace(type) ? options?.namespace : undefined, + }, + savedObject.attributes as Record + )) as T, + }; + }, + }; }; } diff --git a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts index ae0dedce178a81..0d22529fdb0312 100644 --- a/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts +++ b/x-pack/plugins/ingest_manager/server/services/agents/acks.test.ts @@ -22,11 +22,15 @@ import { IngestManagerAppContext } from '../../plugin'; describe('test agent acks services', () => { it('should succeed on valid and matched actions', async () => { const mockSavedObjectsClient = savedObjectsClientMock.create(); - const mockStartEncryptedSOClient = encryptedSavedObjectsMock.createStart(); + const mockStartEncryptedSOPlugin = encryptedSavedObjectsMock.createStart(); appContextService.start(({ - encryptedSavedObjects: mockStartEncryptedSOClient, + encryptedSavedObjects: mockStartEncryptedSOPlugin, } as unknown) as IngestManagerAppContext); + const [ + { value: mockStartEncryptedSOClient }, + ] = mockStartEncryptedSOPlugin.getClient.mock.results; + mockStartEncryptedSOClient.getDecryptedAsInternalUser.mockReturnValue( Promise.resolve({ id: 'action1', diff --git a/x-pack/plugins/ingest_manager/server/services/app_context.ts b/x-pack/plugins/ingest_manager/server/services/app_context.ts index 91b09d651bf5cd..9e6220b6958f17 100644 --- a/x-pack/plugins/ingest_manager/server/services/app_context.ts +++ b/x-pack/plugins/ingest_manager/server/services/app_context.ts @@ -6,14 +6,14 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { first } from 'rxjs/operators'; import { SavedObjectsServiceStart, HttpServiceSetup, Logger } from 'src/core/server'; -import { EncryptedSavedObjectsPluginStart } from '../../../encrypted_saved_objects/server'; +import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; import { SecurityPluginSetup } from '../../../security/server'; import { IngestManagerConfigType } from '../../common'; import { IngestManagerAppContext } from '../plugin'; import { CloudSetup } from '../../../cloud/server'; class AppContextService { - private encryptedSavedObjects: EncryptedSavedObjectsPluginStart | undefined; + private encryptedSavedObjects: EncryptedSavedObjectsClient | undefined; private security: SecurityPluginSetup | undefined; private config$?: Observable; private configSubject$?: BehaviorSubject; @@ -25,7 +25,7 @@ class AppContextService { private httpSetup?: HttpServiceSetup; public async start(appContext: IngestManagerAppContext) { - this.encryptedSavedObjects = appContext.encryptedSavedObjects; + this.encryptedSavedObjects = appContext.encryptedSavedObjects?.getClient(); this.security = appContext.security; this.savedObjects = appContext.savedObjects; this.isProductionMode = appContext.isProductionMode; diff --git a/x-pack/plugins/security/server/saved_objects/index.ts b/x-pack/plugins/security/server/saved_objects/index.ts index 7dac745fcf84b3..40c17e5429aa8a 100644 --- a/x-pack/plugins/security/server/saved_objects/index.ts +++ b/x-pack/plugins/security/server/saved_objects/index.ts @@ -31,12 +31,12 @@ export function setupSavedObjects({ const getKibanaRequest = (request: KibanaRequest | LegacyRequest) => request instanceof KibanaRequest ? request : KibanaRequest.from(request); - savedObjects.setClientFactoryProvider(repositoryFactory => ({ request }) => { + savedObjects.setClientFactoryProvider(repositoryFactory => ({ request, includedHiddenTypes }) => { const kibanaRequest = getKibanaRequest(request); return new SavedObjectsClient( authz.mode.useRbacForRequest(kibanaRequest) - ? repositoryFactory.createInternalRepository() - : repositoryFactory.createScopedRepository(kibanaRequest) + ? repositoryFactory.createInternalRepository(includedHiddenTypes) + : repositoryFactory.createScopedRepository(kibanaRequest, includedHiddenTypes) ); }); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts index 0e9c71d8c20c81..4908b3338a10ab 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/aad/server/plugin.ts @@ -48,9 +48,11 @@ export class FixturePlugin implements Plugin, + deps: PluginsSetup, + hiddenTypes: string[] +) { + router.get( + { + path: '/api/hidden_saved_objects/get-decrypted-as-internal-user/{type}/{id}', + validate: { params: value => ({ value }) }, + }, + async (context, request, response) => { + const [, { encryptedSavedObjects }] = await core.getStartServices(); + const spaceId = deps.spaces.spacesService.getSpaceId(request); + const namespace = deps.spaces.spacesService.spaceIdToNamespace(spaceId); + try { + return response.ok({ + body: await encryptedSavedObjects + .getClient([request.params.type]) + .getDecryptedAsInternalUser(request.params.type, request.params.id, { namespace }), + }); + } catch (err) { + if (encryptedSavedObjects.isEncryptionError(err)) { + return response.badRequest({ body: 'Failed to encrypt attributes' }); + } + + return response.customError({ body: err, statusCode: 500 }); + } + } + ); + + router.get( + { + path: '/api/hidden_saved_objects/_find', + validate: { + query: schema.object({ + per_page: schema.number({ min: 0, defaultValue: 20 }), + page: schema.number({ min: 0, defaultValue: 1 }), + type: schema.oneOf([schema.string(), schema.arrayOf(schema.string())]), + search: schema.maybe(schema.string()), + default_search_operator: schema.oneOf([schema.literal('OR'), schema.literal('AND')], { + defaultValue: 'OR', + }), + search_fields: schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) + ), + sort_field: schema.maybe(schema.string()), + has_reference: schema.maybe( + schema.object({ + type: schema.string(), + id: schema.string(), + }) + ), + fields: schema.maybe(schema.oneOf([schema.string(), schema.arrayOf(schema.string())])), + filter: schema.maybe(schema.string()), + }), + }, + }, + async (context, request, response) => { + const query = request.query; + const [{ savedObjects }] = await core.getStartServices(); + return response.ok({ + body: await savedObjects + .getScopedClient(request, { includedHiddenTypes: hiddenTypes }) + .find({ + perPage: query.per_page, + page: query.page, + type: Array.isArray(query.type) ? query.type : [query.type], + search: query.search, + defaultSearchOperator: query.default_search_operator, + searchFields: + typeof query.search_fields === 'string' ? [query.search_fields] : query.search_fields, + sortField: query.sort_field, + hasReference: query.has_reference, + fields: typeof query.fields === 'string' ? [query.fields] : query.fields, + filter: query.filter, + }), + }); + } + ); + + router.get( + { + path: '/api/hidden_saved_objects/{type}/{id}', + validate: { params: value => ({ value }) }, + }, + async (context, request, response) => { + const [{ savedObjects }] = await core.getStartServices(); + return response.ok({ + body: await savedObjects + .getScopedClient(request, { includedHiddenTypes: hiddenTypes }) + .get(request.params.type, request.params.id), + }); + } + ); + router.post( + { + path: '/api/hidden_saved_objects/{type}', + validate: { + params: schema.object({ + type: schema.string(), + id: schema.maybe(schema.string()), + }), + query: schema.object({ + overwrite: schema.boolean({ defaultValue: false }), + }), + body: schema.object({ + attributes: schema.recordOf(schema.string(), schema.any()), + migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())), + references: schema.maybe( + schema.arrayOf( + schema.object({ + name: schema.string(), + type: schema.string(), + id: schema.string(), + }) + ) + ), + }), + }, + }, + async (context, request, response) => { + const [{ savedObjects }] = await core.getStartServices(); + const { type, id } = request.params; + const { attributes, migrationVersion, references } = request.body as any; + const options = { id, migrationVersion, references }; + const so = await savedObjects + .getScopedClient(request, { includedHiddenTypes: hiddenTypes }) + .create(type, attributes, options); + return response.ok({ + body: so, + }); + } + ); + router.put( + { + path: '/api/hidden_saved_objects/{type}/{id}', + validate: { + params: schema.object({ + type: schema.string(), + id: schema.string(), + }), + body: schema.object({ + attributes: schema.recordOf(schema.string(), schema.any()), + version: schema.maybe(schema.string()), + references: schema.maybe( + schema.arrayOf( + schema.object({ + name: schema.string(), + type: schema.string(), + id: schema.string(), + }) + ) + ), + }), + }, + }, + async (context, request, response) => { + const [{ savedObjects }] = await core.getStartServices(); + const { type, id } = request.params as any; + const { attributes, version, references } = request.body as any; + const options = { version, references }; + return response.ok({ + body: await savedObjects + .getScopedClient(request, { includedHiddenTypes: hiddenTypes }) + .update(type, id, attributes, options), + }); + } + ); + router.post( + { + path: '/api/hidden_saved_objects/_bulk_get', + validate: { + body: schema.arrayOf( + schema.object({ + type: schema.string(), + id: schema.string(), + fields: schema.maybe(schema.arrayOf(schema.string())), + }) + ), + }, + }, + async (context, request, response) => { + const [{ savedObjects }] = await core.getStartServices(); + return response.ok({ + body: await savedObjects + .getScopedClient(request, { includedHiddenTypes: hiddenTypes }) + .bulkGet(request.body as any), + }); + } + ); + router.post( + { + path: '/api/hidden_saved_objects/_bulk_create', + validate: { + body: schema.arrayOf( + schema.object({ + type: schema.string(), + id: schema.maybe(schema.string()), + attributes: schema.recordOf(schema.string(), schema.any()), + version: schema.maybe(schema.string()), + migrationVersion: schema.maybe(schema.recordOf(schema.string(), schema.string())), + references: schema.maybe( + schema.arrayOf( + schema.object({ + name: schema.string(), + type: schema.string(), + id: schema.string(), + }) + ) + ), + }) + ), + }, + }, + async (context, request, response) => { + const [{ savedObjects }] = await core.getStartServices(); + return response.ok({ + body: await savedObjects + .getScopedClient(request, { includedHiddenTypes: hiddenTypes }) + .bulkCreate(request.body as any), + }); + } + ); +} diff --git a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts index 46bb7f80246203..b0b73a54ceefea 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/fixtures/api_consumer_plugin/server/index.ts @@ -15,31 +15,34 @@ import { EncryptedSavedObjectsPluginStart, } from '../../../../../plugins/encrypted_saved_objects/server'; import { SpacesPluginSetup } from '../../../../../plugins/spaces/server'; +import { registerHiddenSORoutes } from './hidden_saved_object_routes'; const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret'; +const HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE = 'hidden-saved-object-with-secret'; const SAVED_OBJECT_WITH_SECRET_AND_MULTIPLE_SPACES_TYPE = 'saved-object-with-secret-and-multiple-spaces'; const SAVED_OBJECT_WITHOUT_SECRET_TYPE = 'saved-object-without-secret'; -interface PluginsSetup { +export interface PluginsSetup { encryptedSavedObjects: EncryptedSavedObjectsPluginSetup; spaces: SpacesPluginSetup; } -interface PluginsStart { +export interface PluginsStart { encryptedSavedObjects: EncryptedSavedObjectsPluginStart; spaces: never; } export const plugin: PluginInitializer = () => ({ setup(core: CoreSetup, deps) { - for (const [name, namespaceType] of [ - [SAVED_OBJECT_WITH_SECRET_TYPE, 'single'], - [SAVED_OBJECT_WITH_SECRET_AND_MULTIPLE_SPACES_TYPE, 'multiple'], - ] as Array<[string, SavedObjectsNamespaceType]>) { + for (const [name, namespaceType, hidden] of [ + [SAVED_OBJECT_WITH_SECRET_TYPE, 'single', false], + [HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE, 'single', true], + [SAVED_OBJECT_WITH_SECRET_AND_MULTIPLE_SPACES_TYPE, 'multiple', false], + ] as Array<[string, SavedObjectsNamespaceType, boolean]>) { core.savedObjects.registerType({ name, - hidden: false, + hidden, namespaceType, mappings: deepFreeze({ properties: { @@ -68,7 +71,8 @@ export const plugin: PluginInitializer = mappings: deepFreeze({ properties: { publicProperty: { type: 'keyword' } } }), }); - core.http.createRouter().get( + const router = core.http.createRouter(); + router.get( { path: '/api/saved_objects/get-decrypted-as-internal-user/{type}/{id}', validate: { params: value => ({ value }) }, @@ -80,11 +84,9 @@ export const plugin: PluginInitializer = try { return response.ok({ - body: await encryptedSavedObjects.getDecryptedAsInternalUser( - request.params.type, - request.params.id, - { namespace } - ), + body: await encryptedSavedObjects + .getClient() + .getDecryptedAsInternalUser(request.params.type, request.params.id, { namespace }), }); } catch (err) { if (encryptedSavedObjects.isEncryptionError(err)) { @@ -95,6 +97,8 @@ export const plugin: PluginInitializer = } } ); + + registerHiddenSORoutes(router, core, deps, [HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE]); }, start() {}, stop() {}, diff --git a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts index 54b1f00616c94a..2c97640b8d6504 100644 --- a/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts +++ b/x-pack/test/encrypted_saved_objects_api_integration/tests/encrypted_saved_objects_api.ts @@ -14,6 +14,7 @@ export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const SAVED_OBJECT_WITH_SECRET_TYPE = 'saved-object-with-secret'; + const HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE = 'hidden-saved-object-with-secret'; const SAVED_OBJECT_WITH_SECRET_AND_MULTIPLE_SPACES_TYPE = 'saved-object-with-secret-and-multiple-spaces'; const SAVED_OBJECT_WITHOUT_SECRET_TYPE = 'saved-object-without-secret'; @@ -438,7 +439,7 @@ export default function({ getService }: FtrProviderContext) { afterEach(async () => { await es.deleteByQuery({ index: '.kibana', - q: `type:${SAVED_OBJECT_WITH_SECRET_TYPE} OR type:${SAVED_OBJECT_WITH_SECRET_AND_MULTIPLE_SPACES_TYPE} OR type:${SAVED_OBJECT_WITHOUT_SECRET_TYPE}`, + q: `type:${SAVED_OBJECT_WITH_SECRET_TYPE} OR type:${HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE} OR type:${SAVED_OBJECT_WITH_SECRET_AND_MULTIPLE_SPACES_TYPE} OR type:${SAVED_OBJECT_WITHOUT_SECRET_TYPE}`, refresh: true, }); }); @@ -452,6 +453,14 @@ export default function({ getService }: FtrProviderContext) { ); }); + describe('hidden type with `single` namespace saved object', () => { + runTests( + HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE, + () => '/api/hidden_saved_objects/', + (id, type) => generateRawId(id, type) + ); + }); + describe('with `multiple` namespace saved object', () => { runTests( SAVED_OBJECT_WITH_SECRET_AND_MULTIPLE_SPACES_TYPE,