mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Actions] Set system actions on Kibana start (#160983)
## Summary This PR: - Adds the ability to create system action types - Creates system connectors on Kibana `start` from the system action types - Prevents system action to be created/updated/deleted - Return system actions from the get/getAll endpoints ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
552a3a6553
commit
67fc8333e7
66 changed files with 3153 additions and 872 deletions
|
@ -50,6 +50,7 @@ export const ConnectorSelector: React.FC<Props> = React.memo(
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['general'],
|
||||
isSystemActionType: false,
|
||||
id: '.gen-ai',
|
||||
name: 'Generative AI',
|
||||
enabled: true,
|
||||
|
|
|
@ -92,6 +92,7 @@ export const useConnectorSetup = ({
|
|||
actionTypes?.find((at) => at.id === GEN_AI_CONNECTOR_ID) ?? {
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['general'],
|
||||
id: '.gen-ai',
|
||||
|
|
|
@ -22,6 +22,7 @@ export interface ActionType {
|
|||
enabledInLicense: boolean;
|
||||
minimumLicenseRequired: LicenseType;
|
||||
supportedFeatureIds: string[];
|
||||
isSystemActionType: boolean;
|
||||
}
|
||||
|
||||
export enum InvalidEmailReason {
|
||||
|
|
|
@ -17,6 +17,7 @@ const createActionTypeRegistryMock = () => {
|
|||
ensureActionTypeEnabled: jest.fn(),
|
||||
isActionTypeEnabled: jest.fn(),
|
||||
isActionExecutable: jest.fn(),
|
||||
isSystemActionType: jest.fn(),
|
||||
getUtils: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
|
|
|
@ -36,7 +36,7 @@ describe('actionTypeRegistry', () => {
|
|||
),
|
||||
actionsConfigUtils: mockedActionsConfig,
|
||||
licenseState: mockedLicenseState,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: 'foo',
|
||||
config: {},
|
||||
|
@ -47,6 +47,16 @@ describe('actionTypeRegistry', () => {
|
|||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
},
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
@ -217,7 +227,7 @@ describe('actionTypeRegistry', () => {
|
|||
expect(actionTypeRegistryParams.licensing.featureUsage.register).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('does not allows registering system actions', () => {
|
||||
test('allows registering system actions', () => {
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
|
||||
expect(() =>
|
||||
|
@ -226,7 +236,7 @@ describe('actionTypeRegistry', () => {
|
|||
name: 'My action type',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemAction: true,
|
||||
isSystemActionType: true,
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
|
@ -234,7 +244,7 @@ describe('actionTypeRegistry', () => {
|
|||
},
|
||||
executor,
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(`"System actions are not supported"`);
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -302,6 +312,7 @@ describe('actionTypeRegistry', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
]);
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalled();
|
||||
|
@ -345,11 +356,46 @@ describe('actionTypeRegistry', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
]);
|
||||
expect(mockedActionsConfig.isActionTypeEnabled).toHaveBeenCalled();
|
||||
expect(mockedLicenseState.isLicenseValidForActionType).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('sets the isSystemActionType correctly for system actions', () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
const actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
|
||||
actionTypeRegistry.register({
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
isSystemActionType: true,
|
||||
executor,
|
||||
});
|
||||
|
||||
const actionTypes = actionTypeRegistry.list();
|
||||
|
||||
expect(actionTypes).toEqual([
|
||||
{
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('has()', () => {
|
||||
|
@ -378,6 +424,7 @@ describe('actionTypeRegistry', () => {
|
|||
|
||||
describe('isActionTypeEnabled', () => {
|
||||
let actionTypeRegistry: ActionTypeRegistry;
|
||||
|
||||
const fooActionType: ActionType = {
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
|
@ -393,9 +440,17 @@ describe('actionTypeRegistry', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const systemActionType: ActionType = {
|
||||
...fooActionType,
|
||||
id: 'system-action-type',
|
||||
name: 'System action type',
|
||||
isSystemActionType: true,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionTypeRegistry.register(fooActionType);
|
||||
actionTypeRegistry.register(systemActionType);
|
||||
});
|
||||
|
||||
test('should call isActionTypeEnabled of the actions config', async () => {
|
||||
|
@ -417,6 +472,15 @@ describe('actionTypeRegistry', () => {
|
|||
expect(actionTypeRegistry.isActionExecutable('my-slack1', 'foo')).toEqual(true);
|
||||
});
|
||||
|
||||
test('should return true when isActionTypeEnabled is false and isLicenseValidForActionType is true and it has system connectors', async () => {
|
||||
mockedActionsConfig.isActionTypeEnabled.mockReturnValue(false);
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
|
||||
expect(
|
||||
actionTypeRegistry.isActionExecutable('system-connector-.cases', 'system-action-type')
|
||||
).toEqual(true);
|
||||
});
|
||||
|
||||
test('should call isLicenseValidForActionType of the license state with notifyUsage false by default', async () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
actionTypeRegistry.isActionTypeEnabled('foo');
|
||||
|
@ -567,4 +631,62 @@ describe('actionTypeRegistry', () => {
|
|||
expect(result).toEqual(['foo']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('isSystemActionType()', () => {
|
||||
it('should return true if the action type is a system action type', () => {
|
||||
const registry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
|
||||
registry.register({
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
isSystemActionType: true,
|
||||
executor,
|
||||
});
|
||||
|
||||
const result = registry.isSystemActionType('.cases');
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the action type is not a system action type', () => {
|
||||
mockedLicenseState.isLicenseValidForActionType.mockReturnValue({ isValid: true });
|
||||
|
||||
const registry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
|
||||
registry.register({
|
||||
id: 'foo',
|
||||
name: 'Foo',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
executor,
|
||||
});
|
||||
|
||||
const allTypes = registry.getAllTypes();
|
||||
expect(allTypes.length).toBe(1);
|
||||
|
||||
const result = registry.isSystemActionType('foo');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false if the action type does not exists', () => {
|
||||
const registry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
|
||||
const allTypes = registry.getAllTypes();
|
||||
expect(allTypes.length).toBe(0);
|
||||
|
||||
const result = registry.isSystemActionType('not-exist');
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ import { ActionsConfigurationUtilities } from './actions_config';
|
|||
import { getActionTypeFeatureUsageName, TaskRunnerFactory, ILicenseState } from './lib';
|
||||
import {
|
||||
ActionType,
|
||||
PreConfiguredAction,
|
||||
InMemoryConnector,
|
||||
ActionTypeConfig,
|
||||
ActionTypeSecrets,
|
||||
ActionTypeParams,
|
||||
|
@ -26,7 +26,7 @@ export interface ActionTypeRegistryOpts {
|
|||
taskRunnerFactory: TaskRunnerFactory;
|
||||
actionsConfigUtils: ActionsConfigurationUtilities;
|
||||
licenseState: ILicenseState;
|
||||
preconfiguredActions: PreConfiguredAction[];
|
||||
inMemoryConnectors: InMemoryConnector[];
|
||||
}
|
||||
|
||||
export class ActionTypeRegistry {
|
||||
|
@ -35,7 +35,7 @@ export class ActionTypeRegistry {
|
|||
private readonly taskRunnerFactory: TaskRunnerFactory;
|
||||
private readonly actionsConfigUtils: ActionsConfigurationUtilities;
|
||||
private readonly licenseState: ILicenseState;
|
||||
private readonly preconfiguredActions: PreConfiguredAction[];
|
||||
private readonly inMemoryConnectors: InMemoryConnector[];
|
||||
private readonly licensing: LicensingPluginSetup;
|
||||
|
||||
constructor(constructorParams: ActionTypeRegistryOpts) {
|
||||
|
@ -43,7 +43,7 @@ export class ActionTypeRegistry {
|
|||
this.taskRunnerFactory = constructorParams.taskRunnerFactory;
|
||||
this.actionsConfigUtils = constructorParams.actionsConfigUtils;
|
||||
this.licenseState = constructorParams.licenseState;
|
||||
this.preconfiguredActions = constructorParams.preconfiguredActions;
|
||||
this.inMemoryConnectors = constructorParams.inMemoryConnectors;
|
||||
this.licensing = constructorParams.licensing;
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ export class ActionTypeRegistry {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns true if action type is enabled or it is a preconfigured action type.
|
||||
* Returns true if action type is enabled or it is an in memory action type.
|
||||
*/
|
||||
public isActionExecutable(
|
||||
actionId: string,
|
||||
|
@ -89,12 +89,17 @@ export class ActionTypeRegistry {
|
|||
return (
|
||||
actionTypeEnabled ||
|
||||
(!actionTypeEnabled &&
|
||||
this.preconfiguredActions.find(
|
||||
(preconfiguredAction) => preconfiguredAction.id === actionId
|
||||
) !== undefined)
|
||||
this.inMemoryConnectors.find((inMemoryConnector) => inMemoryConnector.id === actionId) !==
|
||||
undefined)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the action type is a system action type
|
||||
*/
|
||||
public isSystemActionType = (actionTypeId: string): boolean =>
|
||||
Boolean(this.actionTypes.get(actionTypeId)?.isSystemActionType);
|
||||
|
||||
/**
|
||||
* Registers an action type to the action type registry
|
||||
*/
|
||||
|
@ -104,18 +109,6 @@ export class ActionTypeRegistry {
|
|||
Params extends ActionTypeParams = ActionTypeParams,
|
||||
ExecutorResultData = void
|
||||
>(actionType: ActionType<Config, Secrets, Params, ExecutorResultData>) {
|
||||
// TODO: Remove when system action are supported
|
||||
if (actionType.isSystemAction) {
|
||||
throw new Error(
|
||||
i18n.translate(
|
||||
'xpack.actions.actionTypeRegistry.register.systemActionsNotSupportedErrorMessage',
|
||||
{
|
||||
defaultMessage: 'System actions are not supported',
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (this.has(actionType.id)) {
|
||||
throw new Error(
|
||||
i18n.translate(
|
||||
|
@ -214,6 +207,7 @@ export class ActionTypeRegistry {
|
|||
enabledInConfig: this.actionsConfigUtils.isActionTypeEnabled(actionTypeId),
|
||||
enabledInLicense: !!this.licenseState.isLicenseValidForActionType(actionType).isValid,
|
||||
supportedFeatureIds: actionType.supportedFeatureIds,
|
||||
isSystemActionType: !!actionType.isSystemActionType,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ const createActionsClientMock = () => {
|
|||
listTypes: jest.fn(),
|
||||
isActionTypeEnabled: jest.fn(),
|
||||
isPreconfigured: jest.fn(),
|
||||
isSystemAction: jest.fn(),
|
||||
getGlobalExecutionKpiWithAuth: jest.fn(),
|
||||
getGlobalExecutionLogWithAuth: jest.fn(),
|
||||
};
|
||||
|
|
|
@ -133,7 +133,7 @@ beforeEach(() => {
|
|||
),
|
||||
actionsConfigUtils: actionsConfigMock.create(),
|
||||
licenseState: mockedLicenseState,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
};
|
||||
actionTypeRegistry = new ActionTypeRegistry(actionTypeRegistryParams);
|
||||
actionsClient = new ActionsClient({
|
||||
|
@ -142,7 +142,7 @@ beforeEach(() => {
|
|||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
|
@ -594,7 +594,7 @@ describe('create()', () => {
|
|||
),
|
||||
actionsConfigUtils: localConfigUtils,
|
||||
licenseState: licenseStateMock.create(),
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
};
|
||||
|
||||
actionTypeRegistry = new ActionTypeRegistry(localActionTypeRegistryParams);
|
||||
|
@ -604,7 +604,7 @@ describe('create()', () => {
|
|||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
|
@ -715,7 +715,7 @@ describe('create()', () => {
|
|||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: preDefinedId,
|
||||
actionTypeId: 'my-action-type',
|
||||
|
@ -755,9 +755,79 @@ describe('create()', () => {
|
|||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"This mySuperRadTestPreconfiguredId already exist in preconfigured action."`
|
||||
`"This mySuperRadTestPreconfiguredId already exists in a preconfigured action."`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when creating a system connector', async () => {
|
||||
actionTypeRegistry.register({
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
isSystemActionType: true,
|
||||
executor,
|
||||
});
|
||||
|
||||
await expect(
|
||||
actionsClient.create({
|
||||
action: {
|
||||
name: 'my name',
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"System action creation is forbidden. Action type: .cases."`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when creating a system connector where the action type is not registered but a system connector exists in the in-memory list', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
await expect(
|
||||
actionsClient.create({
|
||||
action: {
|
||||
name: 'my name',
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Action type \\".cases\\" is not registered."`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('get()', () => {
|
||||
|
@ -793,7 +863,7 @@ describe('get()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
|
@ -818,6 +888,40 @@ describe('get()', () => {
|
|||
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('get');
|
||||
});
|
||||
|
||||
test('ensures user is authorised to get a system action', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
await actionsClient.get({ id: 'system-connector-.cases' });
|
||||
|
||||
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('get');
|
||||
});
|
||||
|
||||
test('throws when user is not authorised to get the type of action', async () => {
|
||||
unsecuredSavedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '1',
|
||||
|
@ -855,7 +959,7 @@ describe('get()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
|
@ -885,6 +989,48 @@ describe('get()', () => {
|
|||
|
||||
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('get');
|
||||
});
|
||||
|
||||
test('throws when user is not authorised to get a system action', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
authorization.ensureAuthorized.mockRejectedValue(
|
||||
new Error(`Unauthorized to get a "system-connector-.cases" action`)
|
||||
);
|
||||
|
||||
await expect(
|
||||
actionsClient.get({ id: 'system-connector-.cases' })
|
||||
).rejects.toMatchInlineSnapshot(
|
||||
`[Error: Unauthorized to get a "system-connector-.cases" action]`
|
||||
);
|
||||
|
||||
expect(authorization.ensureAuthorized).toHaveBeenCalledWith('get');
|
||||
});
|
||||
});
|
||||
|
||||
describe('auditLogger', () => {
|
||||
|
@ -980,7 +1126,7 @@ describe('get()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
|
@ -1011,6 +1157,50 @@ describe('get()', () => {
|
|||
});
|
||||
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('return system action with id', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
const result = await actionsClient.get({ id: 'system-connector-.cases' });
|
||||
|
||||
expect(result).toEqual({
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
name: 'System action: .cases',
|
||||
});
|
||||
|
||||
expect(unsecuredSavedObjectsClient.get).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getAll()', () => {
|
||||
|
@ -1058,7 +1248,7 @@ describe('getAll()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
|
@ -1158,7 +1348,7 @@ describe('getAll()', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('calls unsecuredSavedObjectsClient with parameters', async () => {
|
||||
test('calls unsecuredSavedObjectsClient with parameters and returns inMemoryConnectors correctly', async () => {
|
||||
const expectedResult = {
|
||||
total: 1,
|
||||
per_page: 10,
|
||||
|
@ -1186,6 +1376,7 @@ describe('getAll()', () => {
|
|||
aggregations: {
|
||||
'1': { doc_count: 6 },
|
||||
testPreconfigured: { doc_count: 2 },
|
||||
'system-connector-.cases': { doc_count: 2 },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -1202,7 +1393,7 @@ describe('getAll()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
|
@ -1215,31 +1406,51 @@ describe('getAll()', () => {
|
|||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
const result = await actionsClient.getAll();
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
id: '1',
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
isPreconfigured: false,
|
||||
isSystemAction: false,
|
||||
isSystemAction: true,
|
||||
isDeprecated: false,
|
||||
referencedByCount: 2,
|
||||
},
|
||||
{
|
||||
id: '1',
|
||||
name: 'test',
|
||||
config: {
|
||||
foo: 'bar',
|
||||
},
|
||||
isMissingSecrets: false,
|
||||
config: { foo: 'bar' },
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
referencedByCount: 6,
|
||||
},
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
name: 'test',
|
||||
isPreconfigured: true,
|
||||
isSystemAction: false,
|
||||
isDeprecated: false,
|
||||
name: 'test',
|
||||
referencedByCount: 2,
|
||||
},
|
||||
]);
|
||||
|
@ -1288,7 +1499,7 @@ describe('getBulk()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
|
@ -1386,7 +1597,7 @@ describe('getBulk()', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('calls getBulk unsecuredSavedObjectsClient with parameters', async () => {
|
||||
test('calls getBulk unsecuredSavedObjectsClient with parameters and return inMemoryConnectors correctly', async () => {
|
||||
unsecuredSavedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
|
@ -1410,6 +1621,7 @@ describe('getBulk()', () => {
|
|||
aggregations: {
|
||||
'1': { doc_count: 6 },
|
||||
testPreconfigured: { doc_count: 2 },
|
||||
'system-connector-.cases': { doc_count: 2 },
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -1426,7 +1638,7 @@ describe('getBulk()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
|
@ -1439,35 +1651,59 @@ describe('getBulk()', () => {
|
|||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
const result = await actionsClient.getBulk(['1', 'testPreconfigured']);
|
||||
|
||||
const result = await actionsClient.getBulk([
|
||||
'1',
|
||||
'testPreconfigured',
|
||||
'system-connector-.cases',
|
||||
]);
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
actionTypeId: '.slack',
|
||||
config: {
|
||||
foo: 'bar',
|
||||
},
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
secrets: {},
|
||||
isPreconfigured: true,
|
||||
isSystemAction: false,
|
||||
isDeprecated: false,
|
||||
name: 'test',
|
||||
secrets: {},
|
||||
config: { foo: 'bar' },
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
{
|
||||
actionTypeId: 'test',
|
||||
config: {
|
||||
foo: 'bar',
|
||||
},
|
||||
id: '1',
|
||||
actionTypeId: 'test',
|
||||
name: 'test',
|
||||
config: { foo: 'bar' },
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: false,
|
||||
isDeprecated: false,
|
||||
name: 'test',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -1489,7 +1725,7 @@ describe('getOAuthAccessToken()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: '.slack',
|
||||
|
@ -1842,6 +2078,83 @@ describe('delete()', () => {
|
|||
]
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws when trying to delete a preconfigured connector', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
secrets: {
|
||||
test: 'test1',
|
||||
},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'test',
|
||||
config: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
],
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
await expect(
|
||||
actionsClient.delete({ id: 'testPreconfigured' })
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Preconfigured action testPreconfigured is not allowed to delete."`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when trying to delete a system connector', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
await expect(
|
||||
actionsClient.delete({ id: 'system-connector-.cases' })
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"System action system-connector-.cases is not allowed to delete."`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('update()', () => {
|
||||
|
@ -2318,6 +2631,97 @@ describe('update()', () => {
|
|||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
});
|
||||
|
||||
it('throws when trying to update a preconfigured connector', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
secrets: {
|
||||
test: 'test1',
|
||||
},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'test',
|
||||
config: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
],
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
await expect(
|
||||
actionsClient.update({
|
||||
id: 'testPreconfigured',
|
||||
action: {
|
||||
name: 'my name',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"Preconfigured action testPreconfigured can not be updated."`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws when trying to update a system connector', async () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
connectorTokenClient: connectorTokenClientMock.create(),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
await expect(
|
||||
actionsClient.update({
|
||||
id: 'system-connector-.cases',
|
||||
action: {
|
||||
name: 'my name',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
})
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"System action system-connector-.cases can not be updated."`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('execute()', () => {
|
||||
|
@ -2701,7 +3105,7 @@ describe('isActionTypeEnabled()', () => {
|
|||
});
|
||||
|
||||
describe('isPreconfigured()', () => {
|
||||
test('should return true if connector id is in list of preconfigured connectors', () => {
|
||||
test('should return true if the connector is a preconfigured connector', () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
|
@ -2714,7 +3118,7 @@ describe('isPreconfigured()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
|
@ -2729,6 +3133,17 @@ describe('isPreconfigured()', () => {
|
|||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: new ConnectorTokenClient({
|
||||
unsecuredSavedObjectsClient: savedObjectsClientMock.create(),
|
||||
|
@ -2741,7 +3156,7 @@ describe('isPreconfigured()', () => {
|
|||
expect(actionsClient.isPreconfigured('testPreconfigured')).toEqual(true);
|
||||
});
|
||||
|
||||
test('should return false if connector id is not in list of preconfigured connectors', () => {
|
||||
test('should return false if the connector is not preconfigured connector', () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
|
@ -2754,7 +3169,7 @@ describe('isPreconfigured()', () => {
|
|||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
|
@ -2769,6 +3184,17 @@ describe('isPreconfigured()', () => {
|
|||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: new ConnectorTokenClient({
|
||||
unsecuredSavedObjectsClient: savedObjectsClientMock.create(),
|
||||
|
@ -2782,6 +3208,110 @@ describe('isPreconfigured()', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isSystemAction()', () => {
|
||||
test('should return true if the connector is a system connectors', () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
secrets: {
|
||||
test: 'test1',
|
||||
},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'test',
|
||||
config: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: new ConnectorTokenClient({
|
||||
unsecuredSavedObjectsClient: savedObjectsClientMock.create(),
|
||||
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
|
||||
logger,
|
||||
}),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
expect(actionsClient.isSystemAction('system-connector-.cases')).toEqual(true);
|
||||
});
|
||||
|
||||
test('should return false if connector id is not a system action', () => {
|
||||
actionsClient = new ActionsClient({
|
||||
logger,
|
||||
actionTypeRegistry,
|
||||
unsecuredSavedObjectsClient,
|
||||
scopedClusterClient,
|
||||
kibanaIndices,
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
bulkExecutionEnqueuer,
|
||||
request,
|
||||
authorization: authorization as unknown as ActionsAuthorization,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'testPreconfigured',
|
||||
actionTypeId: 'my-action-type',
|
||||
secrets: {
|
||||
test: 'test1',
|
||||
},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'test',
|
||||
config: {
|
||||
foo: 'bar',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
connectorTokenClient: new ConnectorTokenClient({
|
||||
unsecuredSavedObjectsClient: savedObjectsClientMock.create(),
|
||||
encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(),
|
||||
logger,
|
||||
}),
|
||||
getEventLogClient,
|
||||
});
|
||||
|
||||
expect(actionsClient.isSystemAction(uuidv4())).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getGlobalExecutionLogWithAuth()', () => {
|
||||
const opts: GetGlobalExecutionLogParams = {
|
||||
dateStart: '2023-01-09T08:55:56-08:00',
|
||||
|
|
|
@ -45,7 +45,7 @@ import {
|
|||
ActionResult,
|
||||
FindActionResult,
|
||||
RawAction,
|
||||
PreConfiguredAction,
|
||||
InMemoryConnector,
|
||||
ActionTypeExecutorResult,
|
||||
ConnectorTokenClientContract,
|
||||
} from './types';
|
||||
|
@ -115,7 +115,7 @@ interface ConstructorOptions {
|
|||
scopedClusterClient: IScopedClusterClient;
|
||||
actionTypeRegistry: ActionTypeRegistry;
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
preconfiguredActions: PreConfiguredAction[];
|
||||
inMemoryConnectors: InMemoryConnector[];
|
||||
actionExecutor: ActionExecutorContract;
|
||||
executionEnqueuer: ExecutionEnqueuer<void>;
|
||||
ephemeralExecutionEnqueuer: ExecutionEnqueuer<RunNowResult>;
|
||||
|
@ -139,7 +139,7 @@ export class ActionsClient {
|
|||
private readonly scopedClusterClient: IScopedClusterClient;
|
||||
private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract;
|
||||
private readonly actionTypeRegistry: ActionTypeRegistry;
|
||||
private readonly preconfiguredActions: PreConfiguredAction[];
|
||||
private readonly inMemoryConnectors: InMemoryConnector[];
|
||||
private readonly actionExecutor: ActionExecutorContract;
|
||||
private readonly request: KibanaRequest;
|
||||
private readonly authorization: ActionsAuthorization;
|
||||
|
@ -157,7 +157,7 @@ export class ActionsClient {
|
|||
kibanaIndices,
|
||||
scopedClusterClient,
|
||||
unsecuredSavedObjectsClient,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
actionExecutor,
|
||||
executionEnqueuer,
|
||||
ephemeralExecutionEnqueuer,
|
||||
|
@ -174,7 +174,7 @@ export class ActionsClient {
|
|||
this.unsecuredSavedObjectsClient = unsecuredSavedObjectsClient;
|
||||
this.scopedClusterClient = scopedClusterClient;
|
||||
this.kibanaIndices = kibanaIndices;
|
||||
this.preconfiguredActions = preconfiguredActions;
|
||||
this.inMemoryConnectors = inMemoryConnectors;
|
||||
this.actionExecutor = actionExecutor;
|
||||
this.executionEnqueuer = executionEnqueuer;
|
||||
this.ephemeralExecutionEnqueuer = ephemeralExecutionEnqueuer;
|
||||
|
@ -196,17 +196,6 @@ export class ActionsClient {
|
|||
}: CreateOptions): Promise<ActionResult> {
|
||||
const id = options?.id || SavedObjectsUtils.generateId();
|
||||
|
||||
if (this.preconfiguredActions.some((preconfiguredAction) => preconfiguredAction.id === id)) {
|
||||
throw Boom.badRequest(
|
||||
i18n.translate('xpack.actions.serverSideErrors.predefinedIdConnectorAlreadyExists', {
|
||||
defaultMessage: 'This {id} already exist in preconfigured action.',
|
||||
values: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await this.authorization.ensureAuthorized('create', actionTypeId);
|
||||
} catch (error) {
|
||||
|
@ -220,6 +209,33 @@ export class ActionsClient {
|
|||
throw error;
|
||||
}
|
||||
|
||||
const foundInMemoryConnector = this.inMemoryConnectors.find((connector) => connector.id === id);
|
||||
|
||||
if (
|
||||
this.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.actionTypeRegistry.get(actionTypeId);
|
||||
const configurationUtilities = this.actionTypeRegistry.getUtils();
|
||||
const validatedActionTypeConfig = validateConfig(actionType, config, {
|
||||
|
@ -272,13 +288,25 @@ export class ActionsClient {
|
|||
try {
|
||||
await this.authorization.ensureAuthorized('update');
|
||||
|
||||
if (
|
||||
this.preconfiguredActions.find((preconfiguredAction) => preconfiguredAction.id === id) !==
|
||||
undefined
|
||||
) {
|
||||
const foundInMemoryConnector = this.inMemoryConnectors.find(
|
||||
(connector) => connector.id === id
|
||||
);
|
||||
|
||||
if (foundInMemoryConnector?.isSystemAction) {
|
||||
throw Boom.badRequest(
|
||||
i18n.translate('xpack.actions.serverSideErrors.systemActionUpdateForbidden', {
|
||||
defaultMessage: 'System action {id} can not be updated.',
|
||||
values: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (foundInMemoryConnector?.isPreconfigured) {
|
||||
throw new PreconfiguredActionDisabledModificationError(
|
||||
i18n.translate('xpack.actions.serverSideErrors.predefinedActionUpdateDisabled', {
|
||||
defaultMessage: 'Preconfigured action {id} is not allowed to update.',
|
||||
defaultMessage: 'Preconfigured action {id} can not be updated.',
|
||||
values: {
|
||||
id,
|
||||
},
|
||||
|
@ -380,10 +408,9 @@ export class ActionsClient {
|
|||
throw error;
|
||||
}
|
||||
|
||||
const preconfiguredActionsList = this.preconfiguredActions.find(
|
||||
(preconfiguredAction) => preconfiguredAction.id === id
|
||||
);
|
||||
if (preconfiguredActionsList !== undefined) {
|
||||
const foundInMemoryConnector = this.inMemoryConnectors.find((connector) => connector.id === id);
|
||||
|
||||
if (foundInMemoryConnector !== undefined) {
|
||||
this.auditLogger?.log(
|
||||
connectorAuditEvent({
|
||||
action: ConnectorAuditAction.GET,
|
||||
|
@ -393,11 +420,11 @@ export class ActionsClient {
|
|||
|
||||
return {
|
||||
id,
|
||||
actionTypeId: preconfiguredActionsList.actionTypeId,
|
||||
name: preconfiguredActionsList.name,
|
||||
isPreconfigured: true,
|
||||
isSystemAction: false,
|
||||
isDeprecated: isConnectorDeprecated(preconfiguredActionsList),
|
||||
actionTypeId: foundInMemoryConnector.actionTypeId,
|
||||
name: foundInMemoryConnector.name,
|
||||
isPreconfigured: foundInMemoryConnector.isPreconfigured,
|
||||
isSystemAction: foundInMemoryConnector.isSystemAction,
|
||||
isDeprecated: isConnectorDeprecated(foundInMemoryConnector),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -423,7 +450,7 @@ export class ActionsClient {
|
|||
}
|
||||
|
||||
/**
|
||||
* Get all actions with preconfigured list
|
||||
* Get all actions with in-memory connectors
|
||||
*/
|
||||
public async getAll(): Promise<FindActionResult[]> {
|
||||
try {
|
||||
|
@ -458,20 +485,20 @@ export class ActionsClient {
|
|||
|
||||
const mergedResult = [
|
||||
...savedObjectsActions,
|
||||
...this.preconfiguredActions.map((preconfiguredAction) => ({
|
||||
id: preconfiguredAction.id,
|
||||
actionTypeId: preconfiguredAction.actionTypeId,
|
||||
name: preconfiguredAction.name,
|
||||
isPreconfigured: true,
|
||||
isDeprecated: isConnectorDeprecated(preconfiguredAction),
|
||||
isSystemAction: false,
|
||||
...this.inMemoryConnectors.map((inMemoryConnector) => ({
|
||||
id: inMemoryConnector.id,
|
||||
actionTypeId: inMemoryConnector.actionTypeId,
|
||||
name: inMemoryConnector.name,
|
||||
isPreconfigured: inMemoryConnector.isPreconfigured,
|
||||
isDeprecated: isConnectorDeprecated(inMemoryConnector),
|
||||
isSystemAction: inMemoryConnector.isSystemAction,
|
||||
})),
|
||||
].sort((a, b) => a.name.localeCompare(b.name));
|
||||
return await injectExtraFindData(this.kibanaIndices, this.scopedClusterClient, mergedResult);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bulk actions with preconfigured list
|
||||
* Get bulk actions with in-memory list
|
||||
*/
|
||||
public async getBulk(ids: string[]): Promise<ActionResult[]> {
|
||||
try {
|
||||
|
@ -490,17 +517,19 @@ export class ActionsClient {
|
|||
}
|
||||
|
||||
const actionResults = new Array<ActionResult>();
|
||||
|
||||
for (const actionId of ids) {
|
||||
const action = this.preconfiguredActions.find(
|
||||
(preconfiguredAction) => preconfiguredAction.id === actionId
|
||||
const action = this.inMemoryConnectors.find(
|
||||
(inMemoryConnector) => inMemoryConnector.id === actionId
|
||||
);
|
||||
|
||||
if (action !== undefined) {
|
||||
actionResults.push(action);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch action objects in bulk
|
||||
// Excluding preconfigured actions to avoid an not found error, which is already added
|
||||
// Excluding in-memory actions to avoid an not found error, which is already added
|
||||
const actionSavedObjectsIds = [
|
||||
...new Set(
|
||||
ids.filter(
|
||||
|
@ -531,6 +560,7 @@ export class ActionsClient {
|
|||
}
|
||||
actionResults.push(actionFromSavedObject(action, isConnectorDeprecated(action.attributes)));
|
||||
}
|
||||
|
||||
return actionResults;
|
||||
}
|
||||
|
||||
|
@ -632,10 +662,22 @@ export class ActionsClient {
|
|||
try {
|
||||
await this.authorization.ensureAuthorized('delete');
|
||||
|
||||
if (
|
||||
this.preconfiguredActions.find((preconfiguredAction) => preconfiguredAction.id === id) !==
|
||||
undefined
|
||||
) {
|
||||
const foundInMemoryConnector = this.inMemoryConnectors.find(
|
||||
(connector) => connector.id === id
|
||||
);
|
||||
|
||||
if (foundInMemoryConnector?.isSystemAction) {
|
||||
throw Boom.badRequest(
|
||||
i18n.translate('xpack.actions.serverSideErrors.systemActionDeletionForbidden', {
|
||||
defaultMessage: 'System action {id} is not allowed to delete.',
|
||||
values: {
|
||||
id,
|
||||
},
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (foundInMemoryConnector?.isPreconfigured) {
|
||||
throw new PreconfiguredActionDisabledModificationError(
|
||||
i18n.translate('xpack.actions.serverSideErrors.predefinedActionDeleteDisabled', {
|
||||
defaultMessage: 'Preconfigured action {id} is not allowed to delete.',
|
||||
|
@ -765,7 +807,15 @@ export class ActionsClient {
|
|||
}
|
||||
|
||||
public isPreconfigured(connectorId: string): boolean {
|
||||
return !!this.preconfiguredActions.find((preconfigured) => preconfigured.id === connectorId);
|
||||
return !!this.inMemoryConnectors.find(
|
||||
(connector) => connector.isPreconfigured && connector.id === connectorId
|
||||
);
|
||||
}
|
||||
|
||||
public isSystemAction(connectorId: string): boolean {
|
||||
return !!this.inMemoryConnectors.find(
|
||||
(connector) => connector.isSystemAction && connector.id === connectorId
|
||||
);
|
||||
}
|
||||
|
||||
public async getGlobalExecutionLogWithAuth({
|
||||
|
|
|
@ -32,7 +32,7 @@ describe('execute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
|
@ -103,7 +103,7 @@ describe('execute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
|
@ -176,7 +176,7 @@ describe('execute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
|
@ -247,7 +247,7 @@ describe('execute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: 'mock-action-preconfigured',
|
||||
|
@ -322,12 +322,95 @@ describe('execute()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('schedules the action with all given parameters with a system action', async () => {
|
||||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
const source = { type: 'alert', id: uuidv4() };
|
||||
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: '.cases',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {},
|
||||
references: [],
|
||||
});
|
||||
|
||||
await executeFn(savedObjectsClient, {
|
||||
id: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
spaceId: 'default',
|
||||
executionId: 'system-connector-.casesabc',
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
});
|
||||
|
||||
expect(mockTaskManager.schedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.cases",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(savedObjectsClient.get).not.toHaveBeenCalled();
|
||||
expect(savedObjectsClient.create).toHaveBeenCalledWith(
|
||||
'action_task_params',
|
||||
{
|
||||
actionId: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
executionId: 'system-connector-.casesabc',
|
||||
source: 'SAVED_OBJECT',
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
},
|
||||
{
|
||||
references: [
|
||||
{
|
||||
id: source.id,
|
||||
name: 'source',
|
||||
type: source.type,
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('schedules the action with all given parameters with a preconfigured action and relatedSavedObjects', async () => {
|
||||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: 'mock-action-preconfigured',
|
||||
|
@ -423,12 +506,113 @@ describe('execute()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('schedules the action with all given parameters with a system action and relatedSavedObjects', async () => {
|
||||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
const source = { type: 'alert', id: uuidv4() };
|
||||
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: '.cases',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {},
|
||||
references: [],
|
||||
});
|
||||
await executeFn(savedObjectsClient, {
|
||||
id: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
spaceId: 'default',
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
executionId: 'system-connector-.casesabc',
|
||||
relatedSavedObjects: [
|
||||
{
|
||||
id: 'some-id',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
typeId: 'some-typeId',
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(mockTaskManager.schedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.schedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.cases",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(savedObjectsClient.get).not.toHaveBeenCalled();
|
||||
expect(savedObjectsClient.create).toHaveBeenCalledWith(
|
||||
'action_task_params',
|
||||
{
|
||||
actionId: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
executionId: 'system-connector-.casesabc',
|
||||
source: 'SAVED_OBJECT',
|
||||
relatedSavedObjects: [
|
||||
{
|
||||
id: 'related_some-type_0',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
typeId: 'some-typeId',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
references: [
|
||||
{
|
||||
id: source.id,
|
||||
name: 'source',
|
||||
type: source.type,
|
||||
},
|
||||
{
|
||||
id: 'some-id',
|
||||
name: 'related_some-type_0',
|
||||
type: 'some-type',
|
||||
},
|
||||
],
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
test('throws when passing isESOCanEncrypt with false as a value', async () => {
|
||||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: false,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
await expect(
|
||||
executeFn(savedObjectsClient, {
|
||||
|
@ -449,7 +633,7 @@ describe('execute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
|
@ -481,7 +665,7 @@ describe('execute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
mockedActionTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
|
@ -513,7 +697,7 @@ describe('execute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: 'mock-action',
|
||||
config: {},
|
||||
|
@ -553,6 +737,53 @@ describe('execute()', () => {
|
|||
|
||||
expect(mockedActionTypeRegistry.ensureActionTypeEnabled).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should skip ensure action type if action type is system action and license is valid', async () => {
|
||||
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const executeFn = createExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
mockedActionTypeRegistry.isActionExecutable.mockImplementation(() => true);
|
||||
savedObjectsClient.get.mockResolvedValueOnce({
|
||||
id: '123',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: '.cases',
|
||||
},
|
||||
references: [],
|
||||
});
|
||||
savedObjectsClient.create.mockResolvedValueOnce({
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {},
|
||||
references: [],
|
||||
});
|
||||
|
||||
await executeFn(savedObjectsClient, {
|
||||
id: 'system-connector-.case',
|
||||
params: { baz: false },
|
||||
spaceId: 'default',
|
||||
executionId: 'system-connector-.caseabc',
|
||||
apiKey: null,
|
||||
source: asHttpRequestExecutionSource(request),
|
||||
});
|
||||
|
||||
expect(mockedActionTypeRegistry.ensureActionTypeEnabled).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('bulkExecute()', () => {
|
||||
|
@ -562,7 +793,7 @@ describe('bulkExecute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
|
@ -650,7 +881,7 @@ describe('bulkExecute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
|
@ -741,7 +972,7 @@ describe('bulkExecute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
|
@ -825,7 +1056,7 @@ describe('bulkExecute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: 'mock-action-preconfigured',
|
||||
|
@ -917,12 +1148,109 @@ describe('bulkExecute()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('schedules the action with all given parameters with a system action', async () => {
|
||||
const executeFn = createBulkExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
const source = { type: 'alert', id: uuidv4() };
|
||||
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '123',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: '.cases',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: 'system-connector-.cases',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(savedObjectsClient, [
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
spaceId: 'default',
|
||||
executionId: 'system-connector-.casesabc',
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.cases",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
expect(savedObjectsClient.get).not.toHaveBeenCalled();
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
executionId: 'system-connector-.casesabc',
|
||||
source: 'SAVED_OBJECT',
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: source.id,
|
||||
name: 'source',
|
||||
type: source.type,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{ refresh: false }
|
||||
);
|
||||
});
|
||||
|
||||
test('schedules the action with all given parameters with a preconfigured action and relatedSavedObjects', async () => {
|
||||
const executeFn = createBulkExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: 'mock-action-preconfigured',
|
||||
|
@ -1035,12 +1363,130 @@ describe('bulkExecute()', () => {
|
|||
);
|
||||
});
|
||||
|
||||
test('schedules the action with all given parameters with a system action and relatedSavedObjects', async () => {
|
||||
const executeFn = createBulkExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
isESOCanEncrypt: true,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
const source = { type: 'alert', id: uuidv4() };
|
||||
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '123',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: '.cases',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: 'system-connector-.cases',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(savedObjectsClient, [
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
spaceId: 'default',
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
executionId: 'system-connector-.casesabc',
|
||||
relatedSavedObjects: [
|
||||
{
|
||||
id: 'some-id',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
typeId: 'some-typeId',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.cases",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
expect(savedObjectsClient.get).not.toHaveBeenCalled();
|
||||
expect(savedObjectsClient.bulkCreate).toHaveBeenCalledWith(
|
||||
[
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: 'system-connector-.cases',
|
||||
params: { baz: false },
|
||||
apiKey: Buffer.from('system-connector-.cases:abc').toString('base64'),
|
||||
executionId: 'system-connector-.casesabc',
|
||||
source: 'SAVED_OBJECT',
|
||||
relatedSavedObjects: [
|
||||
{
|
||||
id: 'related_some-type_0',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
typeId: 'some-typeId',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: source.id,
|
||||
name: 'source',
|
||||
type: source.type,
|
||||
},
|
||||
{
|
||||
id: 'some-id',
|
||||
name: 'related_some-type_0',
|
||||
type: 'some-type',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
{ refresh: false }
|
||||
);
|
||||
});
|
||||
|
||||
test('throws when passing isESOCanEncrypt with false as a value', async () => {
|
||||
const executeFn = createBulkExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: false,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
await expect(
|
||||
executeFn(savedObjectsClient, [
|
||||
|
@ -1063,7 +1509,7 @@ describe('bulkExecute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
|
@ -1101,7 +1547,7 @@ describe('bulkExecute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
mockedActionTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
|
@ -1139,7 +1585,7 @@ describe('bulkExecute()', () => {
|
|||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: 'mock-action',
|
||||
config: {},
|
||||
|
@ -1191,4 +1637,63 @@ describe('bulkExecute()', () => {
|
|||
|
||||
expect(mockedActionTypeRegistry.ensureActionTypeEnabled).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('should skip ensure action type if action type is system action and license is valid', async () => {
|
||||
const mockedActionTypeRegistry = actionTypeRegistryMock.create();
|
||||
const executeFn = createBulkExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
isESOCanEncrypt: true,
|
||||
actionTypeRegistry: mockedActionTypeRegistry,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
mockedActionTypeRegistry.isActionExecutable.mockImplementation(() => true);
|
||||
savedObjectsClient.bulkGet.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '123',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
actionTypeId: '.cases',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
savedObjectsClient.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
await executeFn(savedObjectsClient, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
spaceId: 'default',
|
||||
executionId: '123abc',
|
||||
apiKey: null,
|
||||
source: asHttpRequestExecutionSource(request),
|
||||
},
|
||||
]);
|
||||
|
||||
expect(mockedActionTypeRegistry.ensureActionTypeEnabled).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import { RunNowResult, TaskManagerStartContract } from '@kbn/task-manager-plugin
|
|||
import {
|
||||
RawAction,
|
||||
ActionTypeRegistryContract,
|
||||
PreConfiguredAction,
|
||||
InMemoryConnector,
|
||||
ActionTaskExecutorParams,
|
||||
} from './types';
|
||||
import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects';
|
||||
|
@ -21,7 +21,7 @@ interface CreateExecuteFunctionOptions {
|
|||
taskManager: TaskManagerStartContract;
|
||||
isESOCanEncrypt: boolean;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
preconfiguredActions: PreConfiguredAction[];
|
||||
inMemoryConnectors: InMemoryConnector[];
|
||||
}
|
||||
|
||||
export interface ExecuteOptions
|
||||
|
@ -39,8 +39,8 @@ interface ActionTaskParams
|
|||
}
|
||||
|
||||
export interface GetConnectorsResult {
|
||||
connector: PreConfiguredAction | RawAction;
|
||||
isPreconfigured: boolean;
|
||||
connector: InMemoryConnector | RawAction;
|
||||
isInMemory: boolean;
|
||||
id: string;
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ export function createExecutionEnqueuerFunction({
|
|||
taskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
}: CreateExecuteFunctionOptions): ExecutionEnqueuer<void> {
|
||||
return async function execute(
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
||||
|
@ -79,9 +79,9 @@ export function createExecutionEnqueuerFunction({
|
|||
);
|
||||
}
|
||||
|
||||
const { action, isPreconfigured } = await getAction(
|
||||
const { action, isInMemory } = await getAction(
|
||||
unsecuredSavedObjectsClient,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
id
|
||||
);
|
||||
validateCanActionBeUsed(action);
|
||||
|
@ -94,7 +94,7 @@ export function createExecutionEnqueuerFunction({
|
|||
// Get saved object references from action ID and relatedSavedObjects
|
||||
const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences(
|
||||
id,
|
||||
isPreconfigured,
|
||||
isInMemory,
|
||||
relatedSavedObjects
|
||||
);
|
||||
const executionSourceReference = executionSourceAsSavedObjectReferences(source);
|
||||
|
@ -139,7 +139,7 @@ export function createBulkExecutionEnqueuerFunction({
|
|||
taskManager,
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
}: CreateExecuteFunctionOptions): BulkExecutionEnqueuer<void> {
|
||||
return async function execute(
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
||||
|
@ -153,15 +153,16 @@ export function createBulkExecutionEnqueuerFunction({
|
|||
|
||||
const actionTypeIds: Record<string, string> = {};
|
||||
const spaceIds: Record<string, string> = {};
|
||||
const connectorIsPreconfigured: Record<string, boolean> = {};
|
||||
const connectorIsInMemory: Record<string, boolean> = {};
|
||||
const connectorIds = [...new Set(actionsToExecute.map((action) => action.id))];
|
||||
const connectors = await getConnectors(
|
||||
unsecuredSavedObjectsClient,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
connectorIds
|
||||
);
|
||||
|
||||
connectors.forEach((c) => {
|
||||
const { id, connector, isPreconfigured } = c;
|
||||
const { id, connector, isInMemory } = c;
|
||||
validateCanActionBeUsed(connector);
|
||||
|
||||
const { actionTypeId } = connector;
|
||||
|
@ -170,16 +171,17 @@ export function createBulkExecutionEnqueuerFunction({
|
|||
}
|
||||
|
||||
actionTypeIds[id] = actionTypeId;
|
||||
connectorIsPreconfigured[id] = isPreconfigured;
|
||||
connectorIsInMemory[id] = isInMemory;
|
||||
});
|
||||
|
||||
const actions = actionsToExecute.map((actionToExecute) => {
|
||||
// Get saved object references from action ID and relatedSavedObjects
|
||||
const { references, relatedSavedObjectWithRefs } = extractSavedObjectReferences(
|
||||
actionToExecute.id,
|
||||
connectorIsPreconfigured[actionToExecute.id],
|
||||
connectorIsInMemory[actionToExecute.id],
|
||||
actionToExecute.relatedSavedObjects
|
||||
);
|
||||
|
||||
const executionSourceReference = executionSourceAsSavedObjectReferences(
|
||||
actionToExecute.source
|
||||
);
|
||||
|
@ -229,13 +231,13 @@ export function createBulkExecutionEnqueuerFunction({
|
|||
export function createEphemeralExecutionEnqueuerFunction({
|
||||
taskManager,
|
||||
actionTypeRegistry,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
}: CreateExecuteFunctionOptions): ExecutionEnqueuer<RunNowResult> {
|
||||
return async function execute(
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
||||
{ id, params, spaceId, source, consumer, apiKey, executionId }: ExecuteOptions
|
||||
): Promise<RunNowResult> {
|
||||
const { action } = await getAction(unsecuredSavedObjectsClient, preconfiguredActions, id);
|
||||
const { action } = await getAction(unsecuredSavedObjectsClient, inMemoryConnectors, id);
|
||||
validateCanActionBeUsed(action);
|
||||
|
||||
const { actionTypeId } = action;
|
||||
|
@ -266,7 +268,7 @@ export function createEphemeralExecutionEnqueuerFunction({
|
|||
};
|
||||
}
|
||||
|
||||
function validateCanActionBeUsed(action: PreConfiguredAction | RawAction) {
|
||||
function validateCanActionBeUsed(action: InMemoryConnector | RawAction) {
|
||||
const { name, isMissingSecrets } = action;
|
||||
if (isMissingSecrets) {
|
||||
throw new Error(
|
||||
|
@ -290,30 +292,32 @@ function executionSourceAsSavedObjectReferences(executionSource: ActionExecutorO
|
|||
|
||||
async function getAction(
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
||||
preconfiguredActions: PreConfiguredAction[],
|
||||
inMemoryConnectors: InMemoryConnector[],
|
||||
actionId: string
|
||||
): Promise<{ action: PreConfiguredAction | RawAction; isPreconfigured: boolean }> {
|
||||
const pcAction = preconfiguredActions.find((action) => action.id === actionId);
|
||||
if (pcAction) {
|
||||
return { action: pcAction, isPreconfigured: true };
|
||||
): Promise<{ action: InMemoryConnector | RawAction; isInMemory: boolean }> {
|
||||
const inMemoryAction = inMemoryConnectors.find((action) => action.id === actionId);
|
||||
|
||||
if (inMemoryAction) {
|
||||
return { action: inMemoryAction, isInMemory: true };
|
||||
}
|
||||
|
||||
const { attributes } = await unsecuredSavedObjectsClient.get<RawAction>('action', actionId);
|
||||
return { action: attributes, isPreconfigured: false };
|
||||
return { action: attributes, isInMemory: false };
|
||||
}
|
||||
|
||||
async function getConnectors(
|
||||
unsecuredSavedObjectsClient: SavedObjectsClientContract,
|
||||
preconfiguredConnectors: PreConfiguredAction[],
|
||||
inMemoryConnectors: InMemoryConnector[],
|
||||
connectorIds: string[]
|
||||
): Promise<GetConnectorsResult[]> {
|
||||
const result: GetConnectorsResult[] = [];
|
||||
|
||||
const connectorIdsToFetch = [];
|
||||
for (const connectorId of connectorIds) {
|
||||
const pcConnector = preconfiguredConnectors.find((connector) => connector.id === connectorId);
|
||||
const pcConnector = inMemoryConnectors.find((connector) => connector.id === connectorId);
|
||||
|
||||
if (pcConnector) {
|
||||
result.push({ connector: pcConnector, isPreconfigured: true, id: connectorId });
|
||||
result.push({ connector: pcConnector, isInMemory: true, id: connectorId });
|
||||
} else {
|
||||
connectorIdsToFetch.push(connectorId);
|
||||
}
|
||||
|
@ -330,7 +334,7 @@ async function getConnectors(
|
|||
for (const item of bulkGetResult.saved_objects) {
|
||||
if (item.error) throw item.error;
|
||||
result.push({
|
||||
isPreconfigured: false,
|
||||
isInMemory: false,
|
||||
connector: item.attributes,
|
||||
id: item.id,
|
||||
});
|
||||
|
|
49
x-pack/plugins/actions/server/create_system_actions.test.ts
Normal file
49
x-pack/plugins/actions/server/create_system_actions.test.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 { createSystemConnectors } from './create_system_actions';
|
||||
|
||||
const actionTypes = [
|
||||
{
|
||||
id: 'action-type',
|
||||
name: 'My action type',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: 'system-action-type-2',
|
||||
name: 'My system action type',
|
||||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: true,
|
||||
},
|
||||
];
|
||||
|
||||
describe('createSystemConnectors', () => {
|
||||
it('creates the system actions correctly', () => {
|
||||
expect(createSystemConnectors(actionTypes)).toEqual([
|
||||
{
|
||||
id: 'system-connector-system-action-type-2',
|
||||
actionTypeId: 'system-action-type-2',
|
||||
name: 'System action: system-action-type-2',
|
||||
secrets: {},
|
||||
config: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
27
x-pack/plugins/actions/server/create_system_actions.ts
Normal file
27
x-pack/plugins/actions/server/create_system_actions.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { ActionType } from '../common';
|
||||
import { InMemoryConnector } from './types';
|
||||
|
||||
export const createSystemConnectors = (actionTypes: ActionType[]): InMemoryConnector[] => {
|
||||
const systemActionTypes = actionTypes.filter((actionType) => actionType.isSystemActionType);
|
||||
|
||||
const systemConnectors: InMemoryConnector[] = systemActionTypes.map((systemActionType) => ({
|
||||
id: `system-connector-${systemActionType.id}`,
|
||||
actionTypeId: systemActionType.id,
|
||||
name: `System action: ${systemActionType.id}`,
|
||||
isMissingSecrets: false,
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
}));
|
||||
|
||||
return systemConnectors;
|
||||
};
|
|
@ -21,255 +21,217 @@ const internalSavedObjectsRepository = savedObjectsRepositoryMock.create();
|
|||
beforeEach(() => jest.resetAllMocks());
|
||||
|
||||
describe('bulkExecute()', () => {
|
||||
test('schedules the actions with all given parameters with a preconfigured connector', async () => {
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "345",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: false },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: true },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('schedules the actions with all given parameters with a preconfigured connector and source specified', async () => {
|
||||
const sourceUuid = uuidv4();
|
||||
const source = { type: 'alert', id: sourceUuid };
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: sourceUuid,
|
||||
name: 'source',
|
||||
type: 'alert',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "345",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: false },
|
||||
apiKey: null,
|
||||
source: 'SAVED_OBJECT',
|
||||
},
|
||||
references: [
|
||||
test.each([
|
||||
[true, false],
|
||||
[false, true],
|
||||
])(
|
||||
'schedules the actions with all given parameters with an in-memory connector: isPreconfigured: %s, isSystemAction: %s',
|
||||
async (isPreconfigured, isSystemAction) => {
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: sourceUuid,
|
||||
name: 'source',
|
||||
type: 'alert',
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured,
|
||||
isDeprecated: false,
|
||||
isSystemAction,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: true },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test('schedules the actions with all given parameters with a preconfigured connector and relatedSavedObjects specified', async () => {
|
||||
const sourceUuid = uuidv4();
|
||||
const source = { type: 'alert', id: sourceUuid };
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredConnectors: [
|
||||
internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "345",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: false },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: true },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
[true, false],
|
||||
[false, true],
|
||||
])(
|
||||
'schedules the actions with all given parameters with an in-memory connector and source specified: isPreconfigured: %s, isSystemAction: %s',
|
||||
async (isPreconfigured, isSystemAction) => {
|
||||
const sourceUuid = uuidv4();
|
||||
const source = { type: 'alert', id: sourceUuid };
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured,
|
||||
isDeprecated: false,
|
||||
isSystemAction,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: sourceUuid,
|
||||
name: 'source',
|
||||
type: 'alert',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "345",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: false },
|
||||
apiKey: null,
|
||||
source: 'SAVED_OBJECT',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
|
@ -280,10 +242,156 @@ describe('bulkExecute()', () => {
|
|||
],
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: true },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
},
|
||||
references: [],
|
||||
},
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
[true, false],
|
||||
[false, true],
|
||||
])(
|
||||
'schedules the actions with all given parameters with an in-memory connector and relatedSavedObjects specified: isPreconfigured: %s, isSystemAction: %s',
|
||||
async (isPreconfigured, isSystemAction) => {
|
||||
const sourceUuid = uuidv4();
|
||||
const source = { type: 'alert', id: sourceUuid };
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured,
|
||||
isDeprecated: false,
|
||||
isSystemAction,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
internalSavedObjectsRepository.bulkCreate.mockResolvedValueOnce({
|
||||
saved_objects: [
|
||||
{
|
||||
id: '234',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: sourceUuid,
|
||||
name: 'source',
|
||||
type: 'alert',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: '345',
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: 'some-id',
|
||||
name: 'related_some-type_0',
|
||||
type: 'some-type',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
relatedSavedObjects: [
|
||||
{
|
||||
id: 'some-id',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "345",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: false },
|
||||
apiKey: null,
|
||||
source: 'SAVED_OBJECT',
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: sourceUuid,
|
||||
name: 'source',
|
||||
type: 'alert',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: true },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
relatedSavedObjects: [
|
||||
{
|
||||
id: 'related_some-type_0',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
|
@ -293,215 +401,143 @@ describe('bulkExecute()', () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
await executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asSavedObjectExecutionSource(source),
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
relatedSavedObjects: [
|
||||
]);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
[true, false],
|
||||
[false, true],
|
||||
])(
|
||||
'throws when scheduling action using non in-memory connector: isPreconfigured: %s, isSystemAction: %s',
|
||||
async (isPreconfigured, isSystemAction) => {
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'some-id',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured,
|
||||
isDeprecated: false,
|
||||
isSystemAction,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
expect(mockTaskManager.bulkSchedule).toHaveBeenCalledTimes(1);
|
||||
expect(mockTaskManager.bulkSchedule.mock.calls[0]).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "234",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
Object {
|
||||
"params": Object {
|
||||
"actionTaskParamsId": "345",
|
||||
"spaceId": "default",
|
||||
},
|
||||
"scope": Array [
|
||||
"actions",
|
||||
],
|
||||
"state": Object {},
|
||||
"taskType": "actions:.email",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
|
||||
expect(internalSavedObjectsRepository.bulkCreate).toHaveBeenCalledWith([
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: false },
|
||||
apiKey: null,
|
||||
source: 'SAVED_OBJECT',
|
||||
},
|
||||
references: [
|
||||
});
|
||||
await expect(
|
||||
executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: sourceUuid,
|
||||
name: 'source',
|
||||
type: 'alert',
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'action_task_params',
|
||||
attributes: {
|
||||
actionId: '123',
|
||||
params: { baz: true },
|
||||
apiKey: null,
|
||||
source: 'NOTIFICATION',
|
||||
relatedSavedObjects: [
|
||||
{
|
||||
id: 'related_some-type_0',
|
||||
namespace: 'some-namespace',
|
||||
type: 'some-type',
|
||||
},
|
||||
],
|
||||
},
|
||||
references: [
|
||||
{
|
||||
id: 'some-id',
|
||||
name: 'related_some-type_0',
|
||||
type: 'some-type',
|
||||
id: 'not-preconfigured',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
])
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"not-preconfigured are not in-memory connectors and can't be scheduled for unsecured actions execution"`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
test.each([
|
||||
[true, false],
|
||||
[false, true],
|
||||
])(
|
||||
'throws when connector type is not enabled: isPreconfigured: %s, isSystemAction: %s',
|
||||
async (isPreconfigured, isSystemAction) => {
|
||||
const mockedConnectorTypeRegistry = actionTypeRegistryMock.create();
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: mockedConnectorTypeRegistry,
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured,
|
||||
isDeprecated: false,
|
||||
isSystemAction,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
mockedConnectorTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
});
|
||||
|
||||
test('throws when scheduling action using non preconfigured connector', async () => {
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
await expect(
|
||||
executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
{
|
||||
id: 'not-preconfigured',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
])
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"not-preconfigured are not preconfigured connectors and can't be scheduled for unsecured actions execution"`
|
||||
);
|
||||
});
|
||||
await expect(
|
||||
executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
])
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
}
|
||||
);
|
||||
|
||||
test('throws when connector type is not enabled', async () => {
|
||||
const mockedConnectorTypeRegistry = actionTypeRegistryMock.create();
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: mockedConnectorTypeRegistry,
|
||||
preconfiguredConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
mockedConnectorTypeRegistry.ensureActionTypeEnabled.mockImplementation(() => {
|
||||
throw new Error('Fail');
|
||||
});
|
||||
|
||||
await expect(
|
||||
executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
])
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(`"Fail"`);
|
||||
});
|
||||
|
||||
test('throws when scheduling action using non allow-listed preconfigured connector', async () => {
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
preconfiguredConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
actionTypeId: 'not-in-allowlist',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
await expect(
|
||||
executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
])
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"not-in-allowlist actions cannot be scheduled for unsecured actions execution"`
|
||||
);
|
||||
});
|
||||
test.each([
|
||||
[true, false],
|
||||
[false, true],
|
||||
])(
|
||||
'throws when scheduling action using non allow-listed in-memory connector: isPreconfigured: %s, isSystemAction: %s',
|
||||
async (isPreconfigured, isSystemAction) => {
|
||||
const executeFn = createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: mockTaskManager,
|
||||
connectorTypeRegistry: actionTypeRegistryMock.create(),
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.email',
|
||||
config: {},
|
||||
isPreconfigured,
|
||||
isDeprecated: false,
|
||||
isSystemAction,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
actionTypeId: 'not-in-allowlist',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
name: 'x',
|
||||
secrets: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
await expect(
|
||||
executeFn(internalSavedObjectsRepository, [
|
||||
{
|
||||
id: '123',
|
||||
params: { baz: false },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
params: { baz: true },
|
||||
source: asNotificationExecutionSource({ connectorId: 'abc', requesterId: 'foo' }),
|
||||
},
|
||||
])
|
||||
).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||
`"not-in-allowlist actions cannot be scheduled for unsecured actions execution"`
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -9,7 +9,7 @@ import { ISavedObjectsRepository, SavedObjectsBulkResponse } from '@kbn/core/ser
|
|||
import { TaskManagerStartContract } from '@kbn/task-manager-plugin/server';
|
||||
import {
|
||||
ActionTypeRegistryContract as ConnectorTypeRegistryContract,
|
||||
PreConfiguredAction as PreconfiguredConnector,
|
||||
InMemoryConnector,
|
||||
} from './types';
|
||||
import { ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE } from './constants/saved_objects';
|
||||
import { ExecuteOptions as ActionExecutorOptions } from './lib/action_executor';
|
||||
|
@ -21,7 +21,7 @@ const ALLOWED_CONNECTOR_TYPE_IDS = ['.email'];
|
|||
interface CreateBulkUnsecuredExecuteFunctionOptions {
|
||||
taskManager: TaskManagerStartContract;
|
||||
connectorTypeRegistry: ConnectorTypeRegistryContract;
|
||||
preconfiguredConnectors: PreconfiguredConnector[];
|
||||
inMemoryConnectors: InMemoryConnector[];
|
||||
}
|
||||
|
||||
export interface ExecuteOptions
|
||||
|
@ -42,7 +42,7 @@ export type BulkUnsecuredExecutionEnqueuer<T> = (
|
|||
export function createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager,
|
||||
connectorTypeRegistry,
|
||||
preconfiguredConnectors,
|
||||
inMemoryConnectors,
|
||||
}: CreateBulkUnsecuredExecuteFunctionOptions): BulkUnsecuredExecutionEnqueuer<void> {
|
||||
return async function execute(
|
||||
internalSavedObjectsRepository: ISavedObjectsRepository,
|
||||
|
@ -51,24 +51,23 @@ export function createBulkUnsecuredExecutionEnqueuerFunction({
|
|||
const connectorTypeIds: Record<string, string> = {};
|
||||
const connectorIds = [...new Set(actionsToExecute.map((action) => action.id))];
|
||||
|
||||
const notPreconfiguredConnectors = connectorIds.filter(
|
||||
(connectorId) =>
|
||||
preconfiguredConnectors.find((connector) => connector.id === connectorId) == null
|
||||
const notInMemoryConnectors = connectorIds.filter(
|
||||
(connectorId) => inMemoryConnectors.find((connector) => connector.id === connectorId) == null
|
||||
);
|
||||
|
||||
if (notPreconfiguredConnectors.length > 0) {
|
||||
if (notInMemoryConnectors.length > 0) {
|
||||
throw new Error(
|
||||
`${notPreconfiguredConnectors.join(
|
||||
`${notInMemoryConnectors.join(
|
||||
','
|
||||
)} are not preconfigured connectors and can't be scheduled for unsecured actions execution`
|
||||
)} are not in-memory connectors and can't be scheduled for unsecured actions execution`
|
||||
);
|
||||
}
|
||||
|
||||
const connectors: PreconfiguredConnector[] = connectorIds
|
||||
const connectors: InMemoryConnector[] = connectorIds
|
||||
.map((connectorId) =>
|
||||
preconfiguredConnectors.find((pConnector) => pConnector.id === connectorId)
|
||||
inMemoryConnectors.find((inMemoryConnector) => inMemoryConnector.id === connectorId)
|
||||
)
|
||||
.filter(Boolean) as PreconfiguredConnector[];
|
||||
.filter(Boolean) as InMemoryConnector[];
|
||||
|
||||
connectors.forEach((connector) => {
|
||||
const { id, actionTypeId } = connector;
|
||||
|
|
|
@ -22,7 +22,7 @@ export type {
|
|||
ActionResult,
|
||||
ActionTypeExecutorOptions,
|
||||
ActionType,
|
||||
PreConfiguredAction,
|
||||
InMemoryConnector,
|
||||
ActionsApiRequestHandlerContext,
|
||||
FindActionResult,
|
||||
} from './types';
|
||||
|
|
|
@ -51,7 +51,7 @@ actionExecutor.initialize({
|
|||
actionTypeRegistry,
|
||||
encryptedSavedObjectsClient,
|
||||
eventLogger,
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'preconfigured',
|
||||
name: 'Preconfigured',
|
||||
|
@ -66,6 +66,16 @@ actionExecutor.initialize({
|
|||
isDeprecated: false,
|
||||
isSystemAction: false,
|
||||
},
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
|
@ -662,6 +672,133 @@ test('successfully executes with preconfigured connector', async () => {
|
|||
`);
|
||||
});
|
||||
|
||||
test('successfully executes with system connector', async () => {
|
||||
const actionType: jest.Mocked<ActionType> = {
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.any() },
|
||||
secrets: { schema: schema.any() },
|
||||
params: { schema: schema.any() },
|
||||
},
|
||||
executor: jest.fn(),
|
||||
};
|
||||
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
await actionExecutor.execute({ ...executeParams, actionId: 'system-connector-.cases' });
|
||||
|
||||
expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).not.toHaveBeenCalled();
|
||||
|
||||
expect(actionTypeRegistry.get).toHaveBeenCalledWith('.cases');
|
||||
expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith(
|
||||
'system-connector-.cases',
|
||||
'.cases',
|
||||
{
|
||||
notifyUsage: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(actionType.executor).toHaveBeenCalledWith({
|
||||
actionId: 'system-connector-.cases',
|
||||
services: expect.anything(),
|
||||
config: {},
|
||||
secrets: {},
|
||||
params: { foo: true },
|
||||
logger: loggerMock,
|
||||
});
|
||||
|
||||
expect(loggerMock.debug).toBeCalledWith(
|
||||
'executing action .cases:system-connector-.cases: System action: .cases'
|
||||
);
|
||||
expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"event": Object {
|
||||
"action": "execute-start",
|
||||
"kind": "action",
|
||||
},
|
||||
"kibana": Object {
|
||||
"action": Object {
|
||||
"execution": Object {
|
||||
"uuid": "2",
|
||||
},
|
||||
"id": "system-connector-.cases",
|
||||
"name": "System action: .cases",
|
||||
},
|
||||
"alert": Object {
|
||||
"rule": Object {
|
||||
"execution": Object {
|
||||
"uuid": "123abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
"saved_objects": Array [
|
||||
Object {
|
||||
"id": "system-connector-.cases",
|
||||
"namespace": "some-namespace",
|
||||
"rel": "primary",
|
||||
"space_agnostic": true,
|
||||
"type": "action",
|
||||
"type_id": ".cases",
|
||||
},
|
||||
],
|
||||
"space_ids": Array [
|
||||
"some-namespace",
|
||||
],
|
||||
},
|
||||
"message": "action started: .cases:system-connector-.cases: System action: .cases",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"event": Object {
|
||||
"action": "execute",
|
||||
"kind": "action",
|
||||
"outcome": "success",
|
||||
},
|
||||
"kibana": Object {
|
||||
"action": Object {
|
||||
"execution": Object {
|
||||
"uuid": "2",
|
||||
},
|
||||
"id": "system-connector-.cases",
|
||||
"name": "System action: .cases",
|
||||
},
|
||||
"alert": Object {
|
||||
"rule": Object {
|
||||
"execution": Object {
|
||||
"uuid": "123abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
"saved_objects": Array [
|
||||
Object {
|
||||
"id": "system-connector-.cases",
|
||||
"namespace": "some-namespace",
|
||||
"rel": "primary",
|
||||
"space_agnostic": true,
|
||||
"type": "action",
|
||||
"type_id": ".cases",
|
||||
},
|
||||
],
|
||||
"space_ids": Array [
|
||||
"some-namespace",
|
||||
],
|
||||
},
|
||||
"message": "action executed: .cases:system-connector-.cases: System action: .cases",
|
||||
"user": Object {
|
||||
"id": "123",
|
||||
"name": "coolguy",
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('successfully executes as a task', async () => {
|
||||
const actionType: jest.Mocked<ActionType> = {
|
||||
id: 'test',
|
||||
|
@ -949,6 +1086,51 @@ test('should not throws an error if actionType is preconfigured', async () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('should not throws an error if actionType is system action', async () => {
|
||||
const actionType: jest.Mocked<ActionType> = {
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.any() },
|
||||
secrets: { schema: schema.any() },
|
||||
params: { schema: schema.any() },
|
||||
},
|
||||
executor: jest.fn(),
|
||||
};
|
||||
|
||||
const actionSavedObject = {
|
||||
id: '1',
|
||||
type: 'action',
|
||||
attributes: {
|
||||
name: '1',
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
references: [],
|
||||
};
|
||||
|
||||
encryptedSavedObjectsClient.getDecryptedAsInternalUser.mockResolvedValueOnce(actionSavedObject);
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
actionTypeRegistry.ensureActionTypeEnabled.mockImplementationOnce(() => {
|
||||
throw new Error('not enabled for test');
|
||||
});
|
||||
actionTypeRegistry.isActionExecutable.mockImplementationOnce(() => true);
|
||||
await actionExecutor.execute(executeParams);
|
||||
|
||||
expect(actionTypeRegistry.ensureActionTypeEnabled).toHaveBeenCalledTimes(0);
|
||||
expect(actionType.executor).toHaveBeenCalledWith({
|
||||
actionId: '1',
|
||||
services: expect.anything(),
|
||||
config: {},
|
||||
secrets: {},
|
||||
params: { foo: true },
|
||||
logger: loggerMock,
|
||||
});
|
||||
});
|
||||
|
||||
test('throws an error when passing isESOCanEncrypt with value of false', async () => {
|
||||
const customActionExecutor = new ActionExecutor({ isESOCanEncrypt: false });
|
||||
customActionExecutor.initialize({
|
||||
|
@ -958,7 +1140,7 @@ test('throws an error when passing isESOCanEncrypt with value of false', async (
|
|||
actionTypeRegistry,
|
||||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
});
|
||||
await expect(
|
||||
customActionExecutor.execute(executeParams)
|
||||
|
@ -976,7 +1158,7 @@ test('should not throw error if action is preconfigured and isESOCanEncrypt is f
|
|||
actionTypeRegistry,
|
||||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
preconfiguredActions: [
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
id: 'preconfigured',
|
||||
name: 'Preconfigured',
|
||||
|
@ -1117,6 +1299,155 @@ test('should not throw error if action is preconfigured and isESOCanEncrypt is f
|
|||
`);
|
||||
});
|
||||
|
||||
test('should not throw error if action is system action and isESOCanEncrypt is false', async () => {
|
||||
const customActionExecutor = new ActionExecutor({ isESOCanEncrypt: false });
|
||||
customActionExecutor.initialize({
|
||||
logger: loggingSystemMock.create().get(),
|
||||
spaces: spacesMock,
|
||||
getServices: () => services,
|
||||
actionTypeRegistry,
|
||||
encryptedSavedObjectsClient,
|
||||
eventLogger: eventLoggerMock.create(),
|
||||
inMemoryConnectors: [
|
||||
{
|
||||
actionTypeId: '.cases',
|
||||
config: {},
|
||||
id: 'system-connector-.cases',
|
||||
name: 'System action: .cases',
|
||||
secrets: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const actionType: jest.Mocked<ActionType> = {
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.any() },
|
||||
secrets: { schema: schema.any() },
|
||||
params: { schema: schema.any() },
|
||||
},
|
||||
executor: jest.fn(),
|
||||
};
|
||||
|
||||
actionTypeRegistry.get.mockReturnValueOnce(actionType);
|
||||
await actionExecutor.execute({ ...executeParams, actionId: 'system-connector-.cases' });
|
||||
|
||||
expect(encryptedSavedObjectsClient.getDecryptedAsInternalUser).not.toHaveBeenCalled();
|
||||
|
||||
expect(actionTypeRegistry.get).toHaveBeenCalledWith('.cases');
|
||||
expect(actionTypeRegistry.isActionExecutable).toHaveBeenCalledWith(
|
||||
'system-connector-.cases',
|
||||
'.cases',
|
||||
{
|
||||
notifyUsage: true,
|
||||
}
|
||||
);
|
||||
|
||||
expect(actionType.executor).toHaveBeenCalledWith({
|
||||
actionId: 'system-connector-.cases',
|
||||
services: expect.anything(),
|
||||
config: {},
|
||||
secrets: {},
|
||||
params: { foo: true },
|
||||
logger: loggerMock,
|
||||
});
|
||||
|
||||
expect(loggerMock.debug).toBeCalledWith(
|
||||
'executing action .cases:system-connector-.cases: System action: .cases'
|
||||
);
|
||||
expect(eventLogger.logEvent.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"event": Object {
|
||||
"action": "execute-start",
|
||||
"kind": "action",
|
||||
},
|
||||
"kibana": Object {
|
||||
"action": Object {
|
||||
"execution": Object {
|
||||
"uuid": "2",
|
||||
},
|
||||
"id": "system-connector-.cases",
|
||||
"name": "System action: .cases",
|
||||
},
|
||||
"alert": Object {
|
||||
"rule": Object {
|
||||
"execution": Object {
|
||||
"uuid": "123abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
"saved_objects": Array [
|
||||
Object {
|
||||
"id": "system-connector-.cases",
|
||||
"namespace": "some-namespace",
|
||||
"rel": "primary",
|
||||
"space_agnostic": true,
|
||||
"type": "action",
|
||||
"type_id": ".cases",
|
||||
},
|
||||
],
|
||||
"space_ids": Array [
|
||||
"some-namespace",
|
||||
],
|
||||
},
|
||||
"message": "action started: .cases:system-connector-.cases: System action: .cases",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"event": Object {
|
||||
"action": "execute",
|
||||
"kind": "action",
|
||||
"outcome": "success",
|
||||
},
|
||||
"kibana": Object {
|
||||
"action": Object {
|
||||
"execution": Object {
|
||||
"uuid": "2",
|
||||
},
|
||||
"id": "system-connector-.cases",
|
||||
"name": "System action: .cases",
|
||||
},
|
||||
"alert": Object {
|
||||
"rule": Object {
|
||||
"execution": Object {
|
||||
"uuid": "123abc",
|
||||
},
|
||||
},
|
||||
},
|
||||
"saved_objects": Array [
|
||||
Object {
|
||||
"id": "system-connector-.cases",
|
||||
"namespace": "some-namespace",
|
||||
"rel": "primary",
|
||||
"space_agnostic": true,
|
||||
"type": "action",
|
||||
"type_id": ".cases",
|
||||
},
|
||||
],
|
||||
"space_ids": Array [
|
||||
"some-namespace",
|
||||
],
|
||||
},
|
||||
"message": "action executed: .cases:system-connector-.cases: System action: .cases",
|
||||
"user": Object {
|
||||
"id": "123",
|
||||
"name": "coolguy",
|
||||
},
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
||||
test('does not log warning when alert executor succeeds', async () => {
|
||||
const executorMock = setupActionExecutorMock();
|
||||
executorMock.mockResolvedValue({
|
||||
|
|
|
@ -25,7 +25,7 @@ import {
|
|||
ActionTypeExecutorRawResult,
|
||||
ActionTypeRegistryContract,
|
||||
GetServicesFunction,
|
||||
PreConfiguredAction,
|
||||
InMemoryConnector,
|
||||
RawAction,
|
||||
ValidatorServices,
|
||||
} from '../types';
|
||||
|
@ -46,7 +46,7 @@ export interface ActionExecutorContext {
|
|||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient;
|
||||
actionTypeRegistry: ActionTypeRegistryContract;
|
||||
eventLogger: IEventLogger;
|
||||
preconfiguredActions: PreConfiguredAction[];
|
||||
inMemoryConnectors: InMemoryConnector[];
|
||||
}
|
||||
|
||||
export interface TaskInfo {
|
||||
|
@ -118,7 +118,7 @@ export class ActionExecutor {
|
|||
encryptedSavedObjectsClient,
|
||||
actionTypeRegistry,
|
||||
eventLogger,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
security,
|
||||
} = this.actionExecutorContext!;
|
||||
|
||||
|
@ -129,7 +129,7 @@ export class ActionExecutor {
|
|||
const actionInfo = await getActionInfoInternal(
|
||||
this.isESOCanEncrypt,
|
||||
encryptedSavedObjectsClient,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
actionId,
|
||||
namespace.namespace
|
||||
);
|
||||
|
@ -186,7 +186,7 @@ export class ActionExecutor {
|
|||
relatedSavedObjects,
|
||||
name,
|
||||
actionExecutionId,
|
||||
isPreconfigured: this.actionInfo.isPreconfigured,
|
||||
isInMemory: this.actionInfo.isInMemory,
|
||||
...(source ? { source } : {}),
|
||||
});
|
||||
|
||||
|
@ -341,7 +341,7 @@ export class ActionExecutor {
|
|||
source?: ActionExecutionSource<Source>;
|
||||
consumer?: string;
|
||||
}) {
|
||||
const { spaces, encryptedSavedObjectsClient, preconfiguredActions, eventLogger } =
|
||||
const { spaces, encryptedSavedObjectsClient, inMemoryConnectors, eventLogger } =
|
||||
this.actionExecutorContext!;
|
||||
|
||||
const spaceId = spaces && spaces.getSpaceId(request);
|
||||
|
@ -350,7 +350,7 @@ export class ActionExecutor {
|
|||
this.actionInfo = await getActionInfoInternal(
|
||||
this.isESOCanEncrypt,
|
||||
encryptedSavedObjectsClient,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
actionId,
|
||||
namespace.namespace
|
||||
);
|
||||
|
@ -385,7 +385,7 @@ export class ActionExecutor {
|
|||
],
|
||||
relatedSavedObjects,
|
||||
actionExecutionId,
|
||||
isPreconfigured: this.actionInfo.isPreconfigured,
|
||||
isInMemory: this.actionInfo.isInMemory,
|
||||
...(source ? { source } : {}),
|
||||
});
|
||||
|
||||
|
@ -399,28 +399,29 @@ interface ActionInfo {
|
|||
config: unknown;
|
||||
secrets: unknown;
|
||||
actionId: string;
|
||||
isPreconfigured?: boolean;
|
||||
isInMemory?: boolean;
|
||||
}
|
||||
|
||||
async function getActionInfoInternal(
|
||||
isESOCanEncrypt: boolean,
|
||||
encryptedSavedObjectsClient: EncryptedSavedObjectsClient,
|
||||
preconfiguredActions: PreConfiguredAction[],
|
||||
inMemoryConnectors: InMemoryConnector[],
|
||||
actionId: string,
|
||||
namespace: string | undefined
|
||||
): Promise<ActionInfo> {
|
||||
// check to see if it's a pre-configured action first
|
||||
const pcAction = preconfiguredActions.find(
|
||||
(preconfiguredAction) => preconfiguredAction.id === actionId
|
||||
// check to see if it's in memory action first
|
||||
const inMemoryAction = inMemoryConnectors.find(
|
||||
(inMemoryConnector) => inMemoryConnector.id === actionId
|
||||
);
|
||||
if (pcAction) {
|
||||
|
||||
if (inMemoryAction) {
|
||||
return {
|
||||
actionTypeId: pcAction.actionTypeId,
|
||||
name: pcAction.name,
|
||||
config: pcAction.config,
|
||||
secrets: pcAction.secrets,
|
||||
actionTypeId: inMemoryAction.actionTypeId,
|
||||
name: inMemoryAction.name,
|
||||
config: inMemoryAction.config,
|
||||
secrets: inMemoryAction.secrets,
|
||||
actionId,
|
||||
isPreconfigured: true,
|
||||
isInMemory: true,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -87,13 +87,13 @@ describe('extractSavedObjectReferences()', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('correctly skips extracting action id if action is preconfigured', () => {
|
||||
test('correctly skips extracting action id if action is in-memory', () => {
|
||||
expect(extractSavedObjectReferences('my-action-id', true)).toEqual({
|
||||
references: [],
|
||||
});
|
||||
});
|
||||
|
||||
test('correctly extracts related saved object into references array if isPreconfigured is true', () => {
|
||||
test('correctly extracts related saved object into references array if isInMemory is true', () => {
|
||||
const relatedSavedObjects = [
|
||||
{
|
||||
id: 'abc',
|
||||
|
|
|
@ -12,7 +12,7 @@ export const ACTION_REF_NAME = `actionRef`;
|
|||
|
||||
export function extractSavedObjectReferences(
|
||||
actionId: string,
|
||||
isPreconfigured: boolean,
|
||||
isInMemory: boolean,
|
||||
relatedSavedObjects?: RelatedSavedObjects
|
||||
): {
|
||||
references: SavedObjectReference[];
|
||||
|
@ -21,8 +21,8 @@ export function extractSavedObjectReferences(
|
|||
const references: SavedObjectReference[] = [];
|
||||
const relatedSavedObjectWithRefs: RelatedSavedObjects = [];
|
||||
|
||||
// Add action saved object to reference if it is not preconfigured
|
||||
if (!isPreconfigured) {
|
||||
// Add action saved object to reference if it is not in-memory action
|
||||
if (!isInMemory) {
|
||||
references.push({
|
||||
id: actionId,
|
||||
name: ACTION_REF_NAME,
|
||||
|
|
|
@ -413,7 +413,7 @@ describe('createActionEventLogRecordObject', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('created action event "execute" for preconfigured connector with space_agnostic true', async () => {
|
||||
test('created action event "execute" for in-memory connector with space_agnostic true', async () => {
|
||||
expect(
|
||||
createActionEventLogRecordObject({
|
||||
actionId: '1',
|
||||
|
@ -432,7 +432,7 @@ describe('createActionEventLogRecordObject', () => {
|
|||
},
|
||||
],
|
||||
actionExecutionId: '123abc',
|
||||
isPreconfigured: true,
|
||||
isInMemory: true,
|
||||
})
|
||||
).toStrictEqual({
|
||||
event: {
|
||||
|
|
|
@ -35,7 +35,7 @@ interface CreateActionEventLogRecordParams {
|
|||
relation?: string;
|
||||
}>;
|
||||
relatedSavedObjects?: RelatedSavedObjects;
|
||||
isPreconfigured?: boolean;
|
||||
isInMemory?: boolean;
|
||||
source?: ActionExecutionSource<unknown>;
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec
|
|||
relatedSavedObjects,
|
||||
name,
|
||||
actionExecutionId,
|
||||
isPreconfigured,
|
||||
isInMemory,
|
||||
actionId,
|
||||
source,
|
||||
} = params;
|
||||
|
@ -80,8 +80,8 @@ export function createActionEventLogRecordObject(params: CreateActionEventLogRec
|
|||
type: so.type,
|
||||
id: so.id,
|
||||
type_id: so.typeId,
|
||||
// set space_agnostic to true for preconfigured connectors
|
||||
...(so.type === 'action' && isPreconfigured ? { space_agnostic: isPreconfigured } : {}),
|
||||
// set space_agnostic to true for in-memory connectors
|
||||
...(so.type === 'action' && isInMemory ? { space_agnostic: isInMemory } : {}),
|
||||
...(namespace ? { namespace } : {}),
|
||||
})),
|
||||
...(spaceId ? { space_ids: [spaceId] } : {}),
|
||||
|
|
|
@ -32,9 +32,6 @@ describe('ensureSufficientLicense()', () => {
|
|||
});
|
||||
|
||||
it('allows licenses below gold for allowed connectors', () => {
|
||||
expect(() =>
|
||||
ensureSufficientLicense({ ...sampleActionType, id: '.case', minimumLicenseRequired: 'basic' })
|
||||
).not.toThrow();
|
||||
expect(() =>
|
||||
ensureSufficientLicense({
|
||||
...sampleActionType,
|
||||
|
|
|
@ -9,14 +9,10 @@ import { LICENSE_TYPE } from '@kbn/licensing-plugin/common/types';
|
|||
import { ActionType } from '../types';
|
||||
import { ActionTypeConfig, ActionTypeSecrets, ActionTypeParams } from '../types';
|
||||
|
||||
const CASE_ACTION_TYPE_ID = '.case';
|
||||
const ServerLogActionTypeId = '.server-log';
|
||||
const IndexActionTypeId = '.index';
|
||||
const ACTIONS_SCOPED_WITHIN_STACK = new Set([
|
||||
ServerLogActionTypeId,
|
||||
IndexActionTypeId,
|
||||
CASE_ACTION_TYPE_ID,
|
||||
]);
|
||||
|
||||
const ACTIONS_SCOPED_WITHIN_STACK = new Set([ServerLogActionTypeId, IndexActionTypeId]);
|
||||
|
||||
export function ensureSufficientLicense<
|
||||
Config extends ActionTypeConfig,
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
*/
|
||||
|
||||
import { isPlainObject } from 'lodash';
|
||||
import { PreConfiguredAction, RawAction } from '../types';
|
||||
import { InMemoryConnector, RawAction } from '../types';
|
||||
|
||||
export type ConnectorWithOptionalDeprecation = Omit<PreConfiguredAction, 'isDeprecated'> &
|
||||
Pick<Partial<PreConfiguredAction>, 'isDeprecated'>;
|
||||
export type ConnectorWithOptionalDeprecation = Omit<InMemoryConnector, 'isDeprecated'> &
|
||||
Pick<Partial<InMemoryConnector>, 'isDeprecated'>;
|
||||
|
||||
const isObject = (obj: unknown): obj is Record<string, unknown> => isPlainObject(obj);
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ const actionExecutorInitializerParams = {
|
|||
getActionsClientWithRequest: jest.fn(async () => actionsClientMock.create()),
|
||||
encryptedSavedObjectsClient: mockedEncryptedSavedObjectsClient,
|
||||
eventLogger,
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
};
|
||||
const taskRunnerFactoryInitializerParams = {
|
||||
spaceIdToNamespace,
|
||||
|
|
|
@ -45,7 +45,7 @@ const createStartMock = () => {
|
|||
getActionsAuthorizationWithRequest: jest
|
||||
.fn()
|
||||
.mockReturnValue(actionsAuthorizationMock.create()),
|
||||
preconfiguredActions: [],
|
||||
inMemoryConnectors: [],
|
||||
renderActionParameterTemplates: jest.fn(),
|
||||
};
|
||||
return mock;
|
||||
|
|
|
@ -29,6 +29,31 @@ const executor: ExecutorType<{}, {}, {}, void> = async (options) => {
|
|||
return { status: 'ok', actionId: options.actionId };
|
||||
};
|
||||
|
||||
function getConfig(overrides = {}) {
|
||||
return {
|
||||
enabled: true,
|
||||
enabledActionTypes: ['*'],
|
||||
allowedHosts: ['*'],
|
||||
preconfiguredAlertHistoryEsIndex: false,
|
||||
preconfigured: {
|
||||
preconfiguredServerLog: {
|
||||
actionTypeId: '.server-log',
|
||||
name: 'preconfigured-server-log',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
},
|
||||
proxyRejectUnauthorizedCertificates: true,
|
||||
proxyBypassHosts: undefined,
|
||||
proxyOnlyHosts: undefined,
|
||||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration('60s'),
|
||||
enableFooterInEmail: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('Actions Plugin', () => {
|
||||
describe('setup()', () => {
|
||||
let context: PluginInitializerContext;
|
||||
|
@ -136,6 +161,106 @@ describe('Actions Plugin', () => {
|
|||
`"Unable to create actions client because the Encrypted Saved Objects plugin is missing encryption key. Please set xpack.encryptedSavedObjects.encryptionKey in the kibana.yml or use the bin/kibana-encryption-keys command."`
|
||||
);
|
||||
});
|
||||
|
||||
it('the actions client should have the correct in-memory connectors', async () => {
|
||||
context = coreMock.createPluginInitializerContext<ActionsConfig>(getConfig());
|
||||
const pluginWithPreconfiguredConnectors = new ActionsPlugin(context);
|
||||
|
||||
const coreStart = coreMock.createStart();
|
||||
const pluginsStart = {
|
||||
licensing: licensingMock.createStart(),
|
||||
taskManager: taskManagerMock.createStart(),
|
||||
encryptedSavedObjects: encryptedSavedObjectsMock.createStart(),
|
||||
eventLog: eventLogMock.createStart(),
|
||||
};
|
||||
|
||||
/**
|
||||
* 1. In the setup of the actions plugin
|
||||
* the preconfigured connectors are being
|
||||
* set up. Also, the action router handler context
|
||||
* is registered
|
||||
*/
|
||||
const pluginSetup = await pluginWithPreconfiguredConnectors.setup(coreSetup, {
|
||||
...pluginsSetup,
|
||||
encryptedSavedObjects: {
|
||||
...pluginsSetup.encryptedSavedObjects,
|
||||
canEncrypt: true,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 2. We simulate the registration of
|
||||
* a system action by another plugin
|
||||
* in the setup
|
||||
*/
|
||||
pluginSetup.registerType({
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
isSystemActionType: true,
|
||||
executor,
|
||||
});
|
||||
|
||||
const handler = coreSetup.http.registerRouteHandlerContext.mock.calls[0];
|
||||
|
||||
/**
|
||||
* 3. On start the system actions are being
|
||||
* created based on the system action types
|
||||
* that got registered on step 2
|
||||
*/
|
||||
await pluginWithPreconfiguredConnectors.start(coreStart, pluginsStart);
|
||||
|
||||
const actionsContextHandler = (await handler[1](
|
||||
{
|
||||
core: {
|
||||
savedObjects: {
|
||||
client: {},
|
||||
},
|
||||
elasticsearch: {
|
||||
client: jest.fn(),
|
||||
},
|
||||
},
|
||||
} as unknown as RequestHandlerContext,
|
||||
httpServerMock.createKibanaRequest(),
|
||||
httpServerMock.createResponseFactory()
|
||||
)) as unknown as ActionsApiRequestHandlerContext;
|
||||
|
||||
/**
|
||||
* 4. We verify that the actions client inside
|
||||
* the router context has the correct system connectors
|
||||
* that got set up on start (step 3).
|
||||
*/
|
||||
// @ts-expect-error: inMemoryConnectors can be accessed
|
||||
expect(actionsContextHandler.getActionsClient().inMemoryConnectors).toEqual([
|
||||
{
|
||||
id: 'preconfiguredServerLog',
|
||||
actionTypeId: '.server-log',
|
||||
name: 'preconfigured-server-log',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isPreconfigured: true,
|
||||
isSystemAction: false,
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
isMissingSecrets: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('registerType()', () => {
|
||||
|
@ -199,31 +324,6 @@ describe('Actions Plugin', () => {
|
|||
});
|
||||
|
||||
describe('isPreconfiguredConnector', () => {
|
||||
function getConfig(overrides = {}) {
|
||||
return {
|
||||
enabled: true,
|
||||
enabledActionTypes: ['*'],
|
||||
allowedHosts: ['*'],
|
||||
preconfiguredAlertHistoryEsIndex: false,
|
||||
preconfigured: {
|
||||
preconfiguredServerLog: {
|
||||
actionTypeId: '.server-log',
|
||||
name: 'preconfigured-server-log',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
},
|
||||
proxyRejectUnauthorizedCertificates: true,
|
||||
proxyBypassHosts: undefined,
|
||||
proxyOnlyHosts: undefined,
|
||||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration('60s'),
|
||||
enableFooterInEmail: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function setup(config: ActionsConfig) {
|
||||
context = coreMock.createPluginInitializerContext<ActionsConfig>(config);
|
||||
plugin = new ActionsPlugin(context);
|
||||
|
@ -323,32 +423,7 @@ describe('Actions Plugin', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Preconfigured connectors', () => {
|
||||
function getConfig(overrides = {}) {
|
||||
return {
|
||||
enabled: true,
|
||||
enabledActionTypes: ['*'],
|
||||
allowedHosts: ['*'],
|
||||
preconfiguredAlertHistoryEsIndex: false,
|
||||
preconfigured: {
|
||||
preconfiguredServerLog: {
|
||||
actionTypeId: '.server-log',
|
||||
name: 'preconfigured-server-log',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
},
|
||||
proxyRejectUnauthorizedCertificates: true,
|
||||
proxyBypassHosts: undefined,
|
||||
proxyOnlyHosts: undefined,
|
||||
rejectUnauthorized: true,
|
||||
maxResponseContentLength: new ByteSizeValue(1000000),
|
||||
responseTimeout: moment.duration('60s'),
|
||||
enableFooterInEmail: true,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe('inMemoryConnectors', () => {
|
||||
function setup(config: ActionsConfig) {
|
||||
context = coreMock.createPluginInitializerContext<ActionsConfig>(config);
|
||||
plugin = new ActionsPlugin(context);
|
||||
|
@ -370,78 +445,134 @@ describe('Actions Plugin', () => {
|
|||
};
|
||||
}
|
||||
|
||||
it('should handle preconfigured actions', async () => {
|
||||
setup(getConfig());
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
pluginSetup.registerType({
|
||||
id: '.server-log',
|
||||
name: 'Server log',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
executor,
|
||||
});
|
||||
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
||||
expect(pluginStart.preconfiguredActions.length).toEqual(1);
|
||||
expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.server-log')).toBe(true);
|
||||
});
|
||||
|
||||
it('should handle preconfiguredAlertHistoryEsIndex = true', async () => {
|
||||
setup(getConfig({ preconfiguredAlertHistoryEsIndex: true }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
pluginSetup.registerType({
|
||||
id: '.index',
|
||||
name: 'ES Index',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
executor,
|
||||
});
|
||||
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
||||
expect(pluginStart.preconfiguredActions.length).toEqual(2);
|
||||
expect(
|
||||
pluginStart.isActionExecutable('preconfigured-alert-history-es-index', '.index')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should not allow preconfigured connector with same ID as AlertHistoryEsIndexConnectorId', async () => {
|
||||
setup(
|
||||
getConfig({
|
||||
preconfigured: {
|
||||
[AlertHistoryEsIndexConnectorId]: {
|
||||
actionTypeId: '.index',
|
||||
name: 'clashing preconfigured index connector',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
describe('Preconfigured connectors', () => {
|
||||
it('should handle preconfigured actions', async () => {
|
||||
setup(getConfig());
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
pluginSetup.registerType({
|
||||
id: '.server-log',
|
||||
name: 'Server log',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
})
|
||||
);
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
executor,
|
||||
});
|
||||
|
||||
expect(pluginStart.preconfiguredActions.length).toEqual(0);
|
||||
expect(context.logger.get().warn).toHaveBeenCalledWith(
|
||||
`Preconfigured connectors cannot have the id "${AlertHistoryEsIndexConnectorId}" because this is a reserved id.`
|
||||
);
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
||||
expect(pluginStart.inMemoryConnectors.length).toEqual(1);
|
||||
expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.server-log')).toBe(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle preconfiguredAlertHistoryEsIndex = true', async () => {
|
||||
setup(getConfig({ preconfiguredAlertHistoryEsIndex: true }));
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
pluginSetup.registerType({
|
||||
id: '.index',
|
||||
name: 'ES Index',
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
executor,
|
||||
});
|
||||
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
||||
expect(pluginStart.inMemoryConnectors.length).toEqual(2);
|
||||
expect(
|
||||
pluginStart.isActionExecutable('preconfigured-alert-history-es-index', '.index')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should not allow preconfigured connector with same ID as AlertHistoryEsIndexConnectorId', async () => {
|
||||
setup(
|
||||
getConfig({
|
||||
preconfigured: {
|
||||
[AlertHistoryEsIndexConnectorId]: {
|
||||
actionTypeId: '.index',
|
||||
name: 'clashing preconfigured index connector',
|
||||
config: {},
|
||||
secrets: {},
|
||||
},
|
||||
},
|
||||
})
|
||||
);
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
||||
expect(pluginStart.inMemoryConnectors.length).toEqual(0);
|
||||
expect(context.logger.get().warn).toHaveBeenCalledWith(
|
||||
`Preconfigured connectors cannot have the id "${AlertHistoryEsIndexConnectorId}" because this is a reserved id.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('System actions', () => {
|
||||
it('should handle system actions', async () => {
|
||||
setup(getConfig());
|
||||
// coreMock.createSetup doesn't support Plugin generics
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const pluginSetup = await plugin.setup(coreSetup as any, pluginsSetup);
|
||||
|
||||
pluginSetup.registerType({
|
||||
id: '.cases',
|
||||
name: 'Cases',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
config: { schema: schema.object({}) },
|
||||
secrets: { schema: schema.object({}) },
|
||||
params: { schema: schema.object({}) },
|
||||
},
|
||||
isSystemActionType: true,
|
||||
executor,
|
||||
});
|
||||
|
||||
const pluginStart = await plugin.start(coreStart, pluginsStart);
|
||||
|
||||
// inMemoryConnectors holds both preconfigure and system connectors
|
||||
expect(pluginStart.inMemoryConnectors.length).toEqual(2);
|
||||
expect(pluginStart.inMemoryConnectors).toEqual([
|
||||
{
|
||||
id: 'preconfiguredServerLog',
|
||||
actionTypeId: '.server-log',
|
||||
name: 'preconfigured-server-log',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isPreconfigured: true,
|
||||
isSystemAction: false,
|
||||
},
|
||||
{
|
||||
id: 'system-connector-.cases',
|
||||
actionTypeId: '.cases',
|
||||
name: 'System action: .cases',
|
||||
config: {},
|
||||
secrets: {},
|
||||
isDeprecated: false,
|
||||
isMissingSecrets: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
},
|
||||
]);
|
||||
expect(pluginStart.isActionExecutable('preconfiguredServerLog', '.cases')).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ import {
|
|||
import {
|
||||
Services,
|
||||
ActionType,
|
||||
PreConfiguredAction,
|
||||
InMemoryConnector,
|
||||
ActionTypeConfig,
|
||||
ActionTypeSecrets,
|
||||
ActionTypeParams,
|
||||
|
@ -106,6 +106,7 @@ import {
|
|||
UnsecuredActionsClient,
|
||||
} from './unsecured_actions_client/unsecured_actions_client';
|
||||
import { createBulkUnsecuredExecutionEnqueuerFunction } from './create_unsecured_execute_function';
|
||||
import { createSystemConnectors } from './create_system_actions';
|
||||
|
||||
export interface PluginSetupContract {
|
||||
registerType<
|
||||
|
@ -147,7 +148,7 @@ export interface PluginStartContract {
|
|||
|
||||
getActionsAuthorizationWithRequest(request: KibanaRequest): PublicMethodsOf<ActionsAuthorization>;
|
||||
|
||||
preconfiguredActions: PreConfiguredAction[];
|
||||
inMemoryConnectors: InMemoryConnector[];
|
||||
|
||||
getUnsecuredActionsClient(): IUnsecuredActionsClient;
|
||||
|
||||
|
@ -200,7 +201,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
private isESOCanEncrypt?: boolean;
|
||||
private usageCounter?: UsageCounter;
|
||||
private readonly telemetryLogger: Logger;
|
||||
private readonly preconfiguredActions: PreConfiguredAction[];
|
||||
private inMemoryConnectors: InMemoryConnector[];
|
||||
private inMemoryMetrics: InMemoryMetrics;
|
||||
|
||||
constructor(initContext: PluginInitializerContext) {
|
||||
|
@ -210,7 +211,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
resolveCustomHosts(this.logger, initContext.config.get<ActionsConfig>())
|
||||
);
|
||||
this.telemetryLogger = initContext.logger.get('usage');
|
||||
this.preconfiguredActions = [];
|
||||
this.inMemoryConnectors = [];
|
||||
this.inMemoryMetrics = new InMemoryMetrics(initContext.logger.get('in_memory_metrics'));
|
||||
}
|
||||
|
||||
|
@ -244,7 +245,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
const actionsConfigUtils = getActionsConfigurationUtilities(this.actionsConfig);
|
||||
|
||||
if (this.actionsConfig.preconfiguredAlertHistoryEsIndex) {
|
||||
this.preconfiguredActions.push(getAlertHistoryEsIndex());
|
||||
this.inMemoryConnectors.push(getAlertHistoryEsIndex());
|
||||
}
|
||||
|
||||
for (const preconfiguredId of Object.keys(this.actionsConfig.preconfigured)) {
|
||||
|
@ -255,7 +256,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
isPreconfigured: true,
|
||||
isSystemAction: false,
|
||||
};
|
||||
this.preconfiguredActions.push({
|
||||
this.inMemoryConnectors.push({
|
||||
...rawPreconfiguredConnector,
|
||||
isDeprecated: isConnectorDeprecated(rawPreconfiguredConnector),
|
||||
});
|
||||
|
@ -272,7 +273,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
taskManager: plugins.taskManager,
|
||||
actionsConfigUtils,
|
||||
licenseState: this.licenseState,
|
||||
preconfiguredActions: this.preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
});
|
||||
this.taskRunnerFactory = taskRunnerFactory;
|
||||
this.actionTypeRegistry = actionTypeRegistry;
|
||||
|
@ -284,7 +285,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
plugins.encryptedSavedObjects,
|
||||
this.actionTypeRegistry!,
|
||||
plugins.taskManager.index,
|
||||
this.preconfiguredActions
|
||||
this.inMemoryConnectors
|
||||
);
|
||||
|
||||
const usageCollection = plugins.usageCollection;
|
||||
|
@ -307,7 +308,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
this.telemetryLogger,
|
||||
plugins.taskManager,
|
||||
core,
|
||||
this.preconfiguredActions,
|
||||
this.getInMemoryConnectors,
|
||||
eventLogIndex
|
||||
);
|
||||
}
|
||||
|
@ -361,8 +362,9 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
subActionFramework.registerConnector(connector);
|
||||
},
|
||||
isPreconfiguredConnector: (connectorId: string): boolean => {
|
||||
return !!this.preconfiguredActions.find(
|
||||
(preconfigured) => preconfigured.id === connectorId
|
||||
return !!this.inMemoryConnectors.find(
|
||||
(inMemoryConnector) =>
|
||||
inMemoryConnector.isPreconfigured && inMemoryConnector.id === connectorId
|
||||
);
|
||||
},
|
||||
getSubActionConnectorClass: () => SubActionConnector,
|
||||
|
@ -384,7 +386,6 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
actionTypeRegistry,
|
||||
taskRunnerFactory,
|
||||
isESOCanEncrypt,
|
||||
preconfiguredActions,
|
||||
instantiateAuthorization,
|
||||
getUnsecuredSavedObjectsClient,
|
||||
} = this;
|
||||
|
@ -395,6 +396,16 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
includedHiddenTypes,
|
||||
});
|
||||
|
||||
/**
|
||||
* Warning: this call mutates the inMemory collection
|
||||
*
|
||||
* Warning: it maybe possible for the task manager to start before
|
||||
* the system actions are being set.
|
||||
*
|
||||
* Issue: https://github.com/elastic/kibana/issues/160797
|
||||
*/
|
||||
this.setSystemActions();
|
||||
|
||||
const getActionsClientWithRequest = async (
|
||||
request: KibanaRequest,
|
||||
authorizationContext?: ActionExecutionSource<unknown>
|
||||
|
@ -416,7 +427,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
actionTypeRegistry: actionTypeRegistry!,
|
||||
kibanaIndices: core.savedObjects.getAllIndices(),
|
||||
scopedClusterClient: core.elasticsearch.client.asScoped(request),
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
request,
|
||||
authorization: instantiateAuthorization(
|
||||
request,
|
||||
|
@ -427,19 +438,19 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
taskManager: plugins.taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
}),
|
||||
executionEnqueuer: createExecutionEnqueuerFunction({
|
||||
taskManager: plugins.taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
}),
|
||||
bulkExecutionEnqueuer: createBulkExecutionEnqueuerFunction({
|
||||
taskManager: plugins.taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
}),
|
||||
auditLogger: this.security?.audit.asScoped(request),
|
||||
usageCounter: this.usageCounter,
|
||||
|
@ -464,7 +475,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
executionEnqueuer: createBulkUnsecuredExecutionEnqueuerFunction({
|
||||
taskManager: plugins.taskManager,
|
||||
connectorTypeRegistry: actionTypeRegistry!,
|
||||
preconfiguredConnectors: preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
@ -501,7 +512,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
),
|
||||
encryptedSavedObjectsClient,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
});
|
||||
|
||||
taskRunnerFactory!.initialize({
|
||||
|
@ -543,7 +554,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
},
|
||||
getActionsClientWithRequest: secureGetActionsClientWithRequest,
|
||||
getUnsecuredActionsClient,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors: this.inMemoryConnectors,
|
||||
renderActionParameterTemplates: (...args) =>
|
||||
renderActionParameterTemplates(actionTypeRegistry, ...args),
|
||||
};
|
||||
|
@ -589,13 +600,20 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
};
|
||||
}
|
||||
|
||||
private getInMemoryConnectors = () => this.inMemoryConnectors;
|
||||
|
||||
private setSystemActions = () => {
|
||||
const systemConnectors = createSystemConnectors(this.actionTypeRegistry?.list() ?? []);
|
||||
this.inMemoryConnectors = [...this.inMemoryConnectors, ...systemConnectors];
|
||||
};
|
||||
|
||||
private createRouteHandlerContext = (
|
||||
core: CoreSetup<ActionsPluginsStart>
|
||||
): IContextProvider<ActionsRequestHandlerContext, 'actions'> => {
|
||||
const {
|
||||
actionTypeRegistry,
|
||||
isESOCanEncrypt,
|
||||
preconfiguredActions,
|
||||
getInMemoryConnectors,
|
||||
actionExecutor,
|
||||
instantiateAuthorization,
|
||||
security,
|
||||
|
@ -606,7 +624,9 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
return async function actionsRouteHandlerContext(context, request) {
|
||||
const [{ savedObjects }, { taskManager, encryptedSavedObjects, eventLog }] =
|
||||
await core.getStartServices();
|
||||
|
||||
const coreContext = await context.core;
|
||||
const inMemoryConnectors = getInMemoryConnectors();
|
||||
|
||||
return {
|
||||
getActionsClient: () => {
|
||||
|
@ -625,7 +645,7 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
actionTypeRegistry: actionTypeRegistry!,
|
||||
kibanaIndices: savedObjects.getAllIndices(),
|
||||
scopedClusterClient: coreContext.elasticsearch.client,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
request,
|
||||
authorization: instantiateAuthorization(request),
|
||||
actionExecutor: actionExecutor!,
|
||||
|
@ -633,19 +653,19 @@ export class ActionsPlugin implements Plugin<PluginSetupContract, PluginStartCon
|
|||
taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
}),
|
||||
executionEnqueuer: createExecutionEnqueuerFunction({
|
||||
taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
}),
|
||||
bulkExecutionEnqueuer: createBulkExecutionEnqueuerFunction({
|
||||
taskManager,
|
||||
actionTypeRegistry: actionTypeRegistry!,
|
||||
isESOCanEncrypt: isESOCanEncrypt!,
|
||||
preconfiguredActions,
|
||||
inMemoryConnectors,
|
||||
}),
|
||||
auditLogger: security?.audit.asScoped(request),
|
||||
usageCounter,
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { PreConfiguredAction } from '../../types';
|
||||
import { InMemoryConnector } from '../../types';
|
||||
import { AlertHistoryEsIndexConnectorId, AlertHistoryDefaultIndexName } from '../../../common';
|
||||
|
||||
const EsIndexActionTypeId = '.index';
|
||||
export function getAlertHistoryEsIndex(): Readonly<PreConfiguredAction> {
|
||||
export function getAlertHistoryEsIndex(): Readonly<InMemoryConnector> {
|
||||
return Object.freeze({
|
||||
name: i18n.translate('xpack.actions.alertHistoryEsIndexConnector.name', {
|
||||
defaultMessage: 'Alert history Elasticsearch index',
|
||||
|
|
|
@ -42,6 +42,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -57,6 +58,7 @@ describe('connectorTypesRoute', () => {
|
|||
"enabled_in_config": true,
|
||||
"enabled_in_license": true,
|
||||
"id": "1",
|
||||
"is_system_action_type": false,
|
||||
"minimum_license_required": "gold",
|
||||
"name": "name",
|
||||
"supported_feature_ids": Array [
|
||||
|
@ -77,6 +79,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'gold',
|
||||
is_system_action_type: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -101,6 +104,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -124,6 +128,7 @@ describe('connectorTypesRoute', () => {
|
|||
"enabled_in_config": true,
|
||||
"enabled_in_license": true,
|
||||
"id": "1",
|
||||
"is_system_action_type": false,
|
||||
"minimum_license_required": "gold",
|
||||
"name": "name",
|
||||
"supported_feature_ids": Array [
|
||||
|
@ -151,6 +156,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'gold',
|
||||
is_system_action_type: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
@ -175,6 +181,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -217,6 +224,7 @@ describe('connectorTypesRoute', () => {
|
|||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ const rewriteBodyRes: RewriteResponseCase<ActionType[]> = (results) => {
|
|||
enabledInLicense,
|
||||
minimumLicenseRequired,
|
||||
supportedFeatureIds,
|
||||
isSystemActionType,
|
||||
...res
|
||||
}) => ({
|
||||
...res,
|
||||
|
@ -30,6 +31,7 @@ const rewriteBodyRes: RewriteResponseCase<ActionType[]> = (results) => {
|
|||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
supported_feature_ids: supportedFeatureIds,
|
||||
is_system_action_type: isSystemActionType,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -50,6 +50,7 @@ describe('listActionTypesRoute', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -65,6 +66,7 @@ describe('listActionTypesRoute', () => {
|
|||
"enabledInConfig": true,
|
||||
"enabledInLicense": true,
|
||||
"id": "1",
|
||||
"isSystemActionType": false,
|
||||
"minimumLicenseRequired": "gold",
|
||||
"name": "name",
|
||||
"supportedFeatureIds": Array [
|
||||
|
@ -99,6 +101,7 @@ describe('listActionTypesRoute', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -141,6 +144,7 @@ describe('listActionTypesRoute', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'gold' as LicenseType,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -6,10 +6,7 @@
|
|||
*/
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import {
|
||||
getActionTaskParamsMigrations,
|
||||
isPreconfiguredAction,
|
||||
} from './action_task_params_migrations';
|
||||
import { getActionTaskParamsMigrations, isInMemoryAction } from './action_task_params_migrations';
|
||||
import { ActionTaskParams } from '../types';
|
||||
import { SavedObjectReference, SavedObjectUnsanitizedDoc } from '@kbn/core/server';
|
||||
import { encryptedSavedObjectsMock } from '@kbn/encrypted-saved-objects-plugin/server/mocks';
|
||||
|
@ -19,7 +16,7 @@ import { SavedObjectsUtils } from '@kbn/core-saved-objects-utils-server';
|
|||
const context = migrationMocks.createContext();
|
||||
const encryptedSavedObjectsSetup = encryptedSavedObjectsMock.createSetup();
|
||||
|
||||
const preconfiguredActions = [
|
||||
const inMemoryConnectors = [
|
||||
{
|
||||
actionTypeId: 'foo',
|
||||
config: {},
|
||||
|
@ -39,9 +36,9 @@ describe('successful migrations', () => {
|
|||
});
|
||||
|
||||
describe('7.16.0', () => {
|
||||
test('adds actionId to references array if actionId is not preconfigured', () => {
|
||||
test('adds actionId to references array if actionId is not in-memory', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData();
|
||||
const migratedActionTaskParam = migration716(actionTaskParam, context);
|
||||
|
@ -57,9 +54,9 @@ describe('successful migrations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('does not add actionId to references array if actionId is preconfigured', () => {
|
||||
test('does not add actionId to references array if actionId is in-memory', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData({ actionId: 'my-slack1' });
|
||||
const migratedActionTaskParam = migration716(actionTaskParam, context);
|
||||
|
@ -71,7 +68,7 @@ describe('successful migrations', () => {
|
|||
|
||||
test('handles empty relatedSavedObjects array', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData({ relatedSavedObjects: [] });
|
||||
const migratedActionTaskParam = migration716(actionTaskParam, context);
|
||||
|
@ -93,7 +90,7 @@ describe('successful migrations', () => {
|
|||
|
||||
test('adds actionId and relatedSavedObjects to references array', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData({
|
||||
relatedSavedObjects: [
|
||||
|
@ -134,9 +131,9 @@ describe('successful migrations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('only adds relatedSavedObjects to references array if action is preconfigured', () => {
|
||||
test('only adds relatedSavedObjects to references array if action is in-memory', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData({
|
||||
actionId: 'my-slack1',
|
||||
|
@ -175,7 +172,7 @@ describe('successful migrations', () => {
|
|||
|
||||
test('adds actionId and multiple relatedSavedObjects to references array', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData({
|
||||
relatedSavedObjects: [
|
||||
|
@ -233,7 +230,7 @@ describe('successful migrations', () => {
|
|||
|
||||
test('does not overwrite existing references', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData(
|
||||
{
|
||||
|
@ -290,7 +287,7 @@ describe('successful migrations', () => {
|
|||
|
||||
test('does not overwrite existing references if relatedSavedObjects is undefined', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData({}, [
|
||||
{
|
||||
|
@ -319,7 +316,7 @@ describe('successful migrations', () => {
|
|||
|
||||
test('does not overwrite existing references if relatedSavedObjects is empty', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData({ relatedSavedObjects: [] }, [
|
||||
{
|
||||
|
@ -373,7 +370,7 @@ describe('handles errors during migrations', () => {
|
|||
describe('7.16.0 throws if migration fails', () => {
|
||||
test('should show the proper exception', () => {
|
||||
const migration716 = SavedObjectsUtils.getMigrationFunction(
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, preconfiguredActions)['7.16.0']
|
||||
getActionTaskParamsMigrations(encryptedSavedObjectsSetup, inMemoryConnectors)['7.16.0']
|
||||
);
|
||||
const actionTaskParam = getMockData();
|
||||
expect(() => {
|
||||
|
@ -391,15 +388,15 @@ describe('handles errors during migrations', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('isPreconfiguredAction()', () => {
|
||||
test('returns true if actionId is preconfigured action', () => {
|
||||
expect(
|
||||
isPreconfiguredAction(getMockData({ actionId: 'my-slack1' }), preconfiguredActions)
|
||||
).toEqual(true);
|
||||
describe('isInMemoryAction()', () => {
|
||||
test('returns true if actionId is in-memory action', () => {
|
||||
expect(isInMemoryAction(getMockData({ actionId: 'my-slack1' }), inMemoryConnectors)).toEqual(
|
||||
true
|
||||
);
|
||||
});
|
||||
|
||||
test('returns false if actionId is not preconfigured action', () => {
|
||||
expect(isPreconfiguredAction(getMockData(), preconfiguredActions)).toEqual(false);
|
||||
test('returns false if actionId is not in-memory action', () => {
|
||||
expect(isInMemoryAction(getMockData(), inMemoryConnectors)).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
} from '@kbn/core/server';
|
||||
import { EncryptedSavedObjectsPluginSetup } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import type { IsMigrationNeededPredicate } from '@kbn/encrypted-saved-objects-plugin/server';
|
||||
import { ActionTaskParams, PreConfiguredAction } from '../types';
|
||||
import { ActionTaskParams, InMemoryConnector } from '../types';
|
||||
import { RelatedSavedObjects } from '../lib/related_saved_objects';
|
||||
|
||||
interface ActionTaskParamsLogMeta extends LogMeta {
|
||||
|
@ -40,12 +40,12 @@ function createEsoMigration(
|
|||
|
||||
export function getActionTaskParamsMigrations(
|
||||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup,
|
||||
preconfiguredActions: PreConfiguredAction[]
|
||||
inMemoryConnectors: InMemoryConnector[]
|
||||
): SavedObjectMigrationMap {
|
||||
const migrationActionTaskParamsSixteen = createEsoMigration(
|
||||
encryptedSavedObjects,
|
||||
(doc): doc is SavedObjectUnsanitizedDoc<ActionTaskParams> => true,
|
||||
pipeMigrations(getUseSavedObjectReferencesFn(preconfiguredActions))
|
||||
pipeMigrations(getUseSavedObjectReferencesFn(inMemoryConnectors))
|
||||
);
|
||||
|
||||
const migrationActionsTaskParams800 = createEsoMigration(
|
||||
|
@ -86,22 +86,22 @@ function executeMigrationWithErrorHandling(
|
|||
};
|
||||
}
|
||||
|
||||
export function isPreconfiguredAction(
|
||||
export function isInMemoryAction(
|
||||
doc: SavedObjectUnsanitizedDoc<ActionTaskParams>,
|
||||
preconfiguredActions: PreConfiguredAction[]
|
||||
inMemoryConnectors: InMemoryConnector[]
|
||||
): boolean {
|
||||
return !!preconfiguredActions.find((action) => action.id === doc.attributes.actionId);
|
||||
return !!inMemoryConnectors.find((action) => action.id === doc.attributes.actionId);
|
||||
}
|
||||
|
||||
function getUseSavedObjectReferencesFn(preconfiguredActions: PreConfiguredAction[]) {
|
||||
function getUseSavedObjectReferencesFn(inMemoryConnectors: InMemoryConnector[]) {
|
||||
return (doc: SavedObjectUnsanitizedDoc<ActionTaskParams>) => {
|
||||
return useSavedObjectReferences(doc, preconfiguredActions);
|
||||
return useSavedObjectReferences(doc, inMemoryConnectors);
|
||||
};
|
||||
}
|
||||
|
||||
function useSavedObjectReferences(
|
||||
doc: SavedObjectUnsanitizedDoc<ActionTaskParams>,
|
||||
preconfiguredActions: PreConfiguredAction[]
|
||||
inMemoryConnectors: InMemoryConnector[]
|
||||
): SavedObjectUnsanitizedDoc<ActionTaskParams> {
|
||||
const {
|
||||
attributes: { actionId, relatedSavedObjects },
|
||||
|
@ -111,7 +111,7 @@ function useSavedObjectReferences(
|
|||
const newReferences: SavedObjectReference[] = [];
|
||||
const relatedSavedObjectRefs: RelatedSavedObjects = [];
|
||||
|
||||
if (!isPreconfiguredAction(doc, preconfiguredActions)) {
|
||||
if (!isInMemoryAction(doc, inMemoryConnectors)) {
|
||||
newReferences.push({
|
||||
id: actionId,
|
||||
name: 'actionRef',
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ALERTING_CASES_SAVED_OBJECT_INDEX } from '@kbn/core-saved-objects-serve
|
|||
import { actionMappings, actionTaskParamsMappings, connectorTokenMappings } from './mappings';
|
||||
import { getActionsMigrations } from './actions_migrations';
|
||||
import { getActionTaskParamsMigrations } from './action_task_params_migrations';
|
||||
import { PreConfiguredAction, RawAction } from '../types';
|
||||
import { InMemoryConnector, RawAction } from '../types';
|
||||
import { getImportWarnings } from './get_import_warnings';
|
||||
import { transformConnectorsForExport } from './transform_connectors_for_export';
|
||||
import { ActionTypeRegistry } from '../action_type_registry';
|
||||
|
@ -31,7 +31,7 @@ export function setupSavedObjects(
|
|||
encryptedSavedObjects: EncryptedSavedObjectsPluginSetup,
|
||||
actionTypeRegistry: ActionTypeRegistry,
|
||||
taskManagerIndex: string,
|
||||
preconfiguredActions: PreConfiguredAction[]
|
||||
inMemoryConnectors: InMemoryConnector[]
|
||||
) {
|
||||
savedObjects.registerType({
|
||||
name: ACTION_SAVED_OBJECT_TYPE,
|
||||
|
@ -79,7 +79,7 @@ export function setupSavedObjects(
|
|||
namespaceType: 'multiple-isolated',
|
||||
convertToMultiNamespaceTypeVersion: '8.0.0',
|
||||
mappings: actionTaskParamsMappings,
|
||||
migrations: getActionTaskParamsMigrations(encryptedSavedObjects, preconfiguredActions),
|
||||
migrations: getActionTaskParamsMigrations(encryptedSavedObjects, inMemoryConnectors),
|
||||
excludeOnUpgrade: async ({ readonlyEsClient }) => {
|
||||
const oldestIdleActionTask = await getOldestIdleActionTask(
|
||||
readonlyEsClient,
|
||||
|
|
|
@ -84,7 +84,7 @@ export interface ActionResult<Config extends ActionTypeConfig = ActionTypeConfig
|
|||
isSystemAction: boolean;
|
||||
}
|
||||
|
||||
export interface PreConfiguredAction<
|
||||
export interface InMemoryConnector<
|
||||
Config extends ActionTypeConfig = ActionTypeConfig,
|
||||
Secrets extends ActionTypeSecrets = ActionTypeSecrets
|
||||
> extends ActionResult<Config> {
|
||||
|
@ -140,7 +140,7 @@ export interface ActionType<
|
|||
secrets: ValidatorType<Secrets>;
|
||||
connector?: (config: Config, secrets: Secrets) => string | null;
|
||||
};
|
||||
isSystemAction?: boolean;
|
||||
isSystemActionType?: boolean;
|
||||
renderParameterTemplates?: RenderParameterTemplates<Params>;
|
||||
executor: ExecutorType<Config, Secrets, Params, ExecutorResultData>;
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@ import {
|
|||
parseActionRunOutcomeByConnectorTypesBucket,
|
||||
} from './lib/parse_connector_type_bucket';
|
||||
import { AlertHistoryEsIndexConnectorId } from '../../common';
|
||||
import { ActionResult, PreConfiguredAction } from '../types';
|
||||
import { ActionResult, InMemoryConnector } from '../types';
|
||||
|
||||
export async function getTotalCount(
|
||||
esClient: ElasticsearchClient,
|
||||
kibanaIndex: string,
|
||||
logger: Logger,
|
||||
preconfiguredActions?: PreConfiguredAction[]
|
||||
inMemoryConnectors?: InMemoryConnector[]
|
||||
) {
|
||||
const scriptedMetric = {
|
||||
scripted_metric: {
|
||||
|
@ -74,9 +74,9 @@ export async function getTotalCount(
|
|||
},
|
||||
{}
|
||||
);
|
||||
if (preconfiguredActions && preconfiguredActions.length) {
|
||||
for (const preconfiguredAction of preconfiguredActions) {
|
||||
const actionTypeId = replaceFirstAndLastDotSymbols(preconfiguredAction.actionTypeId);
|
||||
if (inMemoryConnectors && inMemoryConnectors.length) {
|
||||
for (const inMemoryConnector of inMemoryConnectors) {
|
||||
const actionTypeId = replaceFirstAndLastDotSymbols(inMemoryConnector.actionTypeId);
|
||||
countByType[actionTypeId] = countByType[actionTypeId] || 0;
|
||||
countByType[actionTypeId]++;
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ export async function getTotalCount(
|
|||
Object.keys(aggs).reduce(
|
||||
(total: number, key: string) => parseInt(aggs[key], 10) + total,
|
||||
0
|
||||
) + (preconfiguredActions?.length ?? 0),
|
||||
) + (inMemoryConnectors?.length ?? 0),
|
||||
countByType,
|
||||
};
|
||||
} catch (err) {
|
||||
|
@ -109,7 +109,7 @@ export async function getInUseTotalCount(
|
|||
kibanaIndex: string,
|
||||
logger: Logger,
|
||||
referenceType?: string,
|
||||
preconfiguredActions?: PreConfiguredAction[]
|
||||
inMemoryConnectors?: InMemoryConnector[]
|
||||
): Promise<{
|
||||
hasErrors: boolean;
|
||||
errorMessage?: string;
|
||||
|
@ -363,9 +363,9 @@ export async function getInUseTotalCount(
|
|||
if (actionRef === `preconfigured:${AlertHistoryEsIndexConnectorId}`) {
|
||||
preconfiguredAlertHistoryConnectors++;
|
||||
}
|
||||
if (preconfiguredActions && actionTypeId === '__email') {
|
||||
if (inMemoryConnectors && actionTypeId === '__email') {
|
||||
const preconfiguredConnectorId = actionRef.split(':')[1];
|
||||
const service = (preconfiguredActions.find(
|
||||
const service = (inMemoryConnectors.find(
|
||||
(preconfConnector) => preconfConnector.id === preconfiguredConnectorId
|
||||
)?.config?.service ?? 'other') as string;
|
||||
const currentCount =
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
TaskManagerStartContract,
|
||||
IntervalSchedule,
|
||||
} from '@kbn/task-manager-plugin/server';
|
||||
import { PreConfiguredAction } from '../types';
|
||||
import { InMemoryConnector } from '../types';
|
||||
import { getTotalCount, getInUseTotalCount, getExecutionsPerDayCount } from './actions_telemetry';
|
||||
|
||||
export const TELEMETRY_TASK_TYPE = 'actions_telemetry';
|
||||
|
@ -24,10 +24,10 @@ export function initializeActionsTelemetry(
|
|||
logger: Logger,
|
||||
taskManager: TaskManagerSetupContract,
|
||||
core: CoreSetup,
|
||||
preconfiguredActions: PreConfiguredAction[],
|
||||
getInMemoryConnectors: () => InMemoryConnector[],
|
||||
eventLogIndex: string
|
||||
) {
|
||||
registerActionsTelemetryTask(logger, taskManager, core, preconfiguredActions, eventLogIndex);
|
||||
registerActionsTelemetryTask(logger, taskManager, core, getInMemoryConnectors, eventLogIndex);
|
||||
}
|
||||
|
||||
export function scheduleActionsTelemetry(logger: Logger, taskManager: TaskManagerStartContract) {
|
||||
|
@ -38,14 +38,14 @@ function registerActionsTelemetryTask(
|
|||
logger: Logger,
|
||||
taskManager: TaskManagerSetupContract,
|
||||
core: CoreSetup,
|
||||
preconfiguredActions: PreConfiguredAction[],
|
||||
getInMemoryConnectors: () => InMemoryConnector[],
|
||||
eventLogIndex: string
|
||||
) {
|
||||
taskManager.registerTaskDefinitions({
|
||||
[TELEMETRY_TASK_TYPE]: {
|
||||
title: 'Actions usage fetch task',
|
||||
timeout: '5m',
|
||||
createTaskRunner: telemetryTaskRunner(logger, core, preconfiguredActions, eventLogIndex),
|
||||
createTaskRunner: telemetryTaskRunner(logger, core, getInMemoryConnectors, eventLogIndex),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -67,9 +67,17 @@ async function scheduleTasks(logger: Logger, taskManager: TaskManagerStartContra
|
|||
export function telemetryTaskRunner(
|
||||
logger: Logger,
|
||||
core: CoreSetup,
|
||||
preconfiguredActions: PreConfiguredAction[],
|
||||
getInMemoryConnectors: () => InMemoryConnector[],
|
||||
eventLogIndex: string
|
||||
) {
|
||||
/**
|
||||
* Filter out system actions from the
|
||||
* inMemoryConnectors list.
|
||||
*/
|
||||
const inMemoryConnectors = getInMemoryConnectors().filter(
|
||||
(inMemoryConnector) => inMemoryConnector.isPreconfigured
|
||||
);
|
||||
|
||||
return ({ taskInstance }: RunContext) => {
|
||||
const { state } = taskInstance;
|
||||
const getEsClient = () =>
|
||||
|
@ -89,8 +97,8 @@ export function telemetryTaskRunner(
|
|||
const actionIndex = await getActionIndex();
|
||||
const esClient = await getEsClient();
|
||||
return Promise.all([
|
||||
getTotalCount(esClient, actionIndex, logger, preconfiguredActions),
|
||||
getInUseTotalCount(esClient, actionIndex, logger, undefined, preconfiguredActions),
|
||||
getTotalCount(esClient, actionIndex, logger, inMemoryConnectors),
|
||||
getInUseTotalCount(esClient, actionIndex, logger, undefined, inMemoryConnectors),
|
||||
getExecutionsPerDayCount(esClient, eventLogIndex, logger),
|
||||
]).then(([totalAggegations, totalInUse, totalExecutionsPerDay]) => {
|
||||
const hasErrors =
|
||||
|
|
|
@ -354,7 +354,7 @@ describe('Execution Handler', () => {
|
|||
});
|
||||
|
||||
test('throw error message when action type is disabled', async () => {
|
||||
mockActionsPlugin.preconfiguredActions = [];
|
||||
mockActionsPlugin.inMemoryConnectors = [];
|
||||
mockActionsPlugin.isActionExecutable.mockReturnValue(false);
|
||||
mockActionsPlugin.isActionTypeEnabled.mockReturnValue(false);
|
||||
const executionHandler = new ExecutionHandler(
|
||||
|
|
|
@ -79,6 +79,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.index',
|
||||
|
@ -88,6 +89,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.servicenow',
|
||||
|
@ -97,6 +99,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.jira',
|
||||
|
@ -106,6 +109,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.resilient',
|
||||
|
@ -115,6 +119,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.servicenow-sir',
|
||||
|
@ -124,6 +129,7 @@ export const actionTypesMock: ActionTypeConnector[] = [
|
|||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ describe('client', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.servicenow',
|
||||
|
@ -44,6 +45,7 @@ describe('client', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.unsupported',
|
||||
|
@ -53,6 +55,7 @@ describe('client', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.swimlane',
|
||||
|
@ -62,6 +65,7 @@ describe('client', () => {
|
|||
enabledInLicense: false,
|
||||
minimumLicenseRequired: 'basic' as const,
|
||||
supportedFeatureIds: ['alerting', 'cases'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -85,6 +85,7 @@ export const fetchActionTypes = async (): Promise<ActionType[]> => {
|
|||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
supported_feature_ids: supportedFeatureIds,
|
||||
is_system_action_type: isSystemActionType,
|
||||
...res
|
||||
}: AsApiContract<ActionType>) => ({
|
||||
...res,
|
||||
|
@ -92,6 +93,7 @@ export const fetchActionTypes = async (): Promise<ActionType[]> => {
|
|||
enabledInLicense,
|
||||
minimumLicenseRequired,
|
||||
supportedFeatureIds,
|
||||
isSystemActionType,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('loadActionTypes', () => {
|
|||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'basic',
|
||||
is_system_action_type: false,
|
||||
},
|
||||
];
|
||||
http.get.mockResolvedValueOnce(apiResponseValue);
|
||||
|
@ -37,6 +38,7 @@ describe('loadActionTypes', () => {
|
|||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'basic',
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -59,6 +61,7 @@ describe('loadActionTypes', () => {
|
|||
enabled_in_license: true,
|
||||
supported_feature_ids: ['alerting'],
|
||||
minimum_license_required: 'basic',
|
||||
is_system_action_type: false,
|
||||
},
|
||||
];
|
||||
http.get.mockResolvedValueOnce(apiResponseValue);
|
||||
|
@ -72,6 +75,7 @@ describe('loadActionTypes', () => {
|
|||
enabledInLicense: true,
|
||||
supportedFeatureIds: ['alerting'],
|
||||
minimumLicenseRequired: 'basic',
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
|
|
@ -19,12 +19,14 @@ const rewriteBodyReq: RewriteRequestCase<ActionType> = ({
|
|||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
supported_feature_ids: supportedFeatureIds,
|
||||
is_system_action_type: isSystemActionType,
|
||||
...res
|
||||
}: AsApiContract<ActionType>) => ({
|
||||
enabledInConfig,
|
||||
enabledInLicense,
|
||||
minimumLicenseRequired,
|
||||
supportedFeatureIds,
|
||||
isSystemActionType,
|
||||
...res,
|
||||
});
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ test('should sort enabled action types first', async () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
|
@ -27,6 +28,7 @@ test('should sort enabled action types first', async () => {
|
|||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
|
@ -36,6 +38,7 @@ test('should sort enabled action types first', async () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
|
@ -45,6 +48,7 @@ test('should sort enabled action types first', async () => {
|
|||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
const result = [...actionTypes].sort(actionTypeCompare);
|
||||
|
@ -64,6 +68,7 @@ test('should sort by name when all enabled', async () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
|
@ -73,6 +78,7 @@ test('should sort by name when all enabled', async () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
|
@ -82,6 +88,7 @@ test('should sort by name when all enabled', async () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
|
@ -91,6 +98,7 @@ test('should sort by name when all enabled', async () => {
|
|||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
const result = [...actionTypes].sort(actionTypeCompare);
|
||||
|
|
|
@ -29,6 +29,7 @@ describe('checkActionTypeEnabled', () => {
|
|||
enabled: true,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
};
|
||||
expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -46,6 +47,7 @@ describe('checkActionTypeEnabled', () => {
|
|||
enabled: false,
|
||||
enabledInConfig: true,
|
||||
enabledInLicense: false,
|
||||
isSystemActionType: false,
|
||||
};
|
||||
expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -81,6 +83,7 @@ describe('checkActionTypeEnabled', () => {
|
|||
enabled: false,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
};
|
||||
expect(checkActionTypeEnabled(actionType)).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
|
@ -127,6 +130,7 @@ describe('checkActionFormActionTypeEnabled', () => {
|
|||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
};
|
||||
|
||||
expect(checkActionFormActionTypeEnabled(actionType, preconfiguredConnectors))
|
||||
|
@ -146,6 +150,7 @@ describe('checkActionFormActionTypeEnabled', () => {
|
|||
enabled: true,
|
||||
enabledInConfig: false,
|
||||
enabledInLicense: true,
|
||||
isSystemActionType: false,
|
||||
};
|
||||
expect(checkActionFormActionTypeEnabled(actionType, preconfiguredConnectors))
|
||||
.toMatchInlineSnapshot(`
|
||||
|
|
|
@ -605,6 +605,7 @@ function getActionTypeForm({
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
'.server-log': {
|
||||
id: '.server-log',
|
||||
|
@ -614,6 +615,7 @@ function getActionTypeForm({
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -58,6 +58,7 @@ describe('connector_add_modal', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
};
|
||||
|
||||
const wrapper = mountWithIntl(
|
||||
|
@ -100,6 +101,7 @@ describe('connector_add_modal', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<ConnectorAddModal
|
||||
|
@ -139,6 +141,7 @@ describe('connector_add_modal', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
};
|
||||
const wrapper = mountWithIntl(
|
||||
<ConnectorAddModal
|
||||
|
|
|
@ -57,6 +57,7 @@ describe('connectors_selection', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -216,6 +216,7 @@ describe('rule_details', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -259,6 +260,7 @@ describe('rule_details', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
{
|
||||
id: '.email',
|
||||
|
@ -268,6 +270,7 @@ describe('rule_details', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -338,6 +341,7 @@ describe('rule_details', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
ruleTypeRegistry.has.mockReturnValue(true);
|
||||
|
@ -468,6 +472,7 @@ describe('rule_details', () => {
|
|||
enabledInLicense: true,
|
||||
minimumLicenseRequired: 'basic',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
];
|
||||
ruleTypeRegistry.has.mockReturnValue(true);
|
||||
|
|
|
@ -24,6 +24,7 @@ describe('settings', () => {
|
|||
minimumLicenseRequired: 'gold',
|
||||
name: 'Slack',
|
||||
supportedFeatureIds: ['uptime'],
|
||||
isSystemActionType: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -172,6 +172,7 @@ export const fetchActionTypes = async (): Promise<ActionType[]> => {
|
|||
enabled_in_license: enabledInLicense,
|
||||
minimum_license_required: minimumLicenseRequired,
|
||||
supported_feature_ids: supportedFeatureIds,
|
||||
is_system_action_type: isSystemActionType,
|
||||
...res
|
||||
}: AsApiContract<ActionType>) => ({
|
||||
...res,
|
||||
|
@ -179,6 +180,7 @@ export const fetchActionTypes = async (): Promise<ActionType[]> => {
|
|||
enabledInLicense,
|
||||
minimumLicenseRequired,
|
||||
supportedFeatureIds,
|
||||
isSystemActionType,
|
||||
})
|
||||
);
|
||||
};
|
||||
|
|
|
@ -64,6 +64,7 @@ const enabledActionTypes = [
|
|||
'test.throw',
|
||||
'test.excluded',
|
||||
'test.capped',
|
||||
'test.system-action',
|
||||
];
|
||||
|
||||
export function createTestConfig(name: string, options: CreateTestConfigOptions) {
|
||||
|
|
|
@ -74,6 +74,7 @@ export function defineActionTypes(
|
|||
actions.registerType(getNoAttemptsRateLimitedActionType());
|
||||
actions.registerType(getAuthorizationActionType(core));
|
||||
actions.registerType(getExcludedActionType());
|
||||
actions.registerType(getSystemActionType());
|
||||
|
||||
/** Sub action framework */
|
||||
|
||||
|
@ -399,3 +400,29 @@ function getExcludedActionType() {
|
|||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
function getSystemActionType() {
|
||||
const result: ActionType<{}, {}, {}> = {
|
||||
id: 'test.system-action',
|
||||
name: 'Test system action',
|
||||
minimumLicenseRequired: 'platinum',
|
||||
supportedFeatureIds: ['alerting'],
|
||||
validate: {
|
||||
params: {
|
||||
schema: schema.any(),
|
||||
},
|
||||
config: {
|
||||
schema: schema.any(),
|
||||
},
|
||||
secrets: {
|
||||
schema: schema.any(),
|
||||
},
|
||||
},
|
||||
isSystemActionType: true,
|
||||
async executor({ config, secrets, params, services, actionId }) {
|
||||
return { status: 'ok', actionId };
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -319,6 +319,83 @@ export default function createActionTests({ getService }: FtrProviderContext) {
|
|||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it(`shouldn't create a preconfigured action with the same id as an existing one`, async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector/custom-system-abc-connector`)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My action',
|
||||
connector_type_id: 'system-abc-action-type',
|
||||
config: {},
|
||||
secrets: {},
|
||||
});
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'global_read at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
case 'space_1_all at space2':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message: 'Unauthorized to create a "system-abc-action-type" action',
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'This custom-system-abc-connector already exists in a preconfigured action.',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it(`shouldn't create a system action`, async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.post(`${getUrlPrefix(space.id)}/api/actions/connector`)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My system action',
|
||||
connector_type_id: 'test.system-action',
|
||||
config: {},
|
||||
secrets: {},
|
||||
});
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'global_read at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
case 'space_1_all at space2':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message: 'Unauthorized to create a "test.system-action" action',
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'System action creation is forbidden. Action type: test.system-action.',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -146,7 +146,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) {
|
|||
}
|
||||
});
|
||||
|
||||
it(`shouldn't delete action from preconfigured list`, async () => {
|
||||
it(`shouldn't delete preconfigured action`, async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.delete(`${getUrlPrefix(space.id)}/api/actions/connector/my-slack1`)
|
||||
.auth(user.username, user.password)
|
||||
|
@ -177,6 +177,41 @@ export default function deleteActionTests({ getService }: FtrProviderContext) {
|
|||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it(`shouldn't delete system action`, async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.delete(
|
||||
`${getUrlPrefix(space.id)}/api/actions/connector/system-connector-test.system-action`
|
||||
)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo');
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
case 'global_read at space1':
|
||||
case 'space_1_all at space2':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message: 'Unauthorized to delete actions',
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'System action system-connector-test.system-action is not allowed to delete.',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -160,6 +160,43 @@ export default function getActionTests({ getService }: FtrProviderContext) {
|
|||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it('should handle get system action request appropriately', async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.get(
|
||||
`${getUrlPrefix(space.id)}/api/actions/connector/system-connector-test.system-action`
|
||||
)
|
||||
.auth(user.username, user.password);
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
case 'space_1_all at space2':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message: 'Unauthorized to get actions',
|
||||
});
|
||||
break;
|
||||
case 'global_read at space1':
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.statusCode).to.eql(200);
|
||||
expect(response.body).to.eql({
|
||||
id: 'system-connector-test.system-action',
|
||||
connector_type_id: 'test.system-action',
|
||||
name: 'System action: test.system-action',
|
||||
is_preconfigured: false,
|
||||
is_system_action: true,
|
||||
is_deprecated: false,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -125,6 +125,15 @@ export default function getAllActionTests({ getService }: FtrProviderContext) {
|
|||
name: 'Slack#xyz',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
connector_type_id: 'test.system-action',
|
||||
id: 'system-connector-test.system-action',
|
||||
is_deprecated: false,
|
||||
is_preconfigured: false,
|
||||
is_system_action: true,
|
||||
name: 'System action: test.system-action',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom-system-abc-connector',
|
||||
is_preconfigured: true,
|
||||
|
@ -285,6 +294,15 @@ export default function getAllActionTests({ getService }: FtrProviderContext) {
|
|||
name: 'Slack#xyz',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
connector_type_id: 'test.system-action',
|
||||
id: 'system-connector-test.system-action',
|
||||
is_deprecated: false,
|
||||
is_preconfigured: false,
|
||||
is_system_action: true,
|
||||
name: 'System action: test.system-action',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom-system-abc-connector',
|
||||
is_preconfigured: true,
|
||||
|
@ -408,6 +426,15 @@ export default function getAllActionTests({ getService }: FtrProviderContext) {
|
|||
name: 'Slack#xyz',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
connector_type_id: 'test.system-action',
|
||||
id: 'system-connector-test.system-action',
|
||||
is_deprecated: false,
|
||||
is_preconfigured: false,
|
||||
is_system_action: true,
|
||||
name: 'System action: test.system-action',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom-system-abc-connector',
|
||||
is_preconfigured: true,
|
||||
|
|
|
@ -312,7 +312,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) {
|
|||
}
|
||||
});
|
||||
|
||||
it(`shouldn't update action from preconfigured list`, async () => {
|
||||
it(`shouldn't update a preconfigured action`, async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.put(`${getUrlPrefix(space.id)}/api/actions/connector/custom-system-abc-connector`)
|
||||
.auth(user.username, user.password)
|
||||
|
@ -345,7 +345,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) {
|
|||
expect(response.body).to.eql({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: `Preconfigured action custom-system-abc-connector is not allowed to update.`,
|
||||
message: `Preconfigured action custom-system-abc-connector can not be updated.`,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
|
@ -387,6 +387,49 @@ export default function updateActionTests({ getService }: FtrProviderContext) {
|
|||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
|
||||
it(`shouldn't update a system action`, async () => {
|
||||
const response = await supertestWithoutAuth
|
||||
.put(
|
||||
`${getUrlPrefix(space.id)}/api/actions/connector/system-connector-test.system-action`
|
||||
)
|
||||
.auth(user.username, user.password)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My action updated',
|
||||
config: {
|
||||
unencrypted: `This value shouldn't get encrypted`,
|
||||
},
|
||||
secrets: {
|
||||
encrypted: 'This value should be encrypted',
|
||||
},
|
||||
});
|
||||
|
||||
switch (scenario.id) {
|
||||
case 'no_kibana_privileges at space1':
|
||||
case 'space_1_all_alerts_none_actions at space1':
|
||||
case 'space_1_all at space2':
|
||||
case 'global_read at space1':
|
||||
expect(response.statusCode).to.eql(403);
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 403,
|
||||
error: 'Forbidden',
|
||||
message: 'Unauthorized to update actions',
|
||||
});
|
||||
break;
|
||||
case 'superuser at space1':
|
||||
case 'space_1_all at space1':
|
||||
case 'space_1_all_with_restricted_fixture at space1':
|
||||
expect(response.body).to.eql({
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'System action system-connector-test.system-action can not be updated.',
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -80,6 +80,40 @@ export default function createActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it(`shouldn't create a preconfigured action with the same id as an existing one`, async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/custom-system-abc-connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My action',
|
||||
connector_type_id: 'system-abc-action-type',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'This custom-system-abc-connector already exists in a preconfigured action.',
|
||||
});
|
||||
});
|
||||
|
||||
it(`shouldn't create a system action`, async () => {
|
||||
await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My system action',
|
||||
connector_type_id: 'test.system-action',
|
||||
config: {},
|
||||
secrets: {},
|
||||
})
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'System action creation is forbidden. Action type: test.system-action.',
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should handle create action request appropriately', async () => {
|
||||
const response = await supertest
|
||||
|
|
|
@ -78,7 +78,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it(`shouldn't delete action from preconfigured list`, async () => {
|
||||
it(`shouldn't delete a preconfigured action`, async () => {
|
||||
await supertest
|
||||
.delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/my-slack1`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -89,6 +89,21 @@ export default function deleteActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it(`shouldn't delete a system action`, async () => {
|
||||
await supertest
|
||||
.delete(
|
||||
`${getUrlPrefix(
|
||||
Spaces.space1.id
|
||||
)}/api/actions/connector/system-connector-test.system-action`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'System action system-connector-test.system-action is not allowed to delete.',
|
||||
});
|
||||
});
|
||||
|
||||
describe('legacy', () => {
|
||||
it('should handle delete action request appropriately', async () => {
|
||||
const { body: createdAction } = await supertest
|
||||
|
@ -150,7 +165,7 @@ export default function deleteActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it(`shouldn't delete action from preconfigured list`, async () => {
|
||||
it(`shouldn't delete a preconfigured action`, async () => {
|
||||
await supertest
|
||||
.delete(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/my-slack1`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -160,6 +175,21 @@ export default function deleteActionTests({ getService }: FtrProviderContext) {
|
|||
message: `Preconfigured action my-slack1 is not allowed to delete.`,
|
||||
});
|
||||
});
|
||||
|
||||
it(`shouldn't delete a system action`, async () => {
|
||||
await supertest
|
||||
.delete(
|
||||
`${getUrlPrefix(
|
||||
Spaces.space1.id
|
||||
)}/api/actions/action/system-connector-test.system-action`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'System action system-connector-test.system-action is not allowed to delete.',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ export default function getActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should handle get action request from preconfigured list', async () => {
|
||||
it('should handle get a preconfigured connector', async () => {
|
||||
await supertest
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/my-slack1`)
|
||||
.expect(200, {
|
||||
|
@ -90,7 +90,24 @@ export default function getActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should handle get action request for deprecated connectors from preconfigured list', async () => {
|
||||
it('should handle get a system connector', async () => {
|
||||
await supertest
|
||||
.get(
|
||||
`${getUrlPrefix(
|
||||
Spaces.space1.id
|
||||
)}/api/actions/connector/system-connector-test.system-action`
|
||||
)
|
||||
.expect(200, {
|
||||
id: 'system-connector-test.system-action',
|
||||
connector_type_id: 'test.system-action',
|
||||
name: 'System action: test.system-action',
|
||||
is_preconfigured: false,
|
||||
is_system_action: true,
|
||||
is_deprecated: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle get a deprecated connector', async () => {
|
||||
await supertest
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/my-deprecated-servicenow`)
|
||||
.expect(200, {
|
||||
|
@ -176,7 +193,7 @@ export default function getActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should handle get action request from preconfigured list', async () => {
|
||||
it('should handle get a preconfigured connector', async () => {
|
||||
await supertest
|
||||
.get(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/my-slack1`)
|
||||
.expect(200, {
|
||||
|
@ -188,6 +205,23 @@ export default function getActionTests({ getService }: FtrProviderContext) {
|
|||
name: 'Slack#xyz',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle get a system connector', async () => {
|
||||
await supertest
|
||||
.get(
|
||||
`${getUrlPrefix(
|
||||
Spaces.space1.id
|
||||
)}/api/actions/action/system-connector-test.system-action`
|
||||
)
|
||||
.expect(200, {
|
||||
id: 'system-connector-test.system-action',
|
||||
actionTypeId: 'test.system-action',
|
||||
name: 'System action: test.system-action',
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
isDeprecated: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -114,6 +114,15 @@ export default function getAllActionTests({ getService }: FtrProviderContext) {
|
|||
name: 'Slack#xyz',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
connector_type_id: 'test.system-action',
|
||||
id: 'system-connector-test.system-action',
|
||||
is_deprecated: false,
|
||||
is_preconfigured: false,
|
||||
is_system_action: true,
|
||||
name: 'System action: test.system-action',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom-system-abc-connector',
|
||||
is_preconfigured: true,
|
||||
|
@ -226,6 +235,15 @@ export default function getAllActionTests({ getService }: FtrProviderContext) {
|
|||
name: 'Slack#xyz',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
connector_type_id: 'test.system-action',
|
||||
id: 'system-connector-test.system-action',
|
||||
is_deprecated: false,
|
||||
is_preconfigured: false,
|
||||
is_system_action: true,
|
||||
name: 'System action: test.system-action',
|
||||
referenced_by_count: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom-system-abc-connector',
|
||||
is_preconfigured: true,
|
||||
|
@ -352,6 +370,15 @@ export default function getAllActionTests({ getService }: FtrProviderContext) {
|
|||
name: 'Slack#xyz',
|
||||
referencedByCount: 0,
|
||||
},
|
||||
{
|
||||
actionTypeId: 'test.system-action',
|
||||
id: 'system-connector-test.system-action',
|
||||
isDeprecated: false,
|
||||
isPreconfigured: false,
|
||||
isSystemAction: true,
|
||||
name: 'System action: test.system-action',
|
||||
referencedByCount: 0,
|
||||
},
|
||||
{
|
||||
id: 'custom-system-abc-connector',
|
||||
isPreconfigured: true,
|
||||
|
|
|
@ -149,7 +149,7 @@ export default function createUnsecuredActionTests({ getService }: FtrProviderCo
|
|||
);
|
||||
});
|
||||
|
||||
it('should not allow scheduling action from non preconfigured connectors', async () => {
|
||||
it('should not allow scheduling action from non in-memory connectors', async () => {
|
||||
const response = await supertest
|
||||
.post(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -184,7 +184,7 @@ export default function createUnsecuredActionTests({ getService }: FtrProviderCo
|
|||
.expect(200);
|
||||
expect(result.status).to.eql('error');
|
||||
expect(result.error).to.eql(
|
||||
`Error: ${connectorId} are not preconfigured connectors and can't be scheduled for unsecured actions execution`
|
||||
`Error: ${connectorId} are not in-memory connectors and can't be scheduled for unsecured actions execution`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -106,7 +106,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it(`shouldn't update action from preconfigured list`, async () => {
|
||||
it(`shouldn't update a preconfigured connector`, async () => {
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/connector/custom-system-abc-connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -122,7 +122,31 @@ export default function updateActionTests({ getService }: FtrProviderContext) {
|
|||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: `Preconfigured action custom-system-abc-connector is not allowed to update.`,
|
||||
message: `Preconfigured action custom-system-abc-connector can not be updated.`,
|
||||
});
|
||||
});
|
||||
|
||||
it(`shouldn't update a system connector`, async () => {
|
||||
await supertest
|
||||
.put(
|
||||
`${getUrlPrefix(
|
||||
Spaces.space1.id
|
||||
)}/api/actions/connector/system-connector-test.system-action`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My action updated',
|
||||
config: {
|
||||
unencrypted: `This value shouldn't get encrypted`,
|
||||
},
|
||||
secrets: {
|
||||
encrypted: 'This value should be encrypted',
|
||||
},
|
||||
})
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'System action system-connector-test.system-action can not be updated.',
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -270,7 +294,7 @@ export default function updateActionTests({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it(`shouldn't update action from preconfigured list`, async () => {
|
||||
it(`shouldn't update a preconfigured connector`, async () => {
|
||||
await supertest
|
||||
.put(`${getUrlPrefix(Spaces.space1.id)}/api/actions/action/custom-system-abc-connector`)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
|
@ -286,7 +310,31 @@ export default function updateActionTests({ getService }: FtrProviderContext) {
|
|||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: `Preconfigured action custom-system-abc-connector is not allowed to update.`,
|
||||
message: `Preconfigured action custom-system-abc-connector can not be updated.`,
|
||||
});
|
||||
});
|
||||
|
||||
it(`shouldn't update a system connector`, async () => {
|
||||
await supertest
|
||||
.put(
|
||||
`${getUrlPrefix(
|
||||
Spaces.space1.id
|
||||
)}/api/actions/action/system-connector-test.system-action`
|
||||
)
|
||||
.set('kbn-xsrf', 'foo')
|
||||
.send({
|
||||
name: 'My action updated',
|
||||
config: {
|
||||
unencrypted: `This value shouldn't get encrypted`,
|
||||
},
|
||||
secrets: {
|
||||
encrypted: 'This value should be encrypted',
|
||||
},
|
||||
})
|
||||
.expect(400, {
|
||||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message: 'System action system-connector-test.system-action can not be updated.',
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue