mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
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:
parent
1b222c73c7
commit
a867c0f85d
18 changed files with 319 additions and 62 deletions
|
@ -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"],
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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({});
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
};
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: [],
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue