diff --git a/x-pack/plugins/actions/common/index.ts b/x-pack/plugins/actions/common/index.ts index b56f6c61238c2..336ff003962a0 100644 --- a/x-pack/plugins/actions/common/index.ts +++ b/x-pack/plugins/actions/common/index.ts @@ -15,6 +15,7 @@ export * from './mustache_template'; export * from './validate_email_addresses'; export * from './connector_feature_config'; export * from './execution_log_types'; +export * from './validate_empty_strings'; export const BASE_ACTION_API_PATH = '/api/actions'; export const INTERNAL_BASE_ACTION_API_PATH = '/internal/actions'; diff --git a/x-pack/plugins/actions/common/routes/connector/apis/create/index.ts b/x-pack/plugins/actions/common/routes/connector/apis/create/index.ts new file mode 100644 index 0000000000000..a40970eadc099 --- /dev/null +++ b/x-pack/plugins/actions/common/routes/connector/apis/create/index.ts @@ -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 { + createConnectorRequestParamsSchema, + createConnectorRequestBodySchema, +} from './schemas/latest'; +export type { CreateConnectorRequestParams, CreateConnectorRequestBody } from './types/latest'; + +export { + createConnectorRequestParamsSchema as createConnectorRequestParamsSchemaV1, + createConnectorRequestBodySchema as createConnectorRequestBodySchemaV1, +} from './schemas/v1'; +export type { + CreateConnectorRequestParams as CreateConnectorRequestParamsV1, + CreateConnectorRequestBody as CreateConnectorRequestBodyV1, +} from './types/v1'; diff --git a/x-pack/plugins/actions/server/routes/connector/get/transforms/transform_connector_response/latest.ts b/x-pack/plugins/actions/common/routes/connector/apis/create/schemas/latest.ts similarity index 82% rename from x-pack/plugins/actions/server/routes/connector/get/transforms/transform_connector_response/latest.ts rename to x-pack/plugins/actions/common/routes/connector/apis/create/schemas/latest.ts index 8a64b2c81df0c..25300c97a6d2e 100644 --- a/x-pack/plugins/actions/server/routes/connector/get/transforms/transform_connector_response/latest.ts +++ b/x-pack/plugins/actions/common/routes/connector/apis/create/schemas/latest.ts @@ -5,4 +5,4 @@ * 2.0. */ -export { transformGetConnectorResponse } from './v1'; +export * from './v1'; diff --git a/x-pack/plugins/actions/common/routes/connector/apis/create/schemas/v1.ts b/x-pack/plugins/actions/common/routes/connector/apis/create/schemas/v1.ts new file mode 100644 index 0000000000000..ec74ba1a9279f --- /dev/null +++ b/x-pack/plugins/actions/common/routes/connector/apis/create/schemas/v1.ts @@ -0,0 +1,32 @@ +/* + * 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'; +import { validateEmptyStrings } from '../../../../../validate_empty_strings'; + +export const createConnectorRequestParamsSchema = schema.maybe( + schema.object({ + id: schema.maybe(schema.string({ meta: { description: 'An identifier for the connector.' } })), + }) +); + +export const createConnectorRequestBodySchema = schema.object({ + name: schema.string({ + validate: validateEmptyStrings, + meta: { description: 'The display name for the connector.' }, + }), + connector_type_id: schema.string({ + validate: validateEmptyStrings, + meta: { description: 'The type of connector.' }, + }), + config: schema.recordOf(schema.string(), schema.any({ validate: validateEmptyStrings }), { + defaultValue: {}, + }), + secrets: schema.recordOf(schema.string(), schema.any({ validate: validateEmptyStrings }), { + defaultValue: {}, + }), +}); diff --git a/x-pack/plugins/actions/common/routes/connector/apis/create/types/latest.ts b/x-pack/plugins/actions/common/routes/connector/apis/create/types/latest.ts new file mode 100644 index 0000000000000..25300c97a6d2e --- /dev/null +++ b/x-pack/plugins/actions/common/routes/connector/apis/create/types/latest.ts @@ -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'; diff --git a/x-pack/plugins/actions/common/routes/connector/apis/create/types/v1.ts b/x-pack/plugins/actions/common/routes/connector/apis/create/types/v1.ts new file mode 100644 index 0000000000000..2166ee5a712a6 --- /dev/null +++ b/x-pack/plugins/actions/common/routes/connector/apis/create/types/v1.ts @@ -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 { createConnectorRequestParamsSchemaV1, createConnectorRequestBodySchemaV1 } from '..'; + +export type CreateConnectorRequestParams = TypeOf; +export type CreateConnectorRequestBody = TypeOf; diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.ts b/x-pack/plugins/actions/server/actions_client/actions_client.ts index edad072acbca6..de029ed2acf54 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.ts @@ -14,9 +14,7 @@ import { compact, uniq } from 'lodash'; import { IScopedClusterClient, SavedObjectsClientContract, - SavedObjectAttributes, KibanaRequest, - SavedObjectsUtils, Logger, } from '@kbn/core/server'; import { AuditLogger } from '@kbn/security-plugin/server'; @@ -29,6 +27,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 { create } from '../application/connector/methods/create'; import { execute } from '../application/connector/methods/execute'; import { GetGlobalExecutionKPIParams, @@ -36,15 +35,7 @@ import { IExecutionLogResult, } from '../../common'; import { ActionTypeRegistry } from '../action_type_registry'; -import { - validateConfig, - validateSecrets, - ActionExecutorContract, - validateConnector, - ActionExecutionSource, - parseDate, - tryCatch, -} from '../lib'; +import { ActionExecutorContract, ActionExecutionSource, parseDate } from '../lib'; import { ActionResult, RawAction, @@ -94,20 +85,11 @@ import { import { connectorFromSavedObject, isConnectorDeprecated } from '../application/connector/lib'; 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 { ConnectorCreateParams } from '../application/connector/methods/create/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; -} - -export interface CreateOptions { - action: Action; - options?: { id?: string }; -} - export interface ConstructorOptions { logger: Logger; kibanaIndices: string[]; @@ -187,156 +169,10 @@ export class ActionsClient { * Create an action */ public async create({ - action: { actionTypeId, name, config, secrets }, + action, options, - }: CreateOptions): Promise { - const id = options?.id || SavedObjectsUtils.generateId(); - - try { - await this.context.authorization.ensureAuthorized({ - operation: 'create', - actionTypeId, - }); - } catch (error) { - this.context.auditLogger?.log( - connectorAuditEvent({ - action: ConnectorAuditAction.CREATE, - savedObject: { type: 'action', id }, - error, - }) - ); - throw error; - } - - const foundInMemoryConnector = this.context.inMemoryConnectors.find( - (connector) => connector.id === id - ); - - if ( - this.context.actionTypeRegistry.isSystemActionType(actionTypeId) || - foundInMemoryConnector?.isSystemAction - ) { - throw Boom.badRequest( - i18n.translate('xpack.actions.serverSideErrors.systemActionCreationForbidden', { - defaultMessage: 'System action creation is forbidden. Action type: {actionTypeId}.', - values: { - actionTypeId, - }, - }) - ); - } - - if (foundInMemoryConnector?.isPreconfigured) { - throw Boom.badRequest( - i18n.translate('xpack.actions.serverSideErrors.predefinedIdConnectorAlreadyExists', { - defaultMessage: 'This {id} already exists in a preconfigured action.', - values: { - id, - }, - }) - ); - } - - const actionType = this.context.actionTypeRegistry.get(actionTypeId); - const configurationUtilities = this.context.actionTypeRegistry.getUtils(); - const validatedActionTypeConfig = validateConfig(actionType, config, { - configurationUtilities, - }); - const validatedActionTypeSecrets = validateSecrets(actionType, secrets, { - configurationUtilities, - }); - if (actionType.validate?.connector) { - validateConnector(actionType, { config, secrets }); - } - this.context.actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); - - const hookServices: HookServices = { - scopedClusterClient: this.context.scopedClusterClient, - }; - - if (actionType.preSaveHook) { - try { - await actionType.preSaveHook({ - connectorId: id, - config, - secrets, - logger: this.context.logger, - request: this.context.request, - services: hookServices, - isUpdate: false, - }); - } catch (error) { - this.context.auditLogger?.log( - connectorAuditEvent({ - action: ConnectorAuditAction.CREATE, - savedObject: { type: 'action', id }, - error, - }) - ); - throw error; - } - } - - this.context.auditLogger?.log( - connectorAuditEvent({ - action: ConnectorAuditAction.CREATE, - savedObject: { type: 'action', id }, - outcome: 'unknown', - }) - ); - - const result = await tryCatch( - async () => - await this.context.unsecuredSavedObjectsClient.create( - 'action', - { - actionTypeId, - name, - isMissingSecrets: false, - config: validatedActionTypeConfig as SavedObjectAttributes, - secrets: validatedActionTypeSecrets as SavedObjectAttributes, - }, - { id } - ) - ); - - const wasSuccessful = !(result instanceof Error); - const label = `connectorId: "${id}"; type: ${actionTypeId}`; - const tags = ['post-save-hook', id]; - - if (actionType.postSaveHook) { - try { - await actionType.postSaveHook({ - connectorId: id, - config, - secrets, - logger: this.context.logger, - request: this.context.request, - services: hookServices, - isUpdate: false, - wasSuccessful, - }); - } catch (err) { - this.context.logger.error(`postSaveHook create error for ${label}: ${err.message}`, { - tags, - }); - } - } - - if (!wasSuccessful) { - throw result; - } - - return { - id: result.id, - actionTypeId: result.attributes.actionTypeId, - isMissingSecrets: result.attributes.isMissingSecrets, - name: result.attributes.name, - config: result.attributes.config, - isPreconfigured: false, - isSystemAction: false, - isDeprecated: isConnectorDeprecated(result.attributes), - }; + }: Omit): Promise { + return create({ context: this.context, action, options }); } /** diff --git a/x-pack/plugins/actions/server/application/connector/methods/create/create.ts b/x-pack/plugins/actions/server/application/connector/methods/create/create.ts new file mode 100644 index 0000000000000..3032b38cb36f2 --- /dev/null +++ b/x-pack/plugins/actions/server/application/connector/methods/create/create.ts @@ -0,0 +1,170 @@ +/* + * 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 Boom from '@hapi/boom'; +import { i18n } from '@kbn/i18n'; +import { SavedObjectAttributes, SavedObjectsUtils } from '@kbn/core/server'; +import { ConnectorCreateParams } from './types'; +import { ConnectorAuditAction, connectorAuditEvent } from '../../../../lib/audit_events'; +import { validateConfig, validateConnector, validateSecrets } from '../../../../lib'; +import { isConnectorDeprecated } from '../../lib'; +import { HookServices, ActionResult } from '../../../../types'; +import { tryCatch } from '../../../../lib'; + +export async function create({ + context, + action: { actionTypeId, name, config, secrets }, + options, +}: ConnectorCreateParams): Promise { + const id = options?.id || SavedObjectsUtils.generateId(); + + try { + await context.authorization.ensureAuthorized({ + operation: 'create', + actionTypeId, + }); + } catch (error) { + context.auditLogger?.log( + connectorAuditEvent({ + action: ConnectorAuditAction.CREATE, + savedObject: { type: 'action', id }, + error, + }) + ); + throw error; + } + + const foundInMemoryConnector = context.inMemoryConnectors.find( + (connector) => connector.id === id + ); + + if ( + context.actionTypeRegistry.isSystemActionType(actionTypeId) || + foundInMemoryConnector?.isSystemAction + ) { + throw Boom.badRequest( + i18n.translate('xpack.actions.serverSideErrors.systemActionCreationForbidden', { + defaultMessage: 'System action creation is forbidden. Action type: {actionTypeId}.', + values: { + actionTypeId, + }, + }) + ); + } + + if (foundInMemoryConnector?.isPreconfigured) { + throw Boom.badRequest( + i18n.translate('xpack.actions.serverSideErrors.predefinedIdConnectorAlreadyExists', { + defaultMessage: 'This {id} already exists in a preconfigured action.', + values: { + id, + }, + }) + ); + } + + const actionType = context.actionTypeRegistry.get(actionTypeId); + const configurationUtilities = context.actionTypeRegistry.getUtils(); + const validatedActionTypeConfig = validateConfig(actionType, config, { + configurationUtilities, + }); + const validatedActionTypeSecrets = validateSecrets(actionType, secrets, { + configurationUtilities, + }); + if (actionType.validate?.connector) { + validateConnector(actionType, { config, secrets }); + } + context.actionTypeRegistry.ensureActionTypeEnabled(actionTypeId); + + const hookServices: HookServices = { + scopedClusterClient: context.scopedClusterClient, + }; + + if (actionType.preSaveHook) { + try { + await actionType.preSaveHook({ + connectorId: id, + config, + secrets, + logger: context.logger, + request: context.request, + services: hookServices, + isUpdate: false, + }); + } catch (error) { + context.auditLogger?.log( + connectorAuditEvent({ + action: ConnectorAuditAction.CREATE, + savedObject: { type: 'action', id }, + error, + }) + ); + throw error; + } + } + + context.auditLogger?.log( + connectorAuditEvent({ + action: ConnectorAuditAction.CREATE, + savedObject: { type: 'action', id }, + outcome: 'unknown', + }) + ); + + const result = await tryCatch( + async () => + await context.unsecuredSavedObjectsClient.create( + 'action', + { + actionTypeId, + name, + isMissingSecrets: false, + config: validatedActionTypeConfig as SavedObjectAttributes, + secrets: validatedActionTypeSecrets as SavedObjectAttributes, + }, + { id } + ) + ); + + const wasSuccessful = !(result instanceof Error); + const label = `connectorId: "${id}"; type: ${actionTypeId}`; + const tags = ['post-save-hook', id]; + + if (actionType.postSaveHook) { + try { + await actionType.postSaveHook({ + connectorId: id, + config, + secrets, + logger: context.logger, + request: context.request, + services: hookServices, + isUpdate: false, + wasSuccessful, + }); + } catch (err) { + context.logger.error(`postSaveHook create error for ${label}: ${err.message}`, { + tags, + }); + } + } + + if (!wasSuccessful) { + throw result; + } + + return { + id: result.id, + actionTypeId: result.attributes.actionTypeId, + isMissingSecrets: result.attributes.isMissingSecrets, + name: result.attributes.name, + config: result.attributes.config, + isPreconfigured: false, + isSystemAction: false, + isDeprecated: isConnectorDeprecated(result.attributes), + }; +} diff --git a/x-pack/plugins/actions/server/application/connector/methods/create/index.ts b/x-pack/plugins/actions/server/application/connector/methods/create/index.ts new file mode 100644 index 0000000000000..97fcd17a79499 --- /dev/null +++ b/x-pack/plugins/actions/server/application/connector/methods/create/index.ts @@ -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 { create } from './create'; diff --git a/x-pack/plugins/actions/server/application/connector/methods/create/types/index.ts b/x-pack/plugins/actions/server/application/connector/methods/create/types/index.ts new file mode 100644 index 0000000000000..707a3fd770f2d --- /dev/null +++ b/x-pack/plugins/actions/server/application/connector/methods/create/types/index.ts @@ -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 { ConnectorCreateParams } from './types'; diff --git a/x-pack/plugins/actions/server/application/connector/methods/create/types/types.ts b/x-pack/plugins/actions/server/application/connector/methods/create/types/types.ts new file mode 100644 index 0000000000000..2dc68d59ebe47 --- /dev/null +++ b/x-pack/plugins/actions/server/application/connector/methods/create/types/types.ts @@ -0,0 +1,22 @@ +/* + * 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 { SavedObjectAttributes } from '@kbn/core/server'; +import { ActionsClientContext } from '../../../../../actions_client'; + +export interface ConnectorCreate { + actionTypeId: string; + name: string; + config: SavedObjectAttributes; + secrets: SavedObjectAttributes; +} + +export interface ConnectorCreateParams { + context: ActionsClientContext; + action: ConnectorCreate; + options?: { id?: string }; +} diff --git a/x-pack/plugins/actions/server/routes/connector/get/transforms/index.ts b/x-pack/plugins/actions/server/routes/connector/common_transforms/index.ts similarity index 55% rename from x-pack/plugins/actions/server/routes/connector/get/transforms/index.ts rename to x-pack/plugins/actions/server/routes/connector/common_transforms/index.ts index 44960ddfeec0d..0e26b4ee295a6 100644 --- a/x-pack/plugins/actions/server/routes/connector/get/transforms/index.ts +++ b/x-pack/plugins/actions/server/routes/connector/common_transforms/index.ts @@ -5,6 +5,6 @@ * 2.0. */ -export { transformGetConnectorResponse } from './transform_connector_response/latest'; +export { transformConnectorResponse } from './transform_connector_response/latest'; -export { transformGetConnectorResponse as transformGetConnectorResponseV1 } from './transform_connector_response/v1'; +export { transformConnectorResponse as transformConnectorResponseV1 } from './transform_connector_response/v1'; diff --git a/x-pack/plugins/actions/server/routes/connector/common_transforms/transform_connector_response/latest.ts b/x-pack/plugins/actions/server/routes/connector/common_transforms/transform_connector_response/latest.ts new file mode 100644 index 0000000000000..99b38410e71a8 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector/common_transforms/transform_connector_response/latest.ts @@ -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 { transformConnectorResponse } from './v1'; diff --git a/x-pack/plugins/actions/server/routes/connector/get/transforms/transform_connector_response/v1.ts b/x-pack/plugins/actions/server/routes/connector/common_transforms/transform_connector_response/v1.ts similarity index 73% rename from x-pack/plugins/actions/server/routes/connector/get/transforms/transform_connector_response/v1.ts rename to x-pack/plugins/actions/server/routes/connector/common_transforms/transform_connector_response/v1.ts index ab6f6332280d1..08b32a10e7e45 100644 --- a/x-pack/plugins/actions/server/routes/connector/get/transforms/transform_connector_response/v1.ts +++ b/x-pack/plugins/actions/server/routes/connector/common_transforms/transform_connector_response/v1.ts @@ -5,10 +5,10 @@ * 2.0. */ -import { ConnectorResponseV1 } from '../../../../../../common/routes/connector/response'; -import { Connector } from '../../../../../application/connector/types'; +import { ConnectorResponseV1 } from '../../../../../common/routes/connector/response'; +import { Connector } from '../../../../application/connector/types'; -export const transformGetConnectorResponse = ({ +export const transformConnectorResponse = ({ actionTypeId, isPreconfigured, isMissingSecrets, diff --git a/x-pack/plugins/actions/server/routes/create.test.ts b/x-pack/plugins/actions/server/routes/connector/create/create.test.ts similarity index 84% rename from x-pack/plugins/actions/server/routes/create.test.ts rename to x-pack/plugins/actions/server/routes/connector/create/create.test.ts index 7d9c7fd1a5899..0f97015bd4f01 100644 --- a/x-pack/plugins/actions/server/routes/create.test.ts +++ b/x-pack/plugins/actions/server/routes/connector/create/create.test.ts @@ -5,15 +5,16 @@ * 2.0. */ -import { createActionRoute, bodySchema } from './create'; +import { createConnectorRoute } from './create'; import { httpServiceMock } from '@kbn/core/server/mocks'; -import { licenseStateMock } from '../lib/license_state.mock'; -import { mockHandlerArguments } from './legacy/_mock_handler_arguments'; -import { verifyAccessAndContext } from './verify_access_and_context'; +import { licenseStateMock } from '../../../lib/license_state.mock'; +import { mockHandlerArguments } from '../../legacy/_mock_handler_arguments'; +import { verifyAccessAndContext } from '../../verify_access_and_context'; import { omit } from 'lodash'; -import { actionsClientMock } from '../actions_client/actions_client.mock'; +import { actionsClientMock } from '../../../actions_client/actions_client.mock'; +import { createConnectorRequestBodySchemaV1 } from '../../../../common/routes/connector/apis/create'; -jest.mock('./verify_access_and_context', () => ({ +jest.mock('../../verify_access_and_context', () => ({ verifyAccessAndContext: jest.fn(), })); @@ -22,12 +23,12 @@ beforeEach(() => { (verifyAccessAndContext as jest.Mock).mockImplementation((license, handler) => handler); }); -describe('createActionRoute', () => { +describe('createConnectorRoute', () => { it('creates an action with proper parameters', async () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - createActionRoute(router, licenseState); + createConnectorRoute(router, licenseState); const [config, handler] = router.post.mock.calls[0]; @@ -103,7 +104,7 @@ describe('createActionRoute', () => { const licenseState = licenseStateMock.create(); const router = httpServiceMock.createRouter(); - createActionRoute(router, licenseState); + createConnectorRoute(router, licenseState); const [, handler] = router.post.mock.calls[0]; @@ -144,7 +145,7 @@ describe('createActionRoute', () => { throw new Error('OMG'); }); - createActionRoute(router, licenseState); + createConnectorRoute(router, licenseState); const [, handler] = router.post.mock.calls[0]; @@ -182,8 +183,8 @@ describe('createActionRoute', () => { config: { foo: ' ' }, secrets: {}, }; - expect(() => bodySchema.validate(body)).toThrowErrorMatchingInlineSnapshot( - `"[config.foo]: value '' is not valid"` - ); + expect(() => + createConnectorRequestBodySchemaV1.validate(body) + ).toThrowErrorMatchingInlineSnapshot(`"[config.foo]: value '' is not valid"`); }); }); diff --git a/x-pack/plugins/actions/server/routes/connector/create/create.ts b/x-pack/plugins/actions/server/routes/connector/create/create.ts new file mode 100644 index 0000000000000..cd5073506c03f --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector/create/create.ts @@ -0,0 +1,58 @@ +/* + * 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 { IRouter } from '@kbn/core/server'; +import { ActionsRequestHandlerContext } from '../../../types'; +import { ILicenseState } from '../../../lib'; +import { BASE_ACTION_API_PATH } from '../../../../common'; +import { verifyAccessAndContext } from '../../verify_access_and_context'; +import { connectorResponseSchemaV1 } from '../../../../common/routes/connector/response'; +import { transformConnectorResponseV1 } from '../common_transforms'; +import { + createConnectorRequestParamsSchemaV1, + createConnectorRequestBodySchemaV1, +} from '../../../../common/routes/connector/apis/create'; +import { transformCreateConnectorBodyV1 } from './transforms'; + +export const createConnectorRoute = ( + router: IRouter, + licenseState: ILicenseState +) => { + router.post( + { + path: `${BASE_ACTION_API_PATH}/connector/{id?}`, + options: { + access: 'public', + summary: 'Create a connector', + tags: ['oas-tag:connectors'], + }, + validate: { + request: { + params: createConnectorRequestParamsSchemaV1, + body: createConnectorRequestBodySchemaV1, + }, + response: { + 200: { + description: 'Indicates a successful call.', + body: () => connectorResponseSchemaV1, + }, + }, + }, + }, + router.handleLegacyErrors( + verifyAccessAndContext(licenseState, async function (context, req, res) { + const actionsClient = (await context.actions).getActionsClient(); + const action = transformCreateConnectorBodyV1(req.body); + return res.ok({ + body: transformConnectorResponseV1( + await actionsClient.create({ action, options: req.params }) + ), + }); + }) + ) + ); +}; diff --git a/x-pack/plugins/actions/server/routes/connector/create/index.ts b/x-pack/plugins/actions/server/routes/connector/create/index.ts new file mode 100644 index 0000000000000..47f4f6a55eb71 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector/create/index.ts @@ -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 { createConnectorRoute } from './create'; diff --git a/x-pack/plugins/actions/server/routes/connector/create/transforms/index.ts b/x-pack/plugins/actions/server/routes/connector/create/transforms/index.ts new file mode 100644 index 0000000000000..76f6e098f0564 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector/create/transforms/index.ts @@ -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 { transformCreateConnectorBody } from './transform_connector_body/latest'; + +export { transformCreateConnectorBody as transformCreateConnectorBodyV1 } from './transform_connector_body/v1'; diff --git a/x-pack/plugins/actions/server/routes/connector/create/transforms/transform_connector_body/latest.ts b/x-pack/plugins/actions/server/routes/connector/create/transforms/transform_connector_body/latest.ts new file mode 100644 index 0000000000000..4584455ddf5c2 --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector/create/transforms/transform_connector_body/latest.ts @@ -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 { transformCreateConnectorBody } from './v1'; diff --git a/x-pack/plugins/actions/server/routes/connector/create/transforms/transform_connector_body/v1.ts b/x-pack/plugins/actions/server/routes/connector/create/transforms/transform_connector_body/v1.ts new file mode 100644 index 0000000000000..07d7a5e7afa4c --- /dev/null +++ b/x-pack/plugins/actions/server/routes/connector/create/transforms/transform_connector_body/v1.ts @@ -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 { ConnectorCreateParams } from '../../../../../application/connector/methods/create/types'; +import { CreateConnectorRequestBodyV1 } from '../../../../../../common/routes/connector/apis/create'; + +export const transformCreateConnectorBody = ({ + connector_type_id: actionTypeId, + name, + config, + secrets, +}: CreateConnectorRequestBodyV1): ConnectorCreateParams['action'] => ({ + actionTypeId, + name, + config, + secrets, +}); diff --git a/x-pack/plugins/actions/server/routes/connector/get/get.ts b/x-pack/plugins/actions/server/routes/connector/get/get.ts index 686b54655c892..eaab31594ba1b 100644 --- a/x-pack/plugins/actions/server/routes/connector/get/get.ts +++ b/x-pack/plugins/actions/server/routes/connector/get/get.ts @@ -11,7 +11,7 @@ import { GetConnectorParamsV1, } from '../../../../common/routes/connector/apis/get'; import { connectorResponseSchemaV1 } from '../../../../common/routes/connector/response'; -import { transformGetConnectorResponseV1 } from './transforms'; +import { transformConnectorResponseV1 } from '../common_transforms'; import { ILicenseState } from '../../../lib'; import { BASE_ACTION_API_PATH } from '../../../../common'; import { ActionsRequestHandlerContext } from '../../../types'; @@ -46,7 +46,7 @@ export const getConnectorRoute = ( const actionsClient = (await context.actions).getActionsClient(); const { id }: GetConnectorParamsV1 = req.params; return res.ok({ - body: transformGetConnectorResponseV1(await actionsClient.get({ id })), + body: transformConnectorResponseV1(await actionsClient.get({ id })), }); }) ) diff --git a/x-pack/plugins/actions/server/routes/create.ts b/x-pack/plugins/actions/server/routes/create.ts deleted file mode 100644 index 25962701918a5..0000000000000 --- a/x-pack/plugins/actions/server/routes/create.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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'; -import { IRouter } from '@kbn/core/server'; -import { ActionResult, ActionsRequestHandlerContext } from '../types'; -import { ILicenseState } from '../lib'; -import { BASE_ACTION_API_PATH, RewriteRequestCase, RewriteResponseCase } from '../../common'; -import { verifyAccessAndContext } from './verify_access_and_context'; -import { CreateOptions } from '../actions_client'; -import { connectorResponseSchemaV1 } from '../../common/routes/connector/response'; -import { validateEmptyStrings } from '../../common/validate_empty_strings'; - -export const bodySchema = schema.object({ - name: schema.string({ - validate: validateEmptyStrings, - meta: { description: 'The display name for the connector.' }, - }), - connector_type_id: schema.string({ - validate: validateEmptyStrings, - meta: { description: 'The type of connector.' }, - }), - config: schema.recordOf(schema.string(), schema.any({ validate: validateEmptyStrings }), { - defaultValue: {}, - }), - secrets: schema.recordOf(schema.string(), schema.any({ validate: validateEmptyStrings }), { - defaultValue: {}, - }), -}); - -const rewriteBodyReq: RewriteRequestCase = ({ - connector_type_id: actionTypeId, - name, - config, - secrets, -}) => ({ actionTypeId, name, config, secrets }); -const rewriteBodyRes: RewriteResponseCase = ({ - actionTypeId, - isPreconfigured, - isDeprecated, - isMissingSecrets, - isSystemAction, - ...res -}) => ({ - ...res, - connector_type_id: actionTypeId, - is_preconfigured: isPreconfigured, - is_deprecated: isDeprecated, - is_missing_secrets: isMissingSecrets, - is_system_action: isSystemAction, -}); - -export const createActionRoute = ( - router: IRouter, - licenseState: ILicenseState -) => { - router.post( - { - path: `${BASE_ACTION_API_PATH}/connector/{id?}`, - options: { - access: 'public', - summary: 'Create a connector', - tags: ['oas-tag:connectors'], - }, - validate: { - request: { - params: schema.maybe( - schema.object({ - id: schema.maybe( - schema.string({ meta: { description: 'An identifier for the connector.' } }) - ), - }) - ), - body: bodySchema, - }, - response: { - 200: { - description: 'Indicates a successful call.', - body: () => connectorResponseSchemaV1, - }, - }, - }, - }, - router.handleLegacyErrors( - verifyAccessAndContext(licenseState, async function (context, req, res) { - const actionsClient = (await context.actions).getActionsClient(); - const action = rewriteBodyReq(req.body); - return res.ok({ - body: rewriteBodyRes(await actionsClient.create({ action, options: req.params })), - }); - }) - ) - ); -}; diff --git a/x-pack/plugins/actions/server/routes/index.ts b/x-pack/plugins/actions/server/routes/index.ts index 5ea804d1ce47e..39efe6619176b 100644 --- a/x-pack/plugins/actions/server/routes/index.ts +++ b/x-pack/plugins/actions/server/routes/index.ts @@ -13,7 +13,7 @@ import { listTypesRoute } from './connector/list_types'; import { listTypesWithSystemRoute } from './connector/list_types_system'; import { ILicenseState } from '../lib'; import { ActionsRequestHandlerContext } from '../types'; -import { createActionRoute } from './create'; +import { createConnectorRoute } from './connector/create'; import { deleteConnectorRoute } from './connector/delete'; import { executeConnectorRoute } from './connector/execute'; import { getConnectorRoute } from './connector/get'; @@ -36,7 +36,7 @@ export function defineRoutes(opts: RouteOptions) { defineLegacyRoutes(router, licenseState, usageCounter); - createActionRoute(router, licenseState); + createConnectorRoute(router, licenseState); deleteConnectorRoute(router, licenseState); getConnectorRoute(router, licenseState); getAllConnectorsRoute(router, licenseState); diff --git a/x-pack/plugins/security_solution/scripts/endpoint/common/connectors_services.ts b/x-pack/plugins/security_solution/scripts/endpoint/common/connectors_services.ts index 3fb2971f1236d..62ce76437c1d6 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/common/connectors_services.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/common/connectors_services.ts @@ -7,8 +7,7 @@ import type { KbnClient } from '@kbn/test'; import type { AllConnectorsResponseV1 } from '@kbn/actions-plugin/common/routes/connector/response'; -import type { bodySchema } from '@kbn/actions-plugin/server/routes/create'; -import type { TypeOf } from '@kbn/config-schema'; +import type { CreateConnectorRequestBodyV1 } from '@kbn/actions-plugin/common/routes/connector/apis/create'; import type { Connector } from '@kbn/actions-plugin/server/application/connector/types'; import { catchAxiosErrorFormatAndThrow } from '../../../common/endpoint/format_axios_error'; @@ -46,8 +45,6 @@ export const fetchConnectorByType = async ( } }; -type CreateConnectorBody = TypeOf; - /** * Creates a connector in the stack * @param kbnClient @@ -55,7 +52,7 @@ type CreateConnectorBody = TypeOf; */ export const createConnector = async ( kbnClient: KbnClient, - createPayload: CreateConnectorBody + createPayload: CreateConnectorRequestBodyV1 ): Promise => { return kbnClient .request({