From 34d4a6ce0689b9f15c552f3dbd478145a124ab13 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 9 Nov 2020 10:13:23 +0000 Subject: [PATCH 1/5] enabled actions scoped within the stack to register at Basic license --- .../server/builtin_action_types/es_index.ts | 3 +- .../server/builtin_action_types/server_log.ts | 3 +- .../lib/ensure_sufficient_license.test.ts | 68 +++++++++++++++++++ .../server/lib/ensure_sufficient_license.ts | 36 ++++++++++ x-pack/plugins/actions/server/plugin.ts | 11 +-- .../case/server/connectors/case/index.ts | 3 +- .../plugins/case/server/connectors/index.ts | 1 + x-pack/plugins/case/server/index.ts | 1 + 8 files changed, 114 insertions(+), 12 deletions(-) create mode 100644 x-pack/plugins/actions/server/lib/ensure_sufficient_license.test.ts create mode 100644 x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts 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); From 544c28cc138a5ef108a0e8adc614c19db7adb104 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 9 Nov 2020 14:48:40 +0000 Subject: [PATCH 2/5] attempt to fix type of context --- x-pack/plugins/case/server/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 64c4b422d1cf7..958ce1e7ef55e 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -139,7 +139,7 @@ export class CasePlugin { caseService: CaseServiceSetup; caseConfigureService: CaseConfigureServiceSetup; userActionService: CaseUserActionServiceSetup; - }): IContextProvider, typeof APP_ID> => { + }): IContextProvider, 'case'> => { return async (context, request) => { const [{ savedObjects }] = await core.getStartServices(); return { From 603eda4f80735fff9536f93f338018df8a3425a7 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 9 Nov 2020 15:22:10 +0000 Subject: [PATCH 3/5] touch types --- x-pack/plugins/case/server/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/case/server/index.ts b/x-pack/plugins/case/server/index.ts index d5b3c0b299d65..67ee008973ac2 100644 --- a/x-pack/plugins/case/server/index.ts +++ b/x-pack/plugins/case/server/index.ts @@ -9,6 +9,7 @@ import { ConfigSchema } from './config'; import { CasePlugin } from './plugin'; export { CASE_ACTION_ID } from './connectors'; +export { CaseRequestContext } from './types'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) => new CasePlugin(initializerContext); From 38d1e8e9cf6a32b935d893cdcc4f91bb0acfbbef Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 9 Nov 2020 15:44:14 +0000 Subject: [PATCH 4/5] Revert "attempt to fix type of context" This reverts commit 544c28cc138a5ef108a0e8adc614c19db7adb104. --- x-pack/plugins/case/server/plugin.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts index 958ce1e7ef55e..64c4b422d1cf7 100644 --- a/x-pack/plugins/case/server/plugin.ts +++ b/x-pack/plugins/case/server/plugin.ts @@ -139,7 +139,7 @@ export class CasePlugin { caseService: CaseServiceSetup; caseConfigureService: CaseConfigureServiceSetup; userActionService: CaseUserActionServiceSetup; - }): IContextProvider, 'case'> => { + }): IContextProvider, typeof APP_ID> => { return async (context, request) => { const [{ savedObjects }] = await core.getStartServices(); return { From f55e10800d9aa83e1c428e4d58d36f0c13948ad0 Mon Sep 17 00:00:00 2001 From: Gidi Meir Morris Date: Mon, 9 Nov 2020 17:07:51 +0000 Subject: [PATCH 5/5] added TYPE to IDs --- .../actions/server/builtin_action_types/es_index.ts | 4 ++-- .../server/builtin_action_types/server_log.ts | 4 ++-- .../actions/server/lib/ensure_sufficient_license.ts | 12 ++++++------ x-pack/plugins/case/server/connectors/case/index.ts | 4 ++-- x-pack/plugins/case/server/connectors/index.ts | 2 +- x-pack/plugins/case/server/index.ts | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) 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 4e6b86d5d19b1..6926c826f776e 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,11 +39,11 @@ const ParamsSchema = schema.object({ documents: schema.arrayOf(schema.recordOf(schema.string(), schema.any())), }); -export const ES_INDEX_ACTION_ID = '.index'; +export const ES_INDEX_ACTION_TYPE_ID = '.index'; // action type definition export function getActionType({ logger }: { logger: Logger }): ESIndexActionType { return { - id: ES_INDEX_ACTION_ID, + id: ES_INDEX_ACTION_TYPE_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 7a397ac4ce92e..c485de8628f14 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,11 +38,11 @@ const ParamsSchema = schema.object({ ), }); -export const SERVER_LOG_ACTION_ID = '.server-log'; +export const SERVER_LOG_ACTION_TYPE_ID = '.server-log'; // action type definition export function getActionType({ logger }: { logger: Logger }): ServerLogActionType { return { - id: SERVER_LOG_ACTION_ID, + id: SERVER_LOG_ACTION_TYPE_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.ts b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts index 01cf65a50c79a..0f309bb76b76c 100644 --- a/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts +++ b/x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts @@ -5,15 +5,15 @@ */ 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 { SERVER_LOG_ACTION_TYPE_ID } from '../builtin_action_types/server_log'; +import { ES_INDEX_ACTION_TYPE_ID } from '../builtin_action_types/es_index'; +import { CASE_ACTION_TYPE_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, + SERVER_LOG_ACTION_TYPE_ID, + ES_INDEX_ACTION_TYPE_ID, + CASE_ACTION_TYPE_ID, ]); export function ensureSufficientLicense< diff --git a/x-pack/plugins/case/server/connectors/case/index.ts b/x-pack/plugins/case/server/connectors/case/index.ts index 12d3ecde7af23..f2f8f659f3a2c 100644 --- a/x-pack/plugins/case/server/connectors/case/index.ts +++ b/x-pack/plugins/case/server/connectors/case/index.ts @@ -23,7 +23,7 @@ import { GetActionTypeParams } from '..'; const supportedSubActions: string[] = ['create', 'update', 'addComment']; -export const CASE_ACTION_ID = '.case'; +export const CASE_ACTION_TYPE_ID = '.case'; // action type definition export function getActionType({ logger, @@ -32,7 +32,7 @@ export function getActionType({ userActionService, }: GetActionTypeParams): CaseActionType { return { - id: CASE_ACTION_ID, + id: CASE_ACTION_TYPE_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 5c182b30b4351..bee7b1e475457 100644 --- a/x-pack/plugins/case/server/connectors/index.ts +++ b/x-pack/plugins/case/server/connectors/index.ts @@ -19,7 +19,7 @@ import { } from '../services'; import { getActionType as getCaseConnector } from './case'; -export { CASE_ACTION_ID } from './case'; +export { CASE_ACTION_TYPE_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 67ee008973ac2..d4f06c8a2304c 100644 --- a/x-pack/plugins/case/server/index.ts +++ b/x-pack/plugins/case/server/index.ts @@ -8,7 +8,7 @@ import { PluginInitializerContext } from '../../../../src/core/server'; import { ConfigSchema } from './config'; import { CasePlugin } from './plugin'; -export { CASE_ACTION_ID } from './connectors'; +export { CASE_ACTION_TYPE_ID } from './connectors'; export { CaseRequestContext } from './types'; export const config = { schema: ConfigSchema }; export const plugin = (initializerContext: PluginInitializerContext) =>