-
Notifications
You must be signed in to change notification settings - Fork 8.5k
Add notifications plugin, offering basic email service #143303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
2a93a37
dc9829e
2fe5ce5
dbed0a8
4af9bc6
c3ce0b1
00c611d
b0fba11
baf051a
96b696f
6cf7a3a
74aa62d
fa4a356
8865a80
7c884ae
f4c1851
3b7a388
4b7f482
9a58040
47f1ca9
c87baee
28c6632
db2dc2a
d5bf4ff
026646a
ea31342
802dfa0
c048225
f149cac
cd500db
a293085
ff65387
e241d0d
0079193
7f358bf
8929996
fd4b2ef
c344a61
b02bd81
53575d9
52e7c8c
f214324
e0dd0f7
8218cc4
0ccf326
a561085
2d26740
3ed6752
9dbe577
0238877
06ff938
dc4c9a6
2654fee
1467966
091eb74
d6e46f5
8d026c5
f275913
56f99c9
3aef92a
439b3e7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,158 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import { compact } from 'lodash'; | ||
| import { ISavedObjectsRepository, SavedObjectsBulkResponse } from '@kbn/core/server'; | ||
| import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server'; | ||
| import { | ||
| ActionTypeRegistryContract as ConnectorTypeRegistryContract, | ||
| PreConfiguredAction as PreconfiguredConnector, | ||
| } from './types'; | ||
| import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects'; | ||
| import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor'; | ||
| import { extractSavedObjectReferences, isSavedObjectExecutionSource } from './lib'; | ||
| import { RelatedSavedObjects } from './lib/related_saved_objects'; | ||
|
|
||
| const ALLOWED_CONNECTOR_TYPE_IDS = ['.email', '.slack']; | ||
| interface CreateBulkUnsecuredExecuteFunctionOptions { | ||
| taskManager: TaskManagerStartContract; | ||
| connectorTypeRegistry: ConnectorTypeRegistryContract; | ||
| preconfiguredConnectors: PreconfiguredConnector[]; | ||
| } | ||
|
|
||
| export interface ExecuteOptions extends Pick<ActionExecutorOptions, 'params' | 'source'> { | ||
| id: string; | ||
| executionId: string; | ||
| consumer?: string; | ||
| relatedSavedObjects?: RelatedSavedObjects; | ||
| } | ||
|
|
||
| export interface ActionTaskParams extends Pick<ActionExecutorOptions, 'params'> { | ||
| actionId: string; | ||
| apiKey: string | null; | ||
| executionId: string; | ||
| consumer?: string; | ||
| relatedSavedObjects?: RelatedSavedObjects; | ||
| } | ||
|
|
||
| export type BulkUnsecuredExecutionEnqueuer<T> = ( | ||
| internalSavedObjectsRepository: ISavedObjectsRepository, | ||
| actionsToExectute: ExecuteOptions[] | ||
| ) => Promise<T>; | ||
|
|
||
| export function createBulkUnsecuredExecutionEnqueuerFunction({ | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am curious what does
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the idea is to highlight the fact that this API does not require any form of authentication whatsoever (apiKey or request object), so it should be used cautiously. This is part of the changes that response-ops are performing to support the case assignment email notifications.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Dosant Normally action execution enqueuing is secured via Kibana RBAC where the actions client checks that the user has access to the |
||
| taskManager, | ||
| connectorTypeRegistry, | ||
| preconfiguredConnectors, | ||
| }: CreateBulkUnsecuredExecuteFunctionOptions): BulkUnsecuredExecutionEnqueuer<void> { | ||
| return async function execute( | ||
| internalSavedObjectsRepository: ISavedObjectsRepository, | ||
| actionsToExecute: ExecuteOptions[] | ||
| ) { | ||
| const connectorTypeIds: Record<string, string> = {}; | ||
| const connectorIds = [...new Set(actionsToExecute.map((action) => action.id))]; | ||
|
|
||
| const notPreconfiguredConnectors = connectorIds.filter( | ||
| (connectorId) => | ||
| preconfiguredConnectors.find((connector) => connector.id === connectorId) == null | ||
| ); | ||
|
|
||
| if (notPreconfiguredConnectors.length > 0) { | ||
| throw new Error( | ||
| `${notPreconfiguredConnectors.join( | ||
| ',' | ||
| )} are not preconfigured connectors and can't be scheduled for unsecured actions execution` | ||
| ); | ||
| } | ||
|
|
||
| const connectors: PreconfiguredConnector[] = compact( | ||
ymao1 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| connectorIds.map((connectorId) => | ||
| preconfiguredConnectors.find((pConnector) => pConnector.id === connectorId) | ||
| ) | ||
| ); | ||
|
|
||
| connectors.forEach((connector) => { | ||
| const { id, actionTypeId } = connector; | ||
| if (!connectorTypeRegistry.isActionExecutable(id, actionTypeId, { notifyUsage: true })) { | ||
| connectorTypeRegistry.ensureActionTypeEnabled(actionTypeId); | ||
| } | ||
|
|
||
| if (!ALLOWED_CONNECTOR_TYPE_IDS.includes(actionTypeId)) { | ||
| throw new Error( | ||
| `${actionTypeId} actions cannot be scheduled for unsecured actions execution` | ||
| ); | ||
| } | ||
|
|
||
| connectorTypeIds[id] = actionTypeId; | ||
| }); | ||
|
|
||
| const actions = await Promise.all( | ||
|
||
| actionsToExecute.map(async (actionToExecute) => { | ||
| // Get saved object references from action ID and relatedSavedObjects | ||
| const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences( | ||
| actionToExecute.id, | ||
| true, | ||
| actionToExecute.relatedSavedObjects | ||
| ); | ||
| const executionSourceReference = executionSourceAsSavedObjectReferences( | ||
| actionToExecute.source | ||
| ); | ||
|
|
||
| const taskReferences = []; | ||
| if (executionSourceReference.references) { | ||
| taskReferences.push(...executionSourceReference.references); | ||
| } | ||
| if (references) { | ||
| taskReferences.push(...references); | ||
| } | ||
|
|
||
| return { | ||
| type: ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE, | ||
| attributes: { | ||
| actionId: actionToExecute.id, | ||
| params: actionToExecute.params, | ||
| apiKey: null, | ||
| executionId: actionToExecute.executionId, | ||
| consumer: actionToExecute.consumer, | ||
| relatedSavedObjects: relatedSavedObjectWithRefs, | ||
| }, | ||
| references: taskReferences, | ||
| }; | ||
| }) | ||
| ); | ||
|
|
||
| const actionTaskParamsRecords: SavedObjectsBulkResponse<ActionTaskParams> = | ||
| await internalSavedObjectsRepository.bulkCreate(actions); | ||
|
|
||
| const taskInstances = actionTaskParamsRecords.saved_objects.map((so) => { | ||
| const actionId = so.attributes.actionId; | ||
| return { | ||
| taskType: `actions:${connectorTypeIds[actionId]}`, | ||
| params: { | ||
| spaceId: 'default', | ||
| actionTaskParamsId: so.id, | ||
| }, | ||
| state: {}, | ||
| scope: ['actions'], | ||
| }; | ||
| }); | ||
| await taskManager.bulkSchedule(taskInstances); | ||
| }; | ||
| } | ||
|
|
||
| function executionSourceAsSavedObjectReferences(executionSource: ActionExecutorOptions['source']) { | ||
| return isSavedObjectExecutionSource(executionSource) | ||
| ? { | ||
| references: [ | ||
| { | ||
| name: 'source', | ||
| ...executionSource.source, | ||
| }, | ||
| ], | ||
| } | ||
| : {}; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /* | ||
| * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
| * or more contributor license agreements. Licensed under the Elastic License | ||
| * 2.0; you may not use this file except in compliance with the Elastic License | ||
| * 2.0. | ||
| */ | ||
|
|
||
| import { ISavedObjectsRepository } from '@kbn/core/server'; | ||
| import { UnsecuredActionsClientAccessRegistry } from './unsecured_actions_client_access_registry'; | ||
| import { | ||
| BulkUnsecuredExecutionEnqueuer, | ||
| ExecuteOptions, | ||
| } from '../create_unsecured_execute_function'; | ||
|
|
||
| export interface UnsecuredActionsClientOpts { | ||
| unsecuredActionsClientAccessRegistry: UnsecuredActionsClientAccessRegistry; | ||
| internalSavedObjectsRepository: ISavedObjectsRepository; | ||
| executionEnqueuer: BulkUnsecuredExecutionEnqueuer<void>; | ||
| } | ||
|
|
||
| export class UnsecuredActionsClient { | ||
| private readonly unsecuredActionsClientAccessRegistry: UnsecuredActionsClientAccessRegistry; | ||
| private readonly internalSavedObjectsRepository: ISavedObjectsRepository; | ||
| private readonly executionEnqueuer: BulkUnsecuredExecutionEnqueuer<void>; | ||
|
|
||
| constructor(params: UnsecuredActionsClientOpts) { | ||
| this.unsecuredActionsClientAccessRegistry = params.unsecuredActionsClientAccessRegistry; | ||
| this.executionEnqueuer = params.executionEnqueuer; | ||
| this.internalSavedObjectsRepository = params.internalSavedObjectsRepository; | ||
| } | ||
|
|
||
| public async bulkEnqueueExecution( | ||
| requesterId: string, | ||
| actionsToExecute: ExecuteOptions[] | ||
| ): Promise<void> { | ||
| // Check that requesterId is allowed | ||
| if (!this.unsecuredActionsClientAccessRegistry.has(requesterId)) { | ||
| throw new Error( | ||
| `${requesterId} feature is not registered for UnsecuredActionsClient access.` | ||
| ); | ||
| } | ||
| return this.executionEnqueuer(this.internalSavedObjectsRepository, actionsToExecute); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.