mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[8.x] [ResponseOps] Prepare the connector `execute` HTTP API for versioning (#194481) (#196555)
# Backport This will backport the following commits from `main` to `8.x`: - [[ResponseOps] Prepare the connector `execute` HTTP API for versioning (#194481)](https://github.com/elastic/kibana/pull/194481) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Alexi Doak","email":"109488926+doakalexi@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-10-16T14:25:41Z","message":"[ResponseOps] Prepare the connector `execute` HTTP API for versioning (#194481)\n\nTowards https://github.com/elastic/response-ops-team/issues/125\r\n\r\n## Summary\r\n\r\nPreparing the `POST ${BASE_ACTION_API_PATH}/connector/{id}/_execute`\r\nHTTP API for versioning\r\n\r\n---------\r\n\r\nCo-authored-by: Ying Mao <ying.mao@elastic.co>","sha":"241a05aa68cbaa737f5ccd92c1e6f91252a3cc9b","branchLabelMapping":{"^v9.0.0$":"main","^v8.16.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","Team:ResponseOps","v9.0.0","backport:prev-minor","v8.16.0"],"title":"[ResponseOps] Prepare the connector `execute` HTTP API for versioning","number":194481,"url":"https://github.com/elastic/kibana/pull/194481","mergeCommit":{"message":"[ResponseOps] Prepare the connector `execute` HTTP API for versioning (#194481)\n\nTowards https://github.com/elastic/response-ops-team/issues/125\r\n\r\n## Summary\r\n\r\nPreparing the `POST ${BASE_ACTION_API_PATH}/connector/{id}/_execute`\r\nHTTP API for versioning\r\n\r\n---------\r\n\r\nCo-authored-by: Ying Mao <ying.mao@elastic.co>","sha":"241a05aa68cbaa737f5ccd92c1e6f91252a3cc9b"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/194481","number":194481,"mergeCommit":{"message":"[ResponseOps] Prepare the connector `execute` HTTP API for versioning (#194481)\n\nTowards https://github.com/elastic/response-ops-team/issues/125\r\n\r\n## Summary\r\n\r\nPreparing the `POST ${BASE_ACTION_API_PATH}/connector/{id}/_execute`\r\nHTTP API for versioning\r\n\r\n---------\r\n\r\nCo-authored-by: Ying Mao <ying.mao@elastic.co>","sha":"241a05aa68cbaa737f5ccd92c1e6f91252a3cc9b"}},{"branch":"8.x","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Alexi Doak <109488926+doakalexi@users.noreply.github.com>
This commit is contained in:
parent
44eb0ca393
commit
7059fcab53
33 changed files with 425 additions and 163 deletions
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export {
|
||||
executeConnectorRequestParamsSchema,
|
||||
executeConnectorRequestBodySchema,
|
||||
} from './schemas/latest';
|
||||
export type { ExecuteConnectorRequestParams, ExecuteConnectorRequestBody } from './types/latest';
|
||||
|
||||
export {
|
||||
executeConnectorRequestParamsSchema as executeConnectorRequestParamsSchemaV1,
|
||||
executeConnectorRequestBodySchema as executeConnectorRequestBodySchemaV1,
|
||||
} from './schemas/v1';
|
||||
export type {
|
||||
ExecuteConnectorRequestParams as ExecuteConnectorRequestParamsV1,
|
||||
ExecuteConnectorRequestBody as ExecuteConnectorRequestBodyV1,
|
||||
} from './types/v1';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './v1';
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
|
||||
export const executeConnectorRequestParamsSchema = schema.object({
|
||||
id: schema.string({
|
||||
meta: {
|
||||
description: 'An identifier for the connector.',
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
export const executeConnectorRequestBodySchema = schema.object({
|
||||
params: schema.recordOf(schema.string(), schema.any()),
|
||||
});
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export * from './v1';
|
|
@ -0,0 +1,12 @@
|
|||
/*
|
||||
* 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 type { TypeOf } from '@kbn/config-schema';
|
||||
import { executeConnectorRequestParamsSchemaV1, executeConnectorRequestBodySchemaV1 } from '..';
|
||||
|
||||
export type ExecuteConnectorRequestParams = TypeOf<typeof executeConnectorRequestParamsSchemaV1>;
|
||||
export type ExecuteConnectorRequestBody = TypeOf<typeof executeConnectorRequestBodySchemaV1>;
|
|
@ -6,11 +6,16 @@
|
|||
*/
|
||||
|
||||
// Latest
|
||||
export type { ConnectorResponse, AllConnectorsResponse } from './types/latest';
|
||||
export type {
|
||||
ConnectorResponse,
|
||||
AllConnectorsResponse,
|
||||
ConnectorExecuteResponse,
|
||||
} from './types/latest';
|
||||
export {
|
||||
connectorResponseSchema,
|
||||
allConnectorsResponseSchema,
|
||||
connectorTypesResponseSchema,
|
||||
connectorExecuteResponseSchema,
|
||||
} from './schemas/latest';
|
||||
|
||||
// v1
|
||||
|
@ -18,9 +23,11 @@ export type {
|
|||
ConnectorResponse as ConnectorResponseV1,
|
||||
AllConnectorsResponse as AllConnectorsResponseV1,
|
||||
ConnectorTypesResponse as ConnectorTypesResponseV1,
|
||||
ConnectorExecuteResponse as ConnectorExecuteResponseV1,
|
||||
} from './types/v1';
|
||||
export {
|
||||
connectorResponseSchema as connectorResponseSchemaV1,
|
||||
allConnectorsResponseSchema as connectorWithExtraFindDataSchemaV1,
|
||||
connectorTypesResponseSchema as connectorTypesResponseSchemaV1,
|
||||
connectorExecuteResponseSchema as connectorExecuteResponseSchemaV1,
|
||||
} from './schemas/v1';
|
||||
|
|
|
@ -8,3 +8,4 @@
|
|||
export { connectorResponseSchema } from './v1';
|
||||
export { allConnectorsResponseSchema } from './v1';
|
||||
export { connectorTypesResponseSchema } from './v1';
|
||||
export { connectorExecuteResponseSchema } from './v1';
|
||||
|
|
|
@ -98,3 +98,55 @@ export const connectorTypesResponseSchema = schema.object({
|
|||
meta: { description: 'Indicates whether the action is a system action.' },
|
||||
}),
|
||||
});
|
||||
|
||||
export const connectorExecuteResponseSchema = schema.object({
|
||||
connector_id: schema.string({
|
||||
meta: {
|
||||
description: 'The identifier for the connector.',
|
||||
},
|
||||
}),
|
||||
status: schema.oneOf([schema.literal('ok'), schema.literal('error')], {
|
||||
meta: {
|
||||
description: 'The outcome of the connector execution.',
|
||||
},
|
||||
}),
|
||||
message: schema.maybe(
|
||||
schema.string({
|
||||
meta: {
|
||||
description: 'The connector execution error message.',
|
||||
},
|
||||
})
|
||||
),
|
||||
service_message: schema.maybe(
|
||||
schema.string({
|
||||
meta: {
|
||||
description: 'An error message that contains additional details.',
|
||||
},
|
||||
})
|
||||
),
|
||||
data: schema.maybe(
|
||||
schema.any({
|
||||
meta: {
|
||||
description: 'The connector execution data.',
|
||||
},
|
||||
})
|
||||
),
|
||||
retry: schema.maybe(
|
||||
schema.nullable(
|
||||
schema.oneOf([schema.boolean(), schema.string()], {
|
||||
meta: {
|
||||
description:
|
||||
'When the status is error, identifies whether the connector execution will retry .',
|
||||
},
|
||||
})
|
||||
)
|
||||
),
|
||||
errorSource: schema.maybe(
|
||||
schema.oneOf([schema.literal('user'), schema.literal('framework')], {
|
||||
meta: {
|
||||
description:
|
||||
'When the status is error, identifies whether the error is a framework error or a user error.',
|
||||
},
|
||||
})
|
||||
),
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
connectorResponseSchemaV1,
|
||||
connectorTypesResponseSchemaV1,
|
||||
allConnectorsResponseSchema,
|
||||
connectorExecuteResponseSchema,
|
||||
} from '..';
|
||||
|
||||
type ConnectorResponseSchemaType = TypeOf<typeof connectorResponseSchemaV1>;
|
||||
|
@ -41,3 +42,14 @@ export interface ConnectorTypesResponse {
|
|||
supported_feature_ids: ConnectorTypesResponseSchemaType['supported_feature_ids'];
|
||||
is_system_action_type: ConnectorTypesResponseSchemaType['is_system_action_type'];
|
||||
}
|
||||
|
||||
type ConnectorExecuteResponseSchemaType = TypeOf<typeof connectorExecuteResponseSchema>;
|
||||
export interface ConnectorExecuteResponse {
|
||||
connector_id: ConnectorExecuteResponseSchemaType['connector_id'];
|
||||
status: ConnectorExecuteResponseSchemaType['status'];
|
||||
message?: ConnectorExecuteResponseSchemaType['message'];
|
||||
service_message?: ConnectorExecuteResponseSchemaType['service_message'];
|
||||
data?: ConnectorExecuteResponseSchemaType['data'];
|
||||
retry?: ConnectorExecuteResponseSchemaType['retry'];
|
||||
errorSource?: ConnectorExecuteResponseSchemaType['errorSource'];
|
||||
}
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import Boom from '@hapi/boom';
|
||||
import url from 'url';
|
||||
import { UsageCounter } from '@kbn/usage-collection-plugin/server';
|
||||
|
@ -30,6 +29,7 @@ import { get } from '../application/connector/methods/get';
|
|||
import { getAll, getAllSystemConnectors } from '../application/connector/methods/get_all';
|
||||
import { update } from '../application/connector/methods/update';
|
||||
import { listTypes } from '../application/connector/methods/list_types';
|
||||
import { execute } from '../application/connector/methods/execute';
|
||||
import {
|
||||
GetGlobalExecutionKPIParams,
|
||||
GetGlobalExecutionLogParams,
|
||||
|
@ -54,7 +54,6 @@ import {
|
|||
HookServices,
|
||||
} from '../types';
|
||||
import { PreconfiguredActionDisabledModificationError } from '../lib/errors/preconfigured_action_disabled_modification';
|
||||
import { ExecuteOptions } from '../lib/action_executor';
|
||||
import {
|
||||
ExecutionEnqueuer,
|
||||
ExecuteOptions as EnqueueExecutionOptions,
|
||||
|
@ -96,6 +95,9 @@ import { connectorFromSavedObject, isConnectorDeprecated } from '../application/
|
|||
import { ListTypesParams } from '../application/connector/methods/list_types/types';
|
||||
import { ConnectorUpdateParams } from '../application/connector/methods/update/types';
|
||||
import { ConnectorUpdate } from '../application/connector/methods/update/types/types';
|
||||
import { isPreconfigured } from '../lib/is_preconfigured';
|
||||
import { isSystemAction } from '../lib/is_system_action';
|
||||
import { ConnectorExecuteParams } from '../application/connector/methods/execute/types';
|
||||
|
||||
interface Action extends ConnectorUpdate {
|
||||
actionTypeId: string;
|
||||
|
@ -649,75 +651,10 @@ export class ActionsClient {
|
|||
return result;
|
||||
}
|
||||
|
||||
private getSystemActionKibanaPrivileges(connectorId: string, params?: ExecuteOptions['params']) {
|
||||
const inMemoryConnector = this.context.inMemoryConnectors.find(
|
||||
(connector) => connector.id === connectorId
|
||||
);
|
||||
|
||||
const additionalPrivileges = inMemoryConnector?.isSystemAction
|
||||
? this.context.actionTypeRegistry.getSystemActionKibanaPrivileges(
|
||||
inMemoryConnector.actionTypeId,
|
||||
params
|
||||
)
|
||||
: [];
|
||||
|
||||
return additionalPrivileges;
|
||||
}
|
||||
|
||||
public async execute({
|
||||
actionId,
|
||||
params,
|
||||
source,
|
||||
relatedSavedObjects,
|
||||
}: Omit<ExecuteOptions, 'request' | 'actionExecutionId'>): Promise<
|
||||
ActionTypeExecutorResult<unknown>
|
||||
> {
|
||||
const log = this.context.logger;
|
||||
|
||||
if (
|
||||
(await getAuthorizationModeBySource(this.context.unsecuredSavedObjectsClient, source)) ===
|
||||
AuthorizationMode.RBAC
|
||||
) {
|
||||
const additionalPrivileges = this.getSystemActionKibanaPrivileges(actionId, params);
|
||||
let actionTypeId: string | undefined;
|
||||
|
||||
try {
|
||||
if (this.isPreconfigured(actionId) || this.isSystemAction(actionId)) {
|
||||
const connector = this.context.inMemoryConnectors.find(
|
||||
(inMemoryConnector) => inMemoryConnector.id === actionId
|
||||
);
|
||||
|
||||
actionTypeId = connector?.actionTypeId;
|
||||
} else {
|
||||
// TODO: Optimize so we don't do another get on top of getAuthorizationModeBySource and within the actionExecutor.execute
|
||||
const { attributes } = await this.context.unsecuredSavedObjectsClient.get<RawAction>(
|
||||
'action',
|
||||
actionId
|
||||
);
|
||||
|
||||
actionTypeId = attributes.actionTypeId;
|
||||
}
|
||||
} catch (err) {
|
||||
log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err);
|
||||
}
|
||||
|
||||
await this.context.authorization.ensureAuthorized({
|
||||
operation: 'execute',
|
||||
additionalPrivileges,
|
||||
actionTypeId,
|
||||
});
|
||||
} else {
|
||||
trackLegacyRBACExemption('execute', this.context.usageCounter);
|
||||
}
|
||||
|
||||
return this.context.actionExecutor.execute({
|
||||
actionId,
|
||||
params,
|
||||
source,
|
||||
request: this.context.request,
|
||||
relatedSavedObjects,
|
||||
actionExecutionId: uuidv4(),
|
||||
});
|
||||
public async execute(
|
||||
connectorExecuteParams: ConnectorExecuteParams
|
||||
): Promise<ActionTypeExecutorResult<unknown>> {
|
||||
return execute(this.context, connectorExecuteParams);
|
||||
}
|
||||
|
||||
public async bulkEnqueueExecution(
|
||||
|
@ -789,15 +726,11 @@ export class ActionsClient {
|
|||
}
|
||||
|
||||
public isPreconfigured(connectorId: string): boolean {
|
||||
return !!this.context.inMemoryConnectors.find(
|
||||
(connector) => connector.isPreconfigured && connector.id === connectorId
|
||||
);
|
||||
return isPreconfigured(this.context, connectorId);
|
||||
}
|
||||
|
||||
public isSystemAction(connectorId: string): boolean {
|
||||
return !!this.context.inMemoryConnectors.find(
|
||||
(connector) => connector.isSystemAction && connector.id === connectorId
|
||||
);
|
||||
return isSystemAction(this.context, connectorId);
|
||||
}
|
||||
|
||||
public async getGlobalExecutionLogWithAuth({
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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 { v4 as uuidv4 } from 'uuid';
|
||||
import { RawAction, ActionTypeExecutorResult } from '../../../../types';
|
||||
import { getSystemActionKibanaPrivileges } from '../../../../lib/get_system_action_kibana_privileges';
|
||||
import { isPreconfigured } from '../../../../lib/is_preconfigured';
|
||||
import { isSystemAction } from '../../../../lib/is_system_action';
|
||||
import {
|
||||
getAuthorizationModeBySource,
|
||||
AuthorizationMode,
|
||||
} from '../../../../authorization/get_authorization_mode_by_source';
|
||||
import { trackLegacyRBACExemption } from '../../../../lib/track_legacy_rbac_exemption';
|
||||
import { ConnectorExecuteParams } from './types';
|
||||
import { ACTION_SAVED_OBJECT_TYPE } from '../../../../constants/saved_objects';
|
||||
import { ActionsClientContext } from '../../../../actions_client';
|
||||
|
||||
export async function execute(
|
||||
context: ActionsClientContext,
|
||||
connectorExecuteParams: ConnectorExecuteParams
|
||||
): Promise<ActionTypeExecutorResult<unknown>> {
|
||||
const log = context.logger;
|
||||
const { actionId, params, source, relatedSavedObjects } = connectorExecuteParams;
|
||||
|
||||
if (
|
||||
(await getAuthorizationModeBySource(context.unsecuredSavedObjectsClient, source)) ===
|
||||
AuthorizationMode.RBAC
|
||||
) {
|
||||
const additionalPrivileges = getSystemActionKibanaPrivileges(context, actionId, params);
|
||||
let actionTypeId: string | undefined;
|
||||
|
||||
try {
|
||||
if (isPreconfigured(context, actionId) || isSystemAction(context, actionId)) {
|
||||
const connector = context.inMemoryConnectors.find(
|
||||
(inMemoryConnector) => inMemoryConnector.id === actionId
|
||||
);
|
||||
|
||||
actionTypeId = connector?.actionTypeId;
|
||||
} else {
|
||||
// TODO: Optimize so we don't do another get on top of getAuthorizationModeBySource and within the actionExecutor.execute
|
||||
const { attributes } = await context.unsecuredSavedObjectsClient.get<RawAction>(
|
||||
ACTION_SAVED_OBJECT_TYPE,
|
||||
actionId
|
||||
);
|
||||
|
||||
actionTypeId = attributes.actionTypeId;
|
||||
}
|
||||
} catch (err) {
|
||||
log.debug(`Failed to retrieve actionTypeId for action [${actionId}]`, err);
|
||||
}
|
||||
|
||||
await context.authorization.ensureAuthorized({
|
||||
operation: 'execute',
|
||||
additionalPrivileges,
|
||||
actionTypeId,
|
||||
});
|
||||
} else {
|
||||
trackLegacyRBACExemption('execute', context.usageCounter);
|
||||
}
|
||||
|
||||
return context.actionExecutor.execute({
|
||||
actionId,
|
||||
params,
|
||||
source,
|
||||
request: context.request,
|
||||
relatedSavedObjects,
|
||||
actionExecutionId: uuidv4(),
|
||||
});
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { execute } from './execute';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export type { ConnectorExecuteParams } from './types';
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 { ExecuteOptions } from '../../../../../lib/action_executor';
|
||||
|
||||
export type ConnectorExecuteParams = Omit<ExecuteOptions, 'request' | 'actionExecutionId'>;
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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 { ActionsClientContext } from '../actions_client';
|
||||
import { ExecuteOptions } from './action_executor';
|
||||
|
||||
export function getSystemActionKibanaPrivileges(
|
||||
context: ActionsClientContext,
|
||||
connectorId: string,
|
||||
params?: ExecuteOptions['params']
|
||||
) {
|
||||
const inMemoryConnector = context.inMemoryConnectors.find(
|
||||
(connector) => connector.id === connectorId
|
||||
);
|
||||
|
||||
const additionalPrivileges = inMemoryConnector?.isSystemAction
|
||||
? context.actionTypeRegistry.getSystemActionKibanaPrivileges(
|
||||
inMemoryConnector.actionTypeId,
|
||||
params
|
||||
)
|
||||
: [];
|
||||
|
||||
return additionalPrivileges;
|
||||
}
|
14
x-pack/plugins/actions/server/lib/is_preconfigured.ts
Normal file
14
x-pack/plugins/actions/server/lib/is_preconfigured.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { ActionsClientContext } from '../actions_client';
|
||||
|
||||
export function isPreconfigured(context: ActionsClientContext, connectorId: string): boolean {
|
||||
return !!context.inMemoryConnectors.find(
|
||||
(connector) => connector.isPreconfigured && connector.id === connectorId
|
||||
);
|
||||
}
|
14
x-pack/plugins/actions/server/lib/is_system_action.ts
Normal file
14
x-pack/plugins/actions/server/lib/is_system_action.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { ActionsClientContext } from '../actions_client';
|
||||
|
||||
export function isSystemAction(context: ActionsClientContext, connectorId: string): boolean {
|
||||
return !!context.inMemoryConnectors.find(
|
||||
(connector) => connector.isSystemAction && connector.id === connectorId
|
||||
);
|
||||
}
|
|
@ -5,16 +5,16 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { executeActionRoute } from './execute';
|
||||
import { executeConnectorRoute } from './execute';
|
||||
import { httpServiceMock } from '@kbn/core/server/mocks';
|
||||
import { licenseStateMock } from '../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from './legacy/_mock_handler_arguments';
|
||||
import { asHttpRequestExecutionSource } from '../lib';
|
||||
import { actionsClientMock } from '../actions_client/actions_client.mock';
|
||||
import { ActionTypeExecutorResult } from '../types';
|
||||
import { verifyAccessAndContext } from './verify_access_and_context';
|
||||
import { licenseStateMock } from '../../../lib/license_state.mock';
|
||||
import { mockHandlerArguments } from '../../legacy/_mock_handler_arguments';
|
||||
import { asHttpRequestExecutionSource } from '../../../lib';
|
||||
import { actionsClientMock } from '../../../actions_client/actions_client.mock';
|
||||
import { ActionTypeExecutorResult } from '../../../types';
|
||||
import { verifyAccessAndContext } from '../../verify_access_and_context';
|
||||
|
||||
jest.mock('./verify_access_and_context', () => ({
|
||||
jest.mock('../../verify_access_and_context', () => ({
|
||||
verifyAccessAndContext: jest.fn(),
|
||||
}));
|
||||
|
||||
|
@ -23,7 +23,7 @@ beforeEach(() => {
|
|||
(verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler);
|
||||
});
|
||||
|
||||
describe('executeActionRoute', () => {
|
||||
describe('executeConnectorRoute', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
@ -55,7 +55,7 @@ describe('executeActionRoute', () => {
|
|||
status: 'ok',
|
||||
};
|
||||
|
||||
executeActionRoute(router, licenseState);
|
||||
executeConnectorRoute(router, licenseState);
|
||||
|
||||
const [config, handler] = router.post.mock.calls[0];
|
||||
|
||||
|
@ -95,7 +95,7 @@ describe('executeActionRoute', () => {
|
|||
['noContent']
|
||||
);
|
||||
|
||||
executeActionRoute(router, licenseState);
|
||||
executeConnectorRoute(router, licenseState);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
|
@ -131,7 +131,7 @@ describe('executeActionRoute', () => {
|
|||
['ok']
|
||||
);
|
||||
|
||||
executeActionRoute(router, licenseState);
|
||||
executeConnectorRoute(router, licenseState);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
|
@ -163,7 +163,7 @@ describe('executeActionRoute', () => {
|
|||
['ok']
|
||||
);
|
||||
|
||||
executeActionRoute(router, licenseState);
|
||||
executeConnectorRoute(router, licenseState);
|
||||
|
||||
const [, handler] = router.post.mock.calls[0];
|
||||
|
||||
|
@ -192,7 +192,7 @@ describe('executeActionRoute', () => {
|
|||
['ok']
|
||||
);
|
||||
|
||||
executeActionRoute(router, licenseState);
|
||||
executeConnectorRoute(router, licenseState);
|
||||
|
||||
const [_, handler] = router.post.mock.calls[0];
|
||||
|
|
@ -5,37 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { IRouter } from '@kbn/core/server';
|
||||
import { ILicenseState } from '../lib';
|
||||
import { ILicenseState } from '../../../lib';
|
||||
|
||||
import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../types';
|
||||
import { BASE_ACTION_API_PATH, RewriteResponseCase } from '../../common';
|
||||
import { asHttpRequestExecutionSource } from '../lib/action_execution_source';
|
||||
import { verifyAccessAndContext } from './verify_access_and_context';
|
||||
import { connectorResponseSchemaV1 } from '../../common/routes/connector/response';
|
||||
import { ActionTypeExecutorResult, ActionsRequestHandlerContext } from '../../../types';
|
||||
import { BASE_ACTION_API_PATH } from '../../../../common';
|
||||
import { asHttpRequestExecutionSource } from '../../../lib/action_execution_source';
|
||||
import { verifyAccessAndContext } from '../../verify_access_and_context';
|
||||
import { connectorResponseSchemaV1 } from '../../../../common/routes/connector/response';
|
||||
import {
|
||||
executeConnectorRequestBodySchemaV1,
|
||||
ExecuteConnectorRequestBodyV1,
|
||||
executeConnectorRequestParamsSchemaV1,
|
||||
ExecuteConnectorRequestParamsV1,
|
||||
} from '../../../../common/routes/connector/apis/execute';
|
||||
import { transformExecuteConnectorResponseV1 } from './transforms';
|
||||
|
||||
const paramSchema = schema.object({
|
||||
id: schema.string({
|
||||
meta: { description: 'An identifier for the connector.' },
|
||||
}),
|
||||
});
|
||||
|
||||
const bodySchema = schema.object({
|
||||
params: schema.recordOf(schema.string(), schema.any()),
|
||||
});
|
||||
|
||||
const rewriteBodyRes: RewriteResponseCase<ActionTypeExecutorResult<unknown>> = ({
|
||||
actionId,
|
||||
serviceMessage,
|
||||
...res
|
||||
}) => ({
|
||||
...res,
|
||||
connector_id: actionId,
|
||||
...(serviceMessage ? { service_message: serviceMessage } : {}),
|
||||
});
|
||||
|
||||
export const executeActionRoute = (
|
||||
export const executeConnectorRoute = (
|
||||
router: IRouter<ActionsRequestHandlerContext>,
|
||||
licenseState: ILicenseState
|
||||
) => {
|
||||
|
@ -51,8 +37,8 @@ export const executeActionRoute = (
|
|||
},
|
||||
validate: {
|
||||
request: {
|
||||
body: bodySchema,
|
||||
params: paramSchema,
|
||||
body: executeConnectorRequestBodySchemaV1,
|
||||
params: executeConnectorRequestParamsSchemaV1,
|
||||
},
|
||||
response: {
|
||||
200: {
|
||||
|
@ -65,8 +51,8 @@ export const executeActionRoute = (
|
|||
router.handleLegacyErrors(
|
||||
verifyAccessAndContext(licenseState, async function (context, req, res) {
|
||||
const actionsClient = (await context.actions).getActionsClient();
|
||||
const { params } = req.body;
|
||||
const { id } = req.params;
|
||||
const { params }: ExecuteConnectorRequestBodyV1 = req.body;
|
||||
const { id }: ExecuteConnectorRequestParamsV1 = req.params;
|
||||
|
||||
if (actionsClient.isSystemAction(id)) {
|
||||
return res.badRequest({ body: 'Execution of system action is not allowed' });
|
||||
|
@ -81,7 +67,7 @@ export const executeActionRoute = (
|
|||
|
||||
return body
|
||||
? res.ok({
|
||||
body: rewriteBodyRes(body),
|
||||
body: transformExecuteConnectorResponseV1(body),
|
||||
})
|
||||
: res.noContent();
|
||||
})
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { executeConnectorRoute } from './execute';
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { transformExecuteConnectorResponse } from './transform_connector_response/latest';
|
||||
|
||||
export { transformExecuteConnectorResponse as transformExecuteConnectorResponseV1 } from './transform_connector_response/v1';
|
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export { transformExecuteConnectorResponse } from './v1';
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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 { ConnectorExecuteResponseV1 } from '../../../../../../common/routes/connector/response';
|
||||
import { ActionTypeExecutorResult } from '../../../../../types';
|
||||
|
||||
export const transformExecuteConnectorResponse = ({
|
||||
actionId,
|
||||
retry,
|
||||
serviceMessage,
|
||||
...res
|
||||
}: ActionTypeExecutorResult<unknown>): ConnectorExecuteResponseV1 => ({
|
||||
...res,
|
||||
connector_id: actionId,
|
||||
...(retry && retry instanceof Date ? { retry: retry.toISOString() } : { retry }),
|
||||
...(serviceMessage ? { service_message: serviceMessage } : {}),
|
||||
});
|
|
@ -15,7 +15,7 @@ import { ILicenseState } from '../lib';
|
|||
import { ActionsRequestHandlerContext } from '../types';
|
||||
import { createActionRoute } from './create';
|
||||
import { deleteConnectorRoute } from './connector/delete';
|
||||
import { executeActionRoute } from './execute';
|
||||
import { executeConnectorRoute } from './connector/execute';
|
||||
import { getConnectorRoute } from './connector/get';
|
||||
import { updateConnectorRoute } from './connector/update';
|
||||
import { getOAuthAccessToken } from './get_oauth_access_token';
|
||||
|
@ -42,7 +42,7 @@ export function defineRoutes(opts: RouteOptions) {
|
|||
getAllConnectorsRoute(router, licenseState);
|
||||
updateConnectorRoute(router, licenseState);
|
||||
listTypesRoute(router, licenseState);
|
||||
executeActionRoute(router, licenseState);
|
||||
executeConnectorRoute(router, licenseState);
|
||||
getGlobalExecutionLogRoute(router, licenseState);
|
||||
getGlobalExecutionKPIRoute(router, licenseState);
|
||||
|
||||
|
|
|
@ -246,12 +246,12 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
})
|
||||
.then((resp: any) => {
|
||||
expect(Object.keys(resp.body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(resp.body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(resp.body.connector_id).to.eql(simulatedActionId);
|
||||
expect(resp.body.status).to.eql('error');
|
||||
|
|
|
@ -236,12 +236,12 @@ export default function jiraTest({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
})
|
||||
.then((resp: any) => {
|
||||
expect(Object.keys(resp.body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(resp.body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(resp.body.connector_id).to.eql(simulatedActionId);
|
||||
expect(resp.body.status).to.eql('error');
|
||||
|
|
|
@ -169,12 +169,12 @@ export default function opsgenieTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
expect(200);
|
||||
|
||||
expect(Object.keys(body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(body.connector_id).to.eql(opsgenieActionId);
|
||||
expect(body.status).to.eql('error');
|
||||
|
|
|
@ -230,12 +230,12 @@ export default function resilientTest({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
})
|
||||
.then((resp: any) => {
|
||||
expect(Object.keys(resp.body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(resp.body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(resp.body.connector_id).to.eql(resilientActionId);
|
||||
expect(resp.body.status).to.eql('error');
|
||||
|
|
|
@ -416,12 +416,12 @@ export default function serviceNowITOMTest({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
})
|
||||
.then((resp: any) => {
|
||||
expect(Object.keys(resp.body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(resp.body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(resp.body.connector_id).to.eql(simulatedActionId);
|
||||
expect(resp.body.status).to.eql('error');
|
||||
|
|
|
@ -452,12 +452,12 @@ export default function serviceNowITSMTest({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
})
|
||||
.then((resp: any) => {
|
||||
expect(Object.keys(resp.body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(resp.body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(resp.body.connector_id).to.eql(simulatedActionId);
|
||||
expect(resp.body.status).to.eql('error');
|
||||
|
|
|
@ -465,12 +465,12 @@ export default function serviceNowSIRTest({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
})
|
||||
.then((resp: any) => {
|
||||
expect(Object.keys(resp.body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(resp.body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(resp.body.connector_id).to.eql(simulatedActionId);
|
||||
expect(resp.body.status).to.eql('error');
|
||||
|
|
|
@ -327,12 +327,12 @@ export default function swimlaneTest({ getService }: FtrProviderContext) {
|
|||
params: {},
|
||||
})
|
||||
.then((resp: any) => {
|
||||
expect(Object.keys(resp.body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(resp.body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(resp.body.connector_id).to.eql(simulatedActionId);
|
||||
expect(resp.body.status).to.eql('error');
|
||||
|
|
|
@ -188,12 +188,12 @@ export default function tinesTest({ getService }: FtrProviderContext) {
|
|||
});
|
||||
expect(200);
|
||||
|
||||
expect(Object.keys(body)).to.eql([
|
||||
'status',
|
||||
expect(Object.keys(body).sort()).to.eql([
|
||||
'connector_id',
|
||||
'errorSource',
|
||||
'message',
|
||||
'retry',
|
||||
'errorSource',
|
||||
'connector_id',
|
||||
'status',
|
||||
]);
|
||||
expect(body.connector_id).to.eql(tinesActionId);
|
||||
expect(body.status).to.eql('error');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue