Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

enables actions scoped within the stack to register at Basic license #82931

Merged
merged 8 commits into from
Nov 12, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ const ParamsSchema = schema.object({
documents: schema.arrayOf(schema.recordOf(schema.string(), schema.any())),
});

export const ES_INDEX_ACTION_TYPE_ID = '.index';
// action type definition
export function getActionType({ logger }: { logger: Logger }): ESIndexActionType {
return {
id: '.index',
id: ES_INDEX_ACTION_TYPE_ID,
minimumLicenseRequired: 'basic',
name: i18n.translate('xpack.actions.builtin.esIndexTitle', {
defaultMessage: 'Index',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ const ParamsSchema = schema.object({
),
});

export const SERVER_LOG_ACTION_TYPE_ID = '.server-log';
// action type definition
export function getActionType({ logger }: { logger: Logger }): ServerLogActionType {
return {
id: '.server-log',
id: SERVER_LOG_ACTION_TYPE_ID,
minimumLicenseRequired: 'basic',
name: i18n.translate('xpack.actions.builtin.serverLogTitle', {
defaultMessage: 'Server log',
Expand Down
Original file line number Diff line number Diff line change
@@ -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"`);
});
});
36 changes: 36 additions & 0 deletions x-pack/plugins/actions/server/lib/ensure_sufficient_license.ts
Original file line number Diff line number Diff line change
@@ -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_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_TYPE_ID,
ES_INDEX_ACTION_TYPE_ID,
CASE_ACTION_TYPE_ID,
]);

export function ensureSufficientLicense<
Config extends ActionTypeConfig,
Secrets extends ActionTypeSecrets,
Params extends ActionTypeParams,
ExecutorResultData
>(actionType: ActionType<Config, Secrets, Params, ExecutorResultData>) {
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`
);
}
}
11 changes: 2 additions & 9 deletions x-pack/plugins/actions/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -260,14 +260,7 @@ export class ActionsPlugin implements Plugin<Promise<PluginSetupContract>, Plugi
>(
actionType: ActionType<Config, Secrets, Params, ExecutorResultData>
) => {
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);
},
};
Expand Down
3 changes: 2 additions & 1 deletion x-pack/plugins/case/server/connectors/case/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { GetActionTypeParams } from '..';

const supportedSubActions: string[] = ['create', 'update', 'addComment'];

export const CASE_ACTION_TYPE_ID = '.case';
// action type definition
export function getActionType({
logger,
Expand All @@ -31,7 +32,7 @@ export function getActionType({
userActionService,
}: GetActionTypeParams): CaseActionType {
return {
id: '.case',
id: CASE_ACTION_TYPE_ID,
minimumLicenseRequired: 'gold',
name: i18n.NAME,
validate: {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/case/server/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
} from '../services';

import { getActionType as getCaseConnector } from './case';
export { CASE_ACTION_TYPE_ID } from './case';

export interface GetActionTypeParams {
logger: Logger;
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/case/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { PluginInitializerContext } from '../../../../src/core/server';
import { ConfigSchema } from './config';
import { CasePlugin } from './plugin';

export { CASE_ACTION_TYPE_ID } from './connectors';
export { CaseRequestContext } from './types';
export const config = { schema: ConfigSchema };
export const plugin = (initializerContext: PluginInitializerContext) =>
new CasePlugin(initializerContext);