diff --git a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts index 868c07b775c78..4e6b86d5d19b1 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/es_index.ts @@ -39,10 +39,11 @@ const ParamsSchema = schema.object({ documents: schema.arrayOf(schema.recordOf(schema.string(), schema.any())), }); +export const ES_INDEX_ACTION_ID = '.index'; // action type definition export function getActionType({ logger }: { logger: Logger }): ESIndexActionType { return { - id: '.index', + id: ES_INDEX_ACTION_ID, minimumLicenseRequired: 'basic', name: i18n.translate('xpack.actions.builtin.esIndexTitle', { defaultMessage: 'Index', diff --git a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts index 490764fb16bfd..7a397ac4ce92e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/server_log.ts @@ -38,10 +38,11 @@ const ParamsSchema = schema.object({ ), }); +export const SERVER_LOG_ACTION_ID = '.server-log'; // action type definition export function getActionType({ logger }: { logger: Logger }): ServerLogActionType { return { - id: '.server-log', + id: SERVER_LOG_ACTION_ID, minimumLicenseRequired: 'basic', name: i18n.translate('xpack.actions.builtin.serverLogTitle', { defaultMessage: 'Server log', diff --git a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.test.ts b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.test.ts new file mode 100644 index 0000000000000..845f8d2688f49 --- /dev/null +++ b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.test.ts @@ -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 { ActionType } from '../types'; +import { ensureSufficientLicense } from './ensure_sufficient_license'; + +const sampleActionType: ActionType = { + id: 'test', + name: 'test', + minimumLicenseRequired: 'basic', + async executor({ actionId }) { + return { status: 'ok', actionId }; + }, +}; + +describe('ensureSufficientLicense()', () => { + it('throws for licenses below gold', () => { + expect(() => ensureSufficientLicense(sampleActionType)).toThrowErrorMatchingInlineSnapshot( + `"Third party action type \\"test\\" can only set minimumLicenseRequired to a gold license or higher"` + ); + }); + + it('allows licenses below gold for allowed connectors', () => { + expect(() => + ensureSufficientLicense({ ...sampleActionType, id: '.case', minimumLicenseRequired: 'basic' }) + ).not.toThrow(); + expect(() => + ensureSufficientLicense({ + ...sampleActionType, + id: '.server-log', + minimumLicenseRequired: 'basic', + }) + ).not.toThrow(); + expect(() => + ensureSufficientLicense({ + ...sampleActionType, + id: '.index', + minimumLicenseRequired: 'basic', + }) + ).not.toThrow(); + }); + + it('allows licenses at gold', () => { + expect(() => + ensureSufficientLicense({ ...sampleActionType, minimumLicenseRequired: 'gold' }) + ).not.toThrow(); + }); + + it('allows licenses above gold', () => { + expect(() => + ensureSufficientLicense({ ...sampleActionType, minimumLicenseRequired: 'platinum' }) + ).not.toThrow(); + }); + + it('throws when license type is invalid', async () => { + expect(() => + ensureSufficientLicense({ + ...sampleActionType, + // we're faking an invalid value, this requires stripping the typing + // eslint-disable-next-line @typescript-eslint/no-explicit-any + minimumLicenseRequired: 'foo' as any, + }) + ).toThrowErrorMatchingInlineSnapshot(`"\\"foo\\" is not a valid license type"`); + }); +}); diff --git a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts new file mode 100644 index 0000000000000..01cf65a50c79a --- /dev/null +++ b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts @@ -0,0 +1,36 @@ +/* + * 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 { ActionType } from '../types'; +import { LICENSE_TYPE } from '../../../licensing/common/types'; +import { SERVER_LOG_ACTION_ID } from '../builtin_action_types/server_log'; +import { ES_INDEX_ACTION_ID } from '../builtin_action_types/es_index'; +import { CASE_ACTION_ID } from '../../../case/server'; +import { ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types'; + +const ACTIONS_SCOPED_WITHIN_STACK = new Set([ + SERVER_LOG_ACTION_ID, + ES_INDEX_ACTION_ID, + CASE_ACTION_ID, +]); + +export function ensureSufficientLicense< + Config extends ActionTypeConfig, + Secrets extends ActionTypeSecrets, + Params extends ActionTypeParams, + ExecutorResultData +>(actionType: ActionType) { + if (!(actionType.minimumLicenseRequired in LICENSE_TYPE)) { + throw new Error(`"${actionType.minimumLicenseRequired}" is not a valid license type`); + } + if ( + LICENSE_TYPE[actionType.minimumLicenseRequired] < LICENSE_TYPE.gold && + !ACTIONS_SCOPED_WITHIN_STACK.has(actionType.id) + ) { + throw new Error( + `Third party action type "${actionType.id}" can only set minimumLicenseRequired to a gold license or higher` + ); + } +} diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 599e7461ea312..9db07f653872f 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -27,7 +27,6 @@ import { } from '../../encrypted_saved_objects/server'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server'; -import { LICENSE_TYPE } from '../../licensing/common/types'; import { SpacesPluginSetup, SpacesServiceSetup } from '../../spaces/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../features/server'; import { SecurityPluginSetup } from '../../security/server'; @@ -75,6 +74,7 @@ import { getAuthorizationModeBySource, AuthorizationMode, } from './authorization/get_authorization_mode_by_source'; +import { ensureSufficientLicense } from './lib/ensure_sufficient_license'; const EVENT_LOG_PROVIDER = 'actions'; export const EVENT_LOG_ACTIONS = { @@ -260,14 +260,7 @@ export class ActionsPlugin implements Plugin, Plugi >( actionType: ActionType ) => { - if (!(actionType.minimumLicenseRequired in LICENSE_TYPE)) { - throw new Error(`"${actionType.minimumLicenseRequired}" is not a valid license type`); - } - if (LICENSE_TYPE[actionType.minimumLicenseRequired] < LICENSE_TYPE.gold) { - throw new Error( - `Third party action type "${actionType.id}" can only set minimumLicenseRequired to a gold license or higher` - ); - } + ensureSufficientLicense(actionType); actionTypeRegistry.register(actionType); }, }; diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index f284f0ed9668c..12d3ecde7af23 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -23,6 +23,7 @@ import { GetActionTypeParams } from '..'; const supportedSubActions: string[] = ['create', 'update', 'addComment']; +export const CASE_ACTION_ID = '.case'; // action type definition export function getActionType({ logger, @@ -31,7 +32,7 @@ export function getActionType({ userActionService, }: GetActionTypeParams): CaseActionType { return { - id: '.case', + id: CASE_ACTION_ID, minimumLicenseRequired: 'gold', name: i18n.NAME, validate: { diff --git a/x-pack/plugins/case/server/connectors/index.ts b/x-pack/plugins/case/server/connectors/index.ts index 6a97a9e6e8a8a..5c182b30b4351 100644 --- a/x-pack/plugins/case/server/connectors/index.ts +++ b/x-pack/plugins/case/server/connectors/index.ts @@ -19,6 +19,7 @@ import { } from '../services'; import { getActionType as getCaseConnector } from './case'; +export { CASE_ACTION_ID } from './case'; export interface GetActionTypeParams { logger: Logger; diff --git a/x-pack/plugins/case/server/index.ts b/x-pack/plugins/case/server/index.ts index f924810baa912..d5b3c0b299d65 100644 --- a/x-pack/plugins/case/server/index.ts +++ b/x-pack/plugins/case/server/index.ts @@ -8,6 +8,7 @@ import { PluginInitializerContext } from '../../../../src/core/server'; import { ConfigSchema } from './config'; import { CasePlugin } from './plugin'; +export { CASE_ACTION_ID } from './connectors'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) => new CasePlugin(initializerContext);