diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.mock.ts b/x-pack/plugins/actions/server/actions_client/actions_client.mock.ts index 5a369272617a27..d58476738b9be5 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.mock.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.mock.ts @@ -18,6 +18,7 @@ const createActionsClientMock = () => { delete: jest.fn(), update: jest.fn(), getAll: jest.fn(), + getAllSystemConnectors: jest.fn(), getBulk: jest.fn(), getOAuthAccessToken: jest.fn(), execute: jest.fn(), diff --git a/x-pack/plugins/actions/server/actions_client/actions_client.ts b/x-pack/plugins/actions/server/actions_client/actions_client.ts index f5cf3957217952..b950096fa10002 100644 --- a/x-pack/plugins/actions/server/actions_client/actions_client.ts +++ b/x-pack/plugins/actions/server/actions_client/actions_client.ts @@ -90,6 +90,7 @@ import { } from '../lib/get_execution_log_aggregation'; import { connectorFromSavedObject, isConnectorDeprecated } from '../application/connector/lib'; import { ListTypesParams } from '../application/connector/methods/list_types/types'; +import { getAllSystemConnectors } from '../application/connector/methods/get_all/get_all'; interface ActionUpdate { name: string; @@ -481,6 +482,13 @@ export class ActionsClient { return getAll({ context: this.context, includeSystemActions }); } + /** + * Get all system connectors + */ + public async getAllSystemConnectors(): Promise { + return getAllSystemConnectors({ context: this.context }); + } + /** * Get bulk actions with in-memory list */ diff --git a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts index 2e264300490f87..3fec4d01d336a3 100644 --- a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts +++ b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.test.ts @@ -108,8 +108,152 @@ describe('getAll()', () => { getEventLogClient.mockResolvedValue(eventLogClient); }); - describe('authorization', () => { - function getAllOperation(): ReturnType { + describe('getAll()', () => { + describe('authorization', () => { + function getAllOperation(): ReturnType { + const expectedResult = { + total: 1, + per_page: 10, + page: 1, + saved_objects: [ + { + id: '1', + type: 'type', + attributes: { + name: 'test', + config: { + foo: 'bar', + }, + }, + score: 1, + references: [], + }, + ], + }; + unsecuredSavedObjectsClient.find.mockResolvedValueOnce(expectedResult); + scopedClusterClient.asInternalUser.search.mockResponse( + // @ts-expect-error not full search response + { + aggregations: { + '1': { doc_count: 6 }, + testPreconfigured: { doc_count: 2 }, + }, + } + ); + + actionsClient = new ActionsClient({ + logger, + actionTypeRegistry, + unsecuredSavedObjectsClient, + scopedClusterClient, + kibanaIndices, + actionExecutor, + ephemeralExecutionEnqueuer, + bulkExecutionEnqueuer, + request, + authorization: authorization as unknown as ActionsAuthorization, + inMemoryConnectors: [ + { + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: {}, + isPreconfigured: true, + isDeprecated: false, + isSystemAction: false, + name: 'test', + config: { + foo: 'bar', + }, + }, + ], + connectorTokenClient: connectorTokenClientMock.create(), + getEventLogClient, + }); + return actionsClient.getAll(); + } + + test('ensures user is authorised to get the type of action', async () => { + await getAllOperation(); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); + }); + + test('throws when user is not authorised to create the type of action', async () => { + authorization.ensureAuthorized.mockRejectedValue( + new Error(`Unauthorized to get all actions`) + ); + + await expect(getAllOperation()).rejects.toMatchInlineSnapshot( + `[Error: Unauthorized to get all actions]` + ); + + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); + }); + }); + + describe('auditLogger', () => { + test('logs audit event when searching connectors', async () => { + unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ + total: 1, + per_page: 10, + page: 1, + saved_objects: [ + { + id: '1', + type: 'type', + attributes: { + name: 'test', + isMissingSecrets: false, + config: { + foo: 'bar', + }, + }, + score: 1, + references: [], + }, + ], + }); + + scopedClusterClient.asInternalUser.search.mockResponse( + // @ts-expect-error not full search response + { + aggregations: { + '1': { doc_count: 6 }, + testPreconfigured: { doc_count: 2 }, + }, + } + ); + + await actionsClient.getAll(); + + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action: 'connector_find', + outcome: 'success', + }), + kibana: { saved_object: { id: '1', type: 'action' } }, + }) + ); + }); + + test('logs audit event when not authorised to search connectors', async () => { + authorization.ensureAuthorized.mockRejectedValue(new Error('Unauthorized')); + + await expect(actionsClient.getAll()).rejects.toThrow(); + + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action: 'connector_find', + outcome: 'failure', + }), + error: { code: 'Error', message: 'Unauthorized' }, + }) + ); + }); + }); + + test('calls unsecuredSavedObjectsClient with parameters and returns inMemoryConnectors correctly', async () => { const expectedResult = { total: 1, per_page: 10, @@ -120,6 +264,7 @@ describe('getAll()', () => { type: 'type', attributes: { name: 'test', + isMissingSecrets: false, config: { foo: 'bar', }, @@ -136,6 +281,7 @@ describe('getAll()', () => { aggregations: { '1': { doc_count: 6 }, testPreconfigured: { doc_count: 2 }, + 'system-connector-.cases': { doc_count: 2 }, }, } ); @@ -164,34 +310,54 @@ describe('getAll()', () => { foo: 'bar', }, }, + /** + * System actions will not + * be returned from getAll + * if no options are provided + */ + { + id: 'system-connector-.cases', + actionTypeId: '.cases', + name: 'System action: .cases', + config: {}, + secrets: {}, + isDeprecated: false, + isMissingSecrets: false, + isPreconfigured: false, + isSystemAction: true, + }, ], connectorTokenClient: connectorTokenClientMock.create(), getEventLogClient, }); - return actionsClient.getAll(); - } - - test('ensures user is authorised to get the type of action', async () => { - await getAllOperation(); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); - }); - test('throws when user is not authorised to create the type of action', async () => { - authorization.ensureAuthorized.mockRejectedValue( - new Error(`Unauthorized to get all actions`) - ); + const result = await actionsClient.getAll(); - await expect(getAllOperation()).rejects.toMatchInlineSnapshot( - `[Error: Unauthorized to get all actions]` - ); - - expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); + expect(result).toEqual([ + { + id: '1', + name: 'test', + 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, + referencedByCount: 2, + }, + ]); }); - }); - describe('auditLogger', () => { - test('logs audit event when searching connectors', async () => { - unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ + test('get system actions correctly', async () => { + const expectedResult = { total: 1, per_page: 10, page: 1, @@ -210,344 +376,328 @@ describe('getAll()', () => { references: [], }, ], - }); + }; + unsecuredSavedObjectsClient.find.mockResolvedValueOnce(expectedResult); scopedClusterClient.asInternalUser.search.mockResponse( // @ts-expect-error not full search response { aggregations: { '1': { doc_count: 6 }, testPreconfigured: { doc_count: 2 }, + 'system-connector-.cases': { doc_count: 2 }, }, } ); - await actionsClient.getAll(); - - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: expect.objectContaining({ - action: 'connector_find', - outcome: 'success', - }), - kibana: { saved_object: { id: '1', type: 'action' } }, - }) - ); - }); - - test('logs audit event when not authorised to search connectors', async () => { - authorization.ensureAuthorized.mockRejectedValue(new Error('Unauthorized')); - - await expect(actionsClient.getAll()).rejects.toThrow(); - - expect(auditLogger.log).toHaveBeenCalledWith( - expect.objectContaining({ - event: expect.objectContaining({ - action: 'connector_find', - outcome: 'failure', - }), - error: { code: 'Error', message: 'Unauthorized' }, - }) - ); - }); - }); - - test('calls unsecuredSavedObjectsClient with parameters and returns inMemoryConnectors correctly', async () => { - const expectedResult = { - total: 1, - per_page: 10, - page: 1, - saved_objects: [ - { - id: '1', - type: 'type', - attributes: { + actionsClient = new ActionsClient({ + logger, + actionTypeRegistry, + unsecuredSavedObjectsClient, + scopedClusterClient, + kibanaIndices, + actionExecutor, + ephemeralExecutionEnqueuer, + bulkExecutionEnqueuer, + request, + authorization: authorization as unknown as ActionsAuthorization, + inMemoryConnectors: [ + { + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: {}, + isPreconfigured: true, + isDeprecated: false, + isSystemAction: false, name: 'test', - isMissingSecrets: false, config: { foo: 'bar', }, }, - score: 1, - references: [], + { + 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({ includeSystemActions: true }); + + expect(result).toEqual([ + { + actionTypeId: '.cases', + id: 'system-connector-.cases', + isDeprecated: false, + isPreconfigured: false, + isSystemAction: true, + name: 'System action: .cases', + referencedByCount: 2, }, - ], - }; - unsecuredSavedObjectsClient.find.mockResolvedValueOnce(expectedResult); - scopedClusterClient.asInternalUser.search.mockResponse( - // @ts-expect-error not full search response - { - aggregations: { - '1': { doc_count: 6 }, - testPreconfigured: { doc_count: 2 }, - 'system-connector-.cases': { doc_count: 2 }, + { + id: '1', + name: 'test', + isMissingSecrets: false, + config: { foo: 'bar' }, + isPreconfigured: false, + isDeprecated: false, + isSystemAction: false, + referencedByCount: 6, }, - } - ); - - actionsClient = new ActionsClient({ - logger, - actionTypeRegistry, - unsecuredSavedObjectsClient, - scopedClusterClient, - kibanaIndices, - actionExecutor, - ephemeralExecutionEnqueuer, - bulkExecutionEnqueuer, - request, - authorization: authorization as unknown as ActionsAuthorization, - inMemoryConnectors: [ { id: 'testPreconfigured', actionTypeId: '.slack', - secrets: {}, + name: 'test', isPreconfigured: true, - isDeprecated: false, isSystemAction: false, - name: 'test', - config: { - foo: 'bar', - }, - }, - /** - * System actions will not - * be returned from getAll - * if no options are provided - */ - { - id: 'system-connector-.cases', - actionTypeId: '.cases', - name: 'System action: .cases', - config: {}, - secrets: {}, isDeprecated: false, - isMissingSecrets: false, - isPreconfigured: false, - isSystemAction: true, + referencedByCount: 2, }, - ], - connectorTokenClient: connectorTokenClientMock.create(), - getEventLogClient, + ]); }); - const result = await actionsClient.getAll(); - - expect(result).toEqual([ - { - id: '1', - name: 'test', - 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, - referencedByCount: 2, - }, - ]); - }); - - test('get system actions correctly', async () => { - const expectedResult = { - total: 1, - per_page: 10, - page: 1, - saved_objects: [ + test('validates connectors before return', async () => { + unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ + total: 1, + per_page: 10, + page: 1, + saved_objects: [ + { + id: '1', + type: 'type', + attributes: { + name: 'test', + isMissingSecrets: false, + config: { + foo: 'bar', + }, + }, + score: 1, + references: [], + }, + ], + }); + scopedClusterClient.asInternalUser.search.mockResponse( + // @ts-expect-error not full search response { - id: '1', - type: 'type', - attributes: { + aggregations: { + '1': { doc_count: 6 }, + testPreconfigured: { doc_count: 2 }, + }, + } + ); + + actionsClient = new ActionsClient({ + logger, + actionTypeRegistry, + unsecuredSavedObjectsClient, + scopedClusterClient, + kibanaIndices, + actionExecutor, + ephemeralExecutionEnqueuer, + bulkExecutionEnqueuer, + request, + authorization: authorization as unknown as ActionsAuthorization, + inMemoryConnectors: [ + { + id: 'testPreconfigured', + actionTypeId: '.slack', + secrets: {}, + isPreconfigured: true, + isDeprecated: false, + isSystemAction: false, name: 'test', - isMissingSecrets: false, config: { foo: 'bar', }, }, - score: 1, - references: [], - }, - ], - }; - unsecuredSavedObjectsClient.find.mockResolvedValueOnce(expectedResult); - scopedClusterClient.asInternalUser.search.mockResponse( - // @ts-expect-error not full search response - { - aggregations: { - '1': { doc_count: 6 }, - testPreconfigured: { doc_count: 2 }, - 'system-connector-.cases': { doc_count: 2 }, - }, - } - ); + ], + connectorTokenClient: connectorTokenClientMock.create(), + getEventLogClient, + }); - actionsClient = new ActionsClient({ - logger, - actionTypeRegistry, - unsecuredSavedObjectsClient, - scopedClusterClient, - kibanaIndices, - actionExecutor, - ephemeralExecutionEnqueuer, - bulkExecutionEnqueuer, - request, - authorization: authorization as unknown as ActionsAuthorization, - inMemoryConnectors: [ + const result = await actionsClient.getAll({ includeSystemActions: true }); + expect(result).toEqual([ { - id: 'testPreconfigured', - actionTypeId: '.slack', - secrets: {}, - isPreconfigured: true, - isDeprecated: false, - isSystemAction: false, - name: 'test', config: { foo: 'bar', }, - }, - { - id: 'system-connector-.cases', - actionTypeId: '.cases', - name: 'System action: .cases', - config: {}, - secrets: {}, + id: '1', isDeprecated: false, isMissingSecrets: false, isPreconfigured: false, - isSystemAction: true, + isSystemAction: false, + name: 'test', + referencedByCount: 6, }, - ], - connectorTokenClient: connectorTokenClientMock.create(), - getEventLogClient, - }); + { + actionTypeId: '.slack', + id: 'testPreconfigured', + isDeprecated: false, + isPreconfigured: true, + isSystemAction: false, + name: 'test', + referencedByCount: 2, + }, + ]); - const result = await actionsClient.getAll({ includeSystemActions: true }); - - expect(result).toEqual([ - { - actionTypeId: '.cases', - id: 'system-connector-.cases', - isDeprecated: false, - isPreconfigured: false, - isSystemAction: true, - name: 'System action: .cases', - referencedByCount: 2, - }, - { - id: '1', - name: 'test', - 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, - referencedByCount: 2, - }, - ]); + expect(logger.warn).toHaveBeenCalledWith( + 'Error validating connector: 1, Error: [actionTypeId]: expected value of type [string] but got [undefined]' + ); + }); }); - test('validates connectors before return', async () => { - unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ - total: 1, - per_page: 10, - page: 1, - saved_objects: [ + describe('getAllSystemConnectors()', () => { + describe('authorization', () => { + function getAllOperation(): ReturnType { + scopedClusterClient.asInternalUser.search.mockResponse( + // @ts-expect-error not full search response + { + aggregations: { + 'system-connector-.test': { doc_count: 2 }, + }, + } + ); + + actionsClient = new ActionsClient({ + logger, + actionTypeRegistry, + unsecuredSavedObjectsClient, + scopedClusterClient, + kibanaIndices, + actionExecutor, + ephemeralExecutionEnqueuer, + bulkExecutionEnqueuer, + request, + authorization: authorization as unknown as ActionsAuthorization, + inMemoryConnectors: [ + { + id: 'system-connector-.test', + actionTypeId: '.test', + name: 'Test system action', + config: {}, + secrets: {}, + isDeprecated: false, + isMissingSecrets: false, + isPreconfigured: false, + isSystemAction: true, + }, + ], + connectorTokenClient: connectorTokenClientMock.create(), + getEventLogClient, + }); + + return actionsClient.getAllSystemConnectors(); + } + + test('ensures user is authorised to get the type of action', async () => { + await getAllOperation(); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); + }); + + test('throws when user is not authorised to get the type of action', async () => { + authorization.ensureAuthorized.mockRejectedValue( + new Error(`Unauthorized to get all actions`) + ); + + await expect(getAllOperation()).rejects.toMatchInlineSnapshot( + `[Error: Unauthorized to get all actions]` + ); + + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ operation: 'get' }); + }); + }); + + describe('auditLogger', () => { + test('logs audit event when not authorised to search connectors', async () => { + authorization.ensureAuthorized.mockRejectedValue(new Error('Unauthorized')); + + await expect(actionsClient.getAllSystemConnectors()).rejects.toThrow(); + + expect(auditLogger.log).toHaveBeenCalledWith( + expect.objectContaining({ + event: expect.objectContaining({ + action: 'connector_find', + outcome: 'failure', + }), + error: { code: 'Error', message: 'Unauthorized' }, + }) + ); + }); + }); + + test('get all system actions correctly', async () => { + scopedClusterClient.asInternalUser.search.mockResponse( + // @ts-expect-error not full search response { - id: '1', - type: 'type', - attributes: { + aggregations: { + 'system-connector-.test': { doc_count: 2 }, + }, + } + ); + + actionsClient = new ActionsClient({ + logger, + actionTypeRegistry, + unsecuredSavedObjectsClient, + scopedClusterClient, + kibanaIndices, + actionExecutor, + 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', - isMissingSecrets: false, config: { foo: 'bar', }, }, - score: 1, - references: [], - }, - ], - }); - scopedClusterClient.asInternalUser.search.mockResponse( - // @ts-expect-error not full search response - { - aggregations: { - '1': { doc_count: 6 }, - testPreconfigured: { doc_count: 2 }, - }, - } - ); + { + id: 'system-connector-.test', + actionTypeId: '.test', + name: 'Test system action', + config: {}, + secrets: {}, + isDeprecated: false, + isMissingSecrets: false, + isPreconfigured: false, + isSystemAction: true, + }, + ], + connectorTokenClient: connectorTokenClientMock.create(), + getEventLogClient, + }); - actionsClient = new ActionsClient({ - logger, - actionTypeRegistry, - unsecuredSavedObjectsClient, - scopedClusterClient, - kibanaIndices, - actionExecutor, - ephemeralExecutionEnqueuer, - bulkExecutionEnqueuer, - request, - authorization: authorization as unknown as ActionsAuthorization, - inMemoryConnectors: [ + const result = await actionsClient.getAllSystemConnectors(); + + expect(result).toEqual([ { - id: 'testPreconfigured', - actionTypeId: '.slack', - secrets: {}, - isPreconfigured: true, + id: 'system-connector-.test', + actionTypeId: '.test', + name: 'Test system action', + isPreconfigured: false, isDeprecated: false, - isSystemAction: false, - name: 'test', - config: { - foo: 'bar', - }, + isSystemAction: true, + referencedByCount: 2, }, - ], - connectorTokenClient: connectorTokenClientMock.create(), - getEventLogClient, + ]); }); - - const result = await actionsClient.getAll({ includeSystemActions: true }); - expect(result).toEqual([ - { - config: { - foo: 'bar', - }, - id: '1', - isDeprecated: false, - isMissingSecrets: false, - isPreconfigured: false, - isSystemAction: false, - name: 'test', - referencedByCount: 6, - }, - { - actionTypeId: '.slack', - id: 'testPreconfigured', - isDeprecated: false, - isPreconfigured: true, - isSystemAction: false, - name: 'test', - referencedByCount: 2, - }, - ]); - - expect(logger.warn).toHaveBeenCalledWith( - 'Error validating connector: 1, Error: [actionTypeId]: expected value of type [string] but got [undefined]' - ); }); }); diff --git a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.ts b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.ts index f0945c27265c75..64f6c641c1e6ef 100644 --- a/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.ts +++ b/x-pack/plugins/actions/server/application/connector/methods/get_all/get_all.ts @@ -79,6 +79,46 @@ export async function getAll({ }); } +export async function getAllSystemConnectors({ + context, +}: { + context: GetAllParams['context']; +}): Promise { + try { + await context.authorization.ensureAuthorized({ operation: 'get' }); + } catch (error) { + context.auditLogger?.log( + connectorAuditEvent({ + action: ConnectorAuditAction.FIND, + error, + }) + ); + + throw error; + } + + const systemConnectors = context.inMemoryConnectors.filter( + (connector) => connector.isSystemAction + ); + + const transformedSystemConnectors = systemConnectors + .map((systemConnector) => ({ + id: systemConnector.id, + actionTypeId: systemConnector.actionTypeId, + name: systemConnector.name, + isPreconfigured: systemConnector.isPreconfigured, + isDeprecated: isConnectorDeprecated(systemConnector), + isSystemAction: systemConnector.isSystemAction, + })) + .sort((a, b) => a.name.localeCompare(b.name)); + + return await injectExtraFindData({ + kibanaIndices: context.kibanaIndices, + scopedClusterClient: context.scopedClusterClient, + connectors: transformedSystemConnectors, + }); +} + async function injectExtraFindData({ kibanaIndices, scopedClusterClient, diff --git a/x-pack/plugins/actions/server/mocks.ts b/x-pack/plugins/actions/server/mocks.ts index 70a2cfd9f8e85d..72af76ef55b9c3 100644 --- a/x-pack/plugins/actions/server/mocks.ts +++ b/x-pack/plugins/actions/server/mocks.ts @@ -48,6 +48,7 @@ const createStartMock = () => { .mockReturnValue(actionsAuthorizationMock.create()), inMemoryConnectors: [], renderActionParameterTemplates: jest.fn(), + isSystemActionConnector: jest.fn(), }; return mock; }; diff --git a/x-pack/plugins/actions/server/plugin.test.ts b/x-pack/plugins/actions/server/plugin.test.ts index dd936600d7055b..e6852d370f9436 100644 --- a/x-pack/plugins/actions/server/plugin.test.ts +++ b/x-pack/plugins/actions/server/plugin.test.ts @@ -882,5 +882,53 @@ describe('Actions Plugin', () => { expect(pluginSetup.getActionsHealth()).toEqual({ hasPermanentEncryptionKey: true }); }); }); + + describe('isSystemActionConnector()', () => { + it('should return true if the connector is a system connector', async () => { + // 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); + expect(pluginStart.isSystemActionConnector('system-connector-.cases')).toBe(true); + }); + + it('should return false if the connector is not a system connector', async () => { + // 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); + expect(pluginStart.isSystemActionConnector('preconfiguredServerLog')).toBe(false); + }); + }); }); }); diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 60a27eb04e411f..600259351c9f75 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -157,6 +157,7 @@ export interface PluginStartContract { params: Params, variables: Record ): Params; + isSystemActionConnector: (connectorId: string) => boolean; } export interface ActionsPluginsSetup { @@ -586,6 +587,12 @@ export class ActionsPlugin implements Plugin renderActionParameterTemplates(actionTypeRegistry, ...args), + isSystemActionConnector: (connectorId: string): boolean => { + return this.inMemoryConnectors.some( + (inMemoryConnector) => + inMemoryConnector.isSystemAction && inMemoryConnector.id === connectorId + ); + }, }; }