[Saved Objects] adds support for including hidden types in saved objects client (#66879) (#67091)

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.
This commit is contained in:
Gidi Meir Morris 2020-05-20 14:53:13 +01:00 committed by GitHub
parent 67fdbfdd71
commit 68c9cda277
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
44 changed files with 615 additions and 185 deletions

View file

@ -9,7 +9,8 @@ Describes the factory used to create instances of the Saved Objects Client.
<b>Signature:</b>
```typescript
export declare type SavedObjectsClientFactory = ({ request, }: {
export declare type SavedObjectsClientFactory = ({ request, includedHiddenTypes, }: {
request: KibanaRequest;
includedHiddenTypes?: string[];
}) => SavedObjectsClientContract;
```

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [SavedObjectsClientProviderOptions](./kibana-plugin-core-server.savedobjectsclientprovideroptions.md) &gt; [includedHiddenTypes](./kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md)
## SavedObjectsClientProviderOptions.includedHiddenTypes property
<b>Signature:</b>
```typescript
includedHiddenTypes?: string[];
```

View file

@ -17,4 +17,5 @@ export interface SavedObjectsClientProviderOptions
| Property | Type | Description |
| --- | --- | --- |
| [excludedWrappers](./kibana-plugin-core-server.savedobjectsclientprovideroptions.excludedwrappers.md) | <code>string[]</code> | |
| [includedHiddenTypes](./kibana-plugin-core-server.savedobjectsclientprovideroptions.includedhiddentypes.md) | <code>string[]</code> | |

View file

@ -9,5 +9,5 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre
<b>Signature:</b>
```typescript
createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository;
createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository;
```

View file

@ -9,5 +9,5 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre
<b>Signature:</b>
```typescript
createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository;
createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository;
```

View file

@ -16,6 +16,6 @@ export interface SavedObjectsRepositoryFactory
| Property | Type | Description |
| --- | --- | --- |
| [createInternalRepository](./kibana-plugin-core-server.savedobjectsrepositoryfactory.createinternalrepository.md) | <code>(extraTypes?: string[]) =&gt; ISavedObjectsRepository</code> | 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) | <code>(req: KibanaRequest, extraTypes?: string[]) =&gt; ISavedObjectsRepository</code> | 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) | <code>(includedHiddenTypes?: string[]) =&gt; ISavedObjectsRepository</code> | 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) | <code>(req: KibanaRequest, includedHiddenTypes?: string[]) =&gt; ISavedObjectsRepository</code> | Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. |

View file

@ -9,5 +9,5 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre
<b>Signature:</b>
```typescript
createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository;
createInternalRepository: (includedHiddenTypes?: string[]) => ISavedObjectsRepository;
```

View file

@ -9,7 +9,7 @@ Creates a [Saved Objects repository](./kibana-plugin-core-server.isavedobjectsre
<b>Signature:</b>
```typescript
createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository;
createScopedRepository: (req: KibanaRequest, includedHiddenTypes?: string[]) => ISavedObjectsRepository;
```
## Remarks

View file

@ -16,8 +16,8 @@ export interface SavedObjectsServiceStart
| Property | Type | Description |
| --- | --- | --- |
| [createInternalRepository](./kibana-plugin-core-server.savedobjectsservicestart.createinternalrepository.md) | <code>(extraTypes?: string[]) =&gt; ISavedObjectsRepository</code> | 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) | <code>(req: KibanaRequest, extraTypes?: string[]) =&gt; ISavedObjectsRepository</code> | 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) | <code>(includedHiddenTypes?: string[]) =&gt; ISavedObjectsRepository</code> | 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) | <code>(req: KibanaRequest, includedHiddenTypes?: string[]) =&gt; ISavedObjectsRepository</code> | 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) | <code>() =&gt; SavedObjectsSerializer</code> | Creates a [serializer](./kibana-plugin-core-server.savedobjectsserializer.md) that is aware of all registered types. |
| [getScopedClient](./kibana-plugin-core-server.savedobjectsservicestart.getscopedclient.md) | <code>(req: KibanaRequest, options?: SavedObjectsClientProviderOptions) =&gt; SavedObjectsClientContract</code> | 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) | <code>() =&gt; ISavedObjectTypeRegistry</code> | Returns the [registry](./kibana-plugin-core-server.isavedobjecttyperegistry.md) containing all registered [saved object types](./kibana-plugin-core-server.savedobjectstype.md) |

View file

@ -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<any>).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<any>).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<any>).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<any>).mock.calls;
expect(includedHiddenTypes).toEqual(['someHiddenType']);
});
});
});
});

View file

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

View file

@ -131,7 +131,7 @@ export class SavedObjectsRepository {
typeRegistry: SavedObjectTypeRegistry,
indexName: string,
callCluster: APICaller,
extraTypes: string[] = [],
includedHiddenTypes: string[] = [],
injectedConstructor: any = SavedObjectsRepository
): ISavedObjectsRepository {
const mappings = migrator.getActiveMappings();
@ -139,14 +139,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,

View file

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

View file

@ -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 }) => {

View file

@ -1840,8 +1840,9 @@ export class SavedObjectsClient {
export type SavedObjectsClientContract = Pick<SavedObjectsClient, keyof SavedObjectsClient>;
// @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<any>;
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;

View file

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

View file

@ -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: [],
});

View file

@ -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<RawAction>('action', actionId, {
} = await encryptedSavedObjectsClient.getDecryptedAsInternalUser<RawAction>('action', actionId, {
namespace: namespace === 'default' ? undefined : namespace,
});

View file

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

View file

@ -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<ActionTaskParams>(
} = await encryptedSavedObjectsClient.getDecryptedAsInternalUser<ActionTaskParams>(
'action_task_params',
actionTaskParamsId,
{ namespace }

View file

@ -232,12 +232,14 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, 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<Promise<PluginSetupContract>, Plugi
taskRunnerFactory!.initialize({
logger,
actionTypeRegistry: actionTypeRegistry!,
encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects,
encryptedSavedObjectsClient,
getBasePath: this.getBasePath,
spaceIdToNamespace: this.spaceIdToNamespace,
getScopedSavedObjectsClient: core.savedObjects.getScopedClient,

View file

@ -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: [],
};

View file

@ -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<string | null>;
@ -128,7 +128,7 @@ export class AlertsClient {
params: InvalidateAPIKeyParams
) => Promise<InvalidateAPIKeyResult>;
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<RawAlert>;
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>('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>('alert', alertId);
const mutedInstanceIds = attributes.mutedInstanceIds || [];
if (!attributes.muteAll && mutedInstanceIds.includes(alertInstanceId)) {
await this.savedObjectsClient.update(

View file

@ -24,7 +24,7 @@ const alertsClientFactoryParams: jest.Mocked<AlertsClientFactoryOpts> = {
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: [],
});
});

View file

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

View file

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

View file

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

View file

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

View file

@ -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<RawAlert>(
} = await this.context.encryptedSavedObjectsClient.getDecryptedAsInternalUser<RawAlert>(
'alert',
alertId,
{ namespace }

View file

@ -56,7 +56,7 @@ describe('Task Runner Factory', () => {
const taskRunnerFactoryInitializerParams: jest.Mocked<TaskRunnerContext> = {
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),

View file

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

View file

@ -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) =>

View file

@ -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<EncryptedSavedObjectsPluginStart>;
}
function createEncryptedSavedObjectsClienttMock() {
return {
getDecryptedAsInternalUser: jest.fn(),
} as jest.Mocked<EncryptedSavedObjectsClient>;
}
export const encryptedSavedObjectsMock = {
createSetup: createEncryptedSavedObjectsSetupMock,
createStart: createEncryptedSavedObjectsStartMock,
createClient: createEncryptedSavedObjectsClienttMock,
};

View file

@ -30,10 +30,18 @@ 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 {
"getClient": [Function],
"isEncryptionError": [Function],
}
`);
expect(startContract.getClient()).toMatchInlineSnapshot(`
Object {
"getDecryptedAsInternalUser": [Function],
"isEncryptionError": [Function],
}
`);
});

View file

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

View file

@ -25,12 +25,13 @@ import { EncryptedSavedObjectsService } from '../crypto';
describe('#setupSavedObjects', () => {
let setupContract: SavedObjectsSetup;
let coreStartMock: ReturnType<typeof coreMock.createStart>;
let coreSetupMock: ReturnType<typeof coreMock.createSetup>;
let mockSavedObjectsRepository: jest.Mocked<ISavedObjectsRepository>;
let mockSavedObjectTypeRegistry: jest.Mocked<ISavedObjectTypeRegistry>;
let mockEncryptedSavedObjectsService: jest.Mocked<EncryptedSavedObjectsService>;
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({

View file

@ -23,7 +23,9 @@ interface SetupSavedObjectsParams {
getStartServices: StartServicesAccessor;
}
export interface SavedObjectsSetup {
export type SavedObjectsSetup = (includedHiddenTypes?: string[]) => EncryptedSavedObjectsClient;
export interface EncryptedSavedObjectsClient {
getDecryptedAsInternalUser: <T = unknown>(
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 <T = unknown>(
type: string,
id: string,
options?: SavedObjectsBaseOptions
): Promise<SavedObject<T>> => {
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<string, unknown>
)) as T,
};
},
return (includedHiddenTypes?: string[]) => {
const internalRepositoryAndTypeRegistryPromise = getStartServices().then(
([core]) =>
[
core.savedObjects.createInternalRepository(includedHiddenTypes),
core.savedObjects.getTypeRegistry(),
] as [ISavedObjectsRepository, ISavedObjectTypeRegistry]
);
return {
getDecryptedAsInternalUser: async <T = unknown>(
type: string,
id: string,
options?: SavedObjectsBaseOptions
): Promise<SavedObject<T>> => {
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<string, unknown>
)) as T,
};
},
};
};
}

View file

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

View file

@ -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<IngestManagerConfigType>;
private configSubject$?: BehaviorSubject<IngestManagerConfigType>;
@ -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;

View file

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

View file

@ -48,9 +48,11 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
if (spaces && req.body.spaceId) {
namespace = spaces.spacesService.spaceIdToNamespace(req.body.spaceId);
}
await encryptedSavedObjects.getDecryptedAsInternalUser(req.body.type, req.body.id, {
namespace,
});
await encryptedSavedObjects
.getClient()
.getDecryptedAsInternalUser(req.body.type, req.body.id, {
namespace,
});
return res.ok({ body: { success: true } });
} catch (err) {
return res.internalError({ body: err });

View file

@ -0,0 +1,235 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import { schema } from '@kbn/config-schema';
import { IRouter, CoreSetup } from 'kibana/server';
import { PluginsSetup, PluginsStart } from '.';
export function registerHiddenSORoutes(
router: IRouter,
core: CoreSetup<PluginsStart>,
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),
});
}
);
}

View file

@ -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<void, void, PluginsSetup, PluginsStart> = () => ({
setup(core: CoreSetup<PluginsStart>, 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<void, void, PluginsSetup, PluginsStart> =
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<void, void, PluginsSetup, PluginsStart> =
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<void, void, PluginsSetup, PluginsStart> =
}
}
);
registerHiddenSORoutes(router, core, deps, [HIDDEN_SAVED_OBJECT_WITH_SECRET_TYPE]);
},
start() {},
stop() {},

View file

@ -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';
@ -439,7 +440,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,
});
});
@ -453,6 +454,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,