[Event log] Use Alerts client & Actions client when fetching these types of SOs (#73257) (#74763)

Introduces a pluggable API to Event Log which allows custom Providers for Saved Objects which is used to ensure a user is authorised to get the Saved Object referenced in the Event Log whenever the find api is called.
This commit is contained in:
Gidi Meir Morris 2020-08-12 10:16:22 +01:00 committed by GitHub
parent 1b222c73c7
commit a867c0f85d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 319 additions and 62 deletions

View file

@ -4,6 +4,7 @@
"xpack.actions": "plugins/actions",
"xpack.uiActionsEnhanced": ["plugins/ui_actions_enhanced", "examples/ui_actions_enhanced_examples"],
"xpack.alerts": "plugins/alerts",
"xpack.eventLog": "plugins/event_log",
"xpack.alertingBuiltins": "plugins/alerting_builtins",
"xpack.apm": ["legacy/plugins/apm", "plugins/apm"],
"xpack.beatsManagement": ["legacy/plugins/beats_management", "plugins/beats_management"],

View file

@ -123,6 +123,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
private licenseState: ILicenseState | null = null;
private spaces?: SpacesServiceSetup;
private security?: SecurityPluginSetup;
private eventLogService?: IEventLogService;
private eventLogger?: IEventLogger;
private isESOUsingEphemeralEncryptionKey?: boolean;
private readonly telemetryLogger: Logger;
@ -160,6 +161,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
plugins.features.registerFeature(ACTIONS_FEATURE);
setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects);
this.eventLogService = plugins.eventLog;
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
this.eventLogger = plugins.eventLog.getLogger({
event: { provider: EVENT_LOG_PROVIDER },
@ -295,6 +297,11 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
});
};
this.eventLogService!.registerSavedObjectProvider('action', (request) => {
const client = getActionsClientWithRequest(request);
return async (type: string, id: string) => (await client).get({ id });
});
const getScopedSavedObjectsClientWithoutAccessToActions = (request: KibanaRequest) =>
core.savedObjects.getScopedClient(request);

View file

@ -106,6 +106,7 @@ export class AlertingPlugin {
private readonly alertsClientFactory: AlertsClientFactory;
private readonly telemetryLogger: Logger;
private readonly kibanaIndex: Promise<string>;
private eventLogService?: IEventLogService;
private eventLogger?: IEventLogger;
constructor(initializerContext: PluginInitializerContext) {
@ -150,6 +151,7 @@ export class AlertingPlugin {
setupSavedObjects(core.savedObjects, plugins.encryptedSavedObjects);
this.eventLogService = plugins.eventLog;
plugins.eventLog.registerProviderActions(EVENT_LOG_PROVIDER, Object.values(EVENT_LOG_ACTIONS));
this.eventLogger = plugins.eventLog.getLogger({
event: { provider: EVENT_LOG_PROVIDER },
@ -255,6 +257,11 @@ export class AlertingPlugin {
eventLogger: this.eventLogger!,
});
this.eventLogService!.registerSavedObjectProvider('alert', (request) => {
const client = getAlertsClientWithRequest(request);
return (type: string, id: string) => client.get({ id });
});
scheduleAlertingTelemetry(this.telemetryLogger, plugins.taskManager);
return {

View file

@ -7,7 +7,6 @@
import { KibanaRequest } from 'src/core/server';
import { EventLogClient } from './event_log_client';
import { contextMock } from './es/context.mock';
import { savedObjectsClientMock } from 'src/core/server/mocks';
import { merge } from 'lodash';
import moment from 'moment';
@ -15,14 +14,15 @@ describe('EventLogStart', () => {
describe('findEventsBySavedObject', () => {
test('verifies that the user can access the specified saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});
savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
@ -31,19 +31,21 @@ describe('EventLogStart', () => {
await eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id');
expect(savedObjectsClient.get).toHaveBeenCalledWith('saved-object-type', 'saved-object-id');
expect(savedObjectGetter).toHaveBeenCalledWith('saved-object-type', 'saved-object-id');
});
test('throws when the user doesnt have permission to access the specified saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});
savedObjectsClient.get.mockRejectedValue(new Error('Fail'));
savedObjectGetter.mockRejectedValue(new Error('Fail'));
expect(
eventLogClient.findEventsBySavedObject('saved-object-type', 'saved-object-id')
@ -52,14 +54,16 @@ describe('EventLogStart', () => {
test('fetches all event that reference the saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});
savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
@ -125,14 +129,16 @@ describe('EventLogStart', () => {
test('fetches all events in time frame that reference the saved object', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});
savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
@ -206,14 +212,16 @@ describe('EventLogStart', () => {
test('validates that the start date is valid', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});
savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
@ -236,14 +244,16 @@ describe('EventLogStart', () => {
test('validates that the end date is valid', async () => {
const esContext = contextMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();
const eventLogClient = new EventLogClient({
esContext,
savedObjectsClient,
savedObjectGetter,
request: FakeRequest(),
});
savedObjectsClient.get.mockResolvedValueOnce({
savedObjectGetter.mockResolvedValueOnce({
id: 'saved-object-id',
type: 'saved-object-type',
attributes: {},
@ -297,7 +307,8 @@ function fakeEvent(overrides = {}) {
}
function FakeRequest(): KibanaRequest {
const savedObjectsClient = savedObjectsClientMock.create();
const savedObjectGetter = jest.fn();
return ({
headers: {},
getBasePath: () => '',
@ -311,6 +322,6 @@ function FakeRequest(): KibanaRequest {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
getSavedObjectsClient: () => savedObjectGetter,
} as unknown) as KibanaRequest;
}

View file

@ -6,12 +6,13 @@
import { Observable } from 'rxjs';
import { schema, TypeOf } from '@kbn/config-schema';
import { LegacyClusterClient, SavedObjectsClientContract, KibanaRequest } from 'src/core/server';
import { LegacyClusterClient, KibanaRequest } from 'src/core/server';
import { SpacesServiceSetup } from '../../spaces/server';
import { EsContext } from './es';
import { IEventLogClient } from './types';
import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter';
import { SavedObjectGetter } from './saved_object_provider_registry';
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
export type AdminClusterClient$ = Observable<PluginClusterClient>;
@ -58,7 +59,7 @@ export type FindOptionsType = Pick<
interface EventLogServiceCtorParams {
esContext: EsContext;
savedObjectsClient: SavedObjectsClientContract;
savedObjectGetter: SavedObjectGetter;
spacesService?: SpacesServiceSetup;
request: KibanaRequest;
}
@ -66,18 +67,13 @@ interface EventLogServiceCtorParams {
// note that clusterClient may be null, indicating we can't write to ES
export class EventLogClient implements IEventLogClient {
private esContext: EsContext;
private savedObjectsClient: SavedObjectsClientContract;
private savedObjectGetter: SavedObjectGetter;
private spacesService?: SpacesServiceSetup;
private request: KibanaRequest;
constructor({
esContext,
savedObjectsClient,
spacesService,
request,
}: EventLogServiceCtorParams) {
constructor({ esContext, savedObjectGetter, spacesService, request }: EventLogServiceCtorParams) {
this.esContext = esContext;
this.savedObjectsClient = savedObjectsClient;
this.savedObjectGetter = savedObjectGetter;
this.spacesService = spacesService;
this.request = request;
}
@ -93,7 +89,7 @@ export class EventLogClient implements IEventLogClient {
const namespace = space && this.spacesService?.spaceIdToNamespace(space.id);
// verify the user has the required permissions to view this saved object
await this.savedObjectsClient.get(type, id);
await this.savedObjectGetter(type, id);
return await this.esContext.esAdapter.queryEventsBySavedObject(
this.esContext.esNames.alias,

View file

@ -15,6 +15,7 @@ const createEventLogServiceMock = () => {
registerProviderActions: jest.fn(),
isProviderActionRegistered: jest.fn(),
getProviderActions: jest.fn(),
registerSavedObjectProvider: jest.fn(),
getLogger: jest.fn().mockReturnValue(eventLoggerMock.create()),
};
return mock;

View file

@ -8,9 +8,11 @@ import { IEventLogConfig } from './types';
import { EventLogService } from './event_log_service';
import { contextMock } from './es/context.mock';
import { loggingSystemMock } from 'src/core/server/mocks';
import { savedObjectProviderRegistryMock } from './saved_object_provider_registry.mock';
const loggingService = loggingSystemMock.create();
const systemLogger = loggingService.get();
const savedObjectProviderRegistry = savedObjectProviderRegistryMock.create();
describe('EventLogService', () => {
const esContext = contextMock.create();
@ -21,6 +23,7 @@ describe('EventLogService', () => {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled,
logEntries,
@ -65,6 +68,7 @@ describe('EventLogService', () => {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled: true,
logEntries: true,
@ -102,6 +106,7 @@ describe('EventLogService', () => {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled: true,
logEntries: true,
@ -112,4 +117,24 @@ describe('EventLogService', () => {
const eventLogger = service.getLogger({});
expect(eventLogger).toBeTruthy();
});
describe('registerSavedObjectProvider', () => {
test('register SavedObject Providers in the registry', () => {
const params = {
esContext,
systemLogger,
kibanaUUID: '42',
savedObjectProviderRegistry,
config: {
enabled: true,
logEntries: true,
indexEntries: true,
},
};
const service = new EventLogService(params);
const provider = jest.fn();
service.registerSavedObjectProvider('myType', provider);
expect(savedObjectProviderRegistry.registerProvider).toHaveBeenCalledWith('myType', provider);
});
});
});

View file

@ -11,6 +11,7 @@ import { Plugin } from './plugin';
import { EsContext } from './es';
import { IEvent, IEventLogger, IEventLogService, IEventLogConfig } from './types';
import { EventLogger } from './event_logger';
import { SavedObjectProvider, SavedObjectProviderRegistry } from './saved_object_provider_registry';
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
export type AdminClusterClient$ = Observable<PluginClusterClient>;
@ -21,6 +22,7 @@ interface EventLogServiceCtorParams {
esContext: EsContext;
kibanaUUID: string;
systemLogger: SystemLogger;
savedObjectProviderRegistry: SavedObjectProviderRegistry;
}
// note that clusterClient may be null, indicating we can't write to ES
@ -29,15 +31,23 @@ export class EventLogService implements IEventLogService {
private esContext: EsContext;
private systemLogger: SystemLogger;
private registeredProviderActions: Map<string, Set<string>>;
private savedObjectProviderRegistry: SavedObjectProviderRegistry;
public readonly kibanaUUID: string;
constructor({ config, esContext, kibanaUUID, systemLogger }: EventLogServiceCtorParams) {
constructor({
config,
esContext,
kibanaUUID,
systemLogger,
savedObjectProviderRegistry,
}: EventLogServiceCtorParams) {
this.config = config;
this.esContext = esContext;
this.kibanaUUID = kibanaUUID;
this.systemLogger = systemLogger;
this.registeredProviderActions = new Map<string, Set<string>>();
this.savedObjectProviderRegistry = savedObjectProviderRegistry;
}
public isEnabled(): boolean {
@ -77,6 +87,10 @@ export class EventLogService implements IEventLogService {
return new Map(this.registeredProviderActions.entries());
}
registerSavedObjectProvider(type: string, provider: SavedObjectProvider) {
return this.savedObjectProviderRegistry.registerProvider(type, provider);
}
getLogger(initialProperties: IEvent): IEventLogger {
return new EventLogger({
esContext: this.esContext,

View file

@ -5,10 +5,11 @@
*/
import { KibanaRequest } from 'src/core/server';
import { savedObjectsClientMock, savedObjectsServiceMock } from 'src/core/server/mocks';
import { savedObjectsClientMock } from 'src/core/server/mocks';
import { EventLogClientService } from './event_log_start_service';
import { contextMock } from './es/context.mock';
import { savedObjectProviderRegistryMock } from './saved_object_provider_registry.mock';
jest.mock('./event_log_client');
@ -17,19 +18,17 @@ describe('EventLogClientService', () => {
describe('getClient', () => {
test('creates a client with a scoped SavedObjects client', () => {
const savedObjectsService = savedObjectsServiceMock.createStartContract();
const savedObjectProviderRegistry = savedObjectProviderRegistryMock.create();
const request = fakeRequest();
const eventLogStartService = new EventLogClientService({
esContext,
savedObjectsService,
savedObjectProviderRegistry,
});
eventLogStartService.getClient(request);
expect(savedObjectsService.getScopedClient).toHaveBeenCalledWith(request, {
includedHiddenTypes: ['action', 'alert'],
});
expect(savedObjectProviderRegistry.getProvidersClient).toHaveBeenCalledWith(request);
});
});
});

View file

@ -5,49 +5,42 @@
*/
import { Observable } from 'rxjs';
import {
LegacyClusterClient,
KibanaRequest,
SavedObjectsServiceStart,
SavedObjectsClientContract,
} from 'src/core/server';
import { LegacyClusterClient, KibanaRequest } from 'src/core/server';
import { SpacesServiceSetup } from '../../spaces/server';
import { EsContext } from './es';
import { IEventLogClientService } from './types';
import { EventLogClient } from './event_log_client';
import { SavedObjectProviderRegistry } from './saved_object_provider_registry';
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
export type AdminClusterClient$ = Observable<PluginClusterClient>;
const includedHiddenTypes = ['action', 'alert'];
interface EventLogServiceCtorParams {
esContext: EsContext;
savedObjectsService: SavedObjectsServiceStart;
savedObjectProviderRegistry: SavedObjectProviderRegistry;
spacesService?: SpacesServiceSetup;
}
// note that clusterClient may be null, indicating we can't write to ES
export class EventLogClientService implements IEventLogClientService {
private esContext: EsContext;
private savedObjectsService: SavedObjectsServiceStart;
private savedObjectProviderRegistry: SavedObjectProviderRegistry;
private spacesService?: SpacesServiceSetup;
constructor({ esContext, savedObjectsService, spacesService }: EventLogServiceCtorParams) {
constructor({
esContext,
savedObjectProviderRegistry,
spacesService,
}: EventLogServiceCtorParams) {
this.esContext = esContext;
this.savedObjectsService = savedObjectsService;
this.savedObjectProviderRegistry = savedObjectProviderRegistry;
this.spacesService = spacesService;
}
getClient(request: KibanaRequest) {
const savedObjectsClient: SavedObjectsClientContract = this.savedObjectsService.getScopedClient(
request,
{ includedHiddenTypes }
);
return new EventLogClient({
esContext: this.esContext,
savedObjectsClient,
savedObjectGetter: this.savedObjectProviderRegistry.getProvidersClient(request),
spacesService: this.spacesService,
request,
});

View file

@ -12,6 +12,7 @@ import { contextMock } from './es/context.mock';
import { loggingSystemMock } from 'src/core/server/mocks';
import { delay } from './lib/delay';
import { EVENT_LOGGED_PREFIX } from './event_logger';
import { savedObjectProviderRegistryMock } from './saved_object_provider_registry.mock';
const KIBANA_SERVER_UUID = '424-24-2424';
const WRITE_LOG_WAIT_MILLIS = 3000;
@ -31,6 +32,7 @@ describe('EventLogger', () => {
systemLogger,
config: { enabled: true, logEntries: true, indexEntries: true },
kibanaUUID: KIBANA_SERVER_UUID,
savedObjectProviderRegistry: savedObjectProviderRegistryMock.create(),
});
eventLogger = service.getLogger({});
});

View file

@ -30,6 +30,7 @@ import { findRoute } from './routes';
import { EventLogService } from './event_log_service';
import { createEsContext, EsContext } from './es';
import { EventLogClientService } from './event_log_start_service';
import { SavedObjectProviderRegistry } from './saved_object_provider_registry';
export type PluginClusterClient = Pick<LegacyClusterClient, 'callAsInternalUser' | 'asScoped'>;
@ -53,11 +54,13 @@ export class Plugin implements CorePlugin<IEventLogService, IEventLogClientServi
private globalConfig$: Observable<SharedGlobalConfig>;
private eventLogClientService?: EventLogClientService;
private spacesService?: SpacesServiceSetup;
private savedObjectProviderRegistry: SavedObjectProviderRegistry;
constructor(private readonly context: PluginInitializerContext) {
this.systemLogger = this.context.logger.get();
this.config$ = this.context.config.create<IEventLogConfig>();
this.globalConfig$ = this.context.config.legacy.globalConfig$;
this.savedObjectProviderRegistry = new SavedObjectProviderRegistry();
}
async setup(core: CoreSetup, { spaces }: PluginSetupDeps): Promise<IEventLogService> {
@ -83,6 +86,7 @@ export class Plugin implements CorePlugin<IEventLogService, IEventLogClientServi
esContext: this.esContext,
systemLogger: this.systemLogger,
kibanaUUID: core.uuid.getInstanceUuid(),
savedObjectProviderRegistry: this.savedObjectProviderRegistry,
});
this.eventLogService.registerProviderActions(PROVIDER, Object.values(ACTIONS));
@ -119,9 +123,14 @@ export class Plugin implements CorePlugin<IEventLogService, IEventLogClientServi
message: 'eventLog starting',
});
this.savedObjectProviderRegistry.registerDefaultProvider((request) => {
const client = core.savedObjects.getScopedClient(request);
return client.get.bind(client);
});
this.eventLogClientService = new EventLogClientService({
esContext: this.esContext,
savedObjectsService: core.savedObjects,
savedObjectProviderRegistry: this.savedObjectProviderRegistry,
spacesService: this.spacesService,
});
return this.eventLogClientService;

View file

@ -0,0 +1,19 @@
/*
* 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 { SavedObjectProviderRegistry } from './saved_object_provider_registry';
const createSavedObjectProviderRegistryMock = () => {
return ({
registerProvider: jest.fn(),
registerDefaultProvider: jest.fn(),
getProvidersClient: jest.fn(),
} as unknown) as jest.Mocked<SavedObjectProviderRegistry>;
};
export const savedObjectProviderRegistryMock = {
create: createSavedObjectProviderRegistryMock,
};

View file

@ -0,0 +1,98 @@
/*
* 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 { SavedObjectProviderRegistry } from './saved_object_provider_registry';
import uuid from 'uuid';
import { KibanaRequest } from 'src/core/server';
import { savedObjectsClientMock } from 'src/core/server/mocks';
describe('SavedObjectProviderRegistry', () => {
beforeEach(() => jest.resetAllMocks());
describe('registerProvider()', () => {
test('should register providers', () => {
const registry = new SavedObjectProviderRegistry();
registry.registerProvider('alert', jest.fn());
});
test('should throw an error if type is already registered', () => {
const registry = new SavedObjectProviderRegistry();
registry.registerProvider('alert', jest.fn());
expect(() =>
registry.registerProvider('alert', jest.fn())
).toThrowErrorMatchingInlineSnapshot(
`"The Event Log has already registered a Provider for the Save Object type \\"alert\\"."`
);
});
});
describe('getProvidersClient()', () => {
test('should get SavedObject using the registered provider by type', async () => {
const registry = new SavedObjectProviderRegistry();
registry.registerDefaultProvider(jest.fn());
const getter = jest.fn();
const provider = jest.fn().mockReturnValue(getter);
registry.registerProvider('alert', provider);
const request = fakeRequest();
const alert = {
id: uuid.v4(),
};
getter.mockResolvedValue(alert);
expect(await registry.getProvidersClient(request)('alert', alert.id)).toMatchObject(alert);
expect(provider).toHaveBeenCalledWith(request);
expect(getter).toHaveBeenCalledWith('alert', alert.id);
});
test('should get SavedObject using the default provider for unregistered types', async () => {
const registry = new SavedObjectProviderRegistry();
const defaultProvider = jest.fn();
registry.registerDefaultProvider(defaultProvider);
registry.registerProvider('alert', jest.fn().mockReturnValue(jest.fn()));
const request = fakeRequest();
const action = {
id: uuid.v4(),
type: 'action',
attributes: {},
references: [],
};
const getter = jest.fn();
defaultProvider.mockReturnValue(getter);
getter.mockResolvedValue(action);
expect(await registry.getProvidersClient(request)('action', action.id)).toMatchObject(action);
expect(getter).toHaveBeenCalledWith('action', action.id);
expect(defaultProvider).toHaveBeenCalledWith(request);
});
});
});
function fakeRequest(): KibanaRequest {
const savedObjectsClient = savedObjectsClientMock.create();
return ({
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
getSavedObjectsClient: () => savedObjectsClient,
} as unknown) as KibanaRequest;
}

View file

@ -0,0 +1,68 @@
/*
* 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 { i18n } from '@kbn/i18n';
import { KibanaRequest, SavedObjectsClientContract } from 'src/core/server';
import { fromNullable, getOrElse } from 'fp-ts/lib/Option';
import { pipe } from 'fp-ts/lib/pipeable';
export type SavedObjectGetter = (
...params: Parameters<SavedObjectsClientContract['get']>
) => Promise<unknown>;
export type SavedObjectProvider = (request: KibanaRequest) => SavedObjectGetter;
export class SavedObjectProviderRegistry {
private providers = new Map<string, SavedObjectProvider>();
private defaultProvider?: SavedObjectProvider;
constructor() {}
public registerDefaultProvider(provider: SavedObjectProvider) {
this.defaultProvider = provider;
}
public registerProvider(type: string, provider: SavedObjectProvider) {
if (this.providers.has(type)) {
throw new Error(
`The Event Log has already registered a Provider for the Save Object type "${type}".`
);
}
this.providers.set(type, provider);
}
public getProvidersClient(request: KibanaRequest): SavedObjectGetter {
if (!this.defaultProvider) {
throw new Error(
i18n.translate(
'xpack.eventLog.savedObjectProviderRegistry.getProvidersClient.noDefaultProvider',
{
defaultMessage: 'The Event Log requires a default Provider.',
}
)
);
}
// `scopedProviders` is a cache of providers which are scoped t othe current request.
// The client will only instantiate a provider on-demand and it will cache each
// one to enable the request to reuse each provider.
const scopedProviders = new Map<string, SavedObjectGetter>();
const defaultGetter = this.defaultProvider(request);
return (type: string, id: string) => {
const getter = pipe(
fromNullable(scopedProviders.get(type)),
getOrElse(() => {
const client = this.providers.has(type)
? this.providers.get(type)!(request)
: defaultGetter;
scopedProviders.set(type, client);
return client;
})
);
return getter(type, id);
};
}
}

View file

@ -12,6 +12,7 @@ export { IEvent, IValidatedEvent, EventSchema, ECS_VERSION } from '../generated/
import { IEvent } from '../generated/schemas';
import { FindOptionsType } from './event_log_client';
import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter';
import { SavedObjectProvider } from './saved_object_provider_registry';
export const SAVED_OBJECT_REL_PRIMARY = 'primary';
@ -40,7 +41,7 @@ export interface IEventLogService {
registerProviderActions(provider: string, actions: string[]): void;
isProviderActionRegistered(provider: string, action: string): boolean;
getProviderActions(): Map<string, Set<string>>;
registerSavedObjectProvider(type: string, provider: SavedObjectProvider): void;
getLogger(properties: IEvent): IEventLogger;
}

View file

@ -296,7 +296,7 @@ export function defineAlertTypes(
name: 'Default',
},
],
producer: 'alerting',
producer: 'alertsFixture',
defaultActionGroupId: 'default',
async executor({ services, params, state }: AlertExecutorOptions) {
throw new Error('this alert is intended to fail');
@ -306,7 +306,7 @@ export function defineAlertTypes(
id: 'test.patternFiring',
name: 'Test: Firing on a Pattern',
actionGroups: [{ id: 'default', name: 'Default' }],
producer: 'alerting',
producer: 'alertsFixture',
defaultActionGroupId: 'default',
async executor(alertExecutorOptions: AlertExecutorOptions) {
const { services, state, params } = alertExecutorOptions;

View file

@ -40,6 +40,8 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
'test.onlyStateVariables',
'test.noop',
'test.unrestricted-noop',
'test.patternFiring',
'test.throw',
],
privileges: {
all: {
@ -60,6 +62,8 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
'test.onlyStateVariables',
'test.noop',
'test.unrestricted-noop',
'test.patternFiring',
'test.throw',
],
},
ui: [],
@ -82,6 +86,8 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
'test.onlyStateVariables',
'test.noop',
'test.unrestricted-noop',
'test.patternFiring',
'test.throw',
],
},
ui: [],