diff --git a/packages/kbn-rule-data-utils/index.ts b/packages/kbn-rule-data-utils/index.ts index ea0028b972ed9..3e9787891be05 100644 --- a/packages/kbn-rule-data-utils/index.ts +++ b/packages/kbn-rule-data-utils/index.ts @@ -14,3 +14,4 @@ export * from './src/alerts_as_data_severity'; export * from './src/alerts_as_data_status'; export * from './src/alerts_as_data_cases'; export * from './src/routes/stack_rule_paths'; +export * from './src/rule_types'; diff --git a/packages/kbn-rule-data-utils/src/rule_types/index.ts b/packages/kbn-rule-data-utils/src/rule_types/index.ts new file mode 100644 index 0000000000000..b2f860b063c9b --- /dev/null +++ b/packages/kbn-rule-data-utils/src/rule_types/index.ts @@ -0,0 +1,10 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export * from './stack_rules'; +export * from './o11y_rules'; diff --git a/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts b/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts new file mode 100644 index 0000000000000..86c5ef3f2e646 --- /dev/null +++ b/packages/kbn-rule-data-utils/src/rule_types/o11y_rules.ts @@ -0,0 +1,9 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold'; diff --git a/packages/kbn-rule-data-utils/src/rule_types/stack_rules.ts b/packages/kbn-rule-data-utils/src/rule_types/stack_rules.ts new file mode 100644 index 0000000000000..ff426a9069537 --- /dev/null +++ b/packages/kbn-rule-data-utils/src/rule_types/stack_rules.ts @@ -0,0 +1,10 @@ +/* + * 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 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; +export const ES_QUERY_ID = '.es-query'; diff --git a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx index 2993193ccc2bf..618557d4388eb 100644 --- a/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx +++ b/src/plugins/discover/public/application/main/components/top_nav/open_alerts_popover.tsx @@ -13,6 +13,7 @@ import { FormattedMessage } from '@kbn/i18n-react'; import type { DataView } from '@kbn/data-plugin/common'; import { KibanaRenderContextProvider } from '@kbn/react-kibana-context-render'; import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public'; +import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; import { DiscoverStateContainer } from '../../services/discover_state'; import { DiscoverServices } from '../../../../build_services'; @@ -97,7 +98,7 @@ export function AlertsPopover({ return triggersActionsUi?.getAddRuleFlyout({ metadata: discoverMetadata, - consumer: 'discover', + consumer: STACK_ALERTS_FEATURE_ID, onClose: (_, metadata) => { onFinishFlyoutInteraction(metadata as EsQueryAlertMetaData); onClose(); diff --git a/src/plugins/discover/tsconfig.json b/src/plugins/discover/tsconfig.json index f7b1e45e9a608..cb84d95d69f7c 100644 --- a/src/plugins/discover/tsconfig.json +++ b/src/plugins/discover/tsconfig.json @@ -72,6 +72,7 @@ "@kbn/react-kibana-context-render", "@kbn/unified-data-table", "@kbn/no-data-page-plugin", + "@kbn/rule-data-utils", "@kbn/global-search-plugin" ], "exclude": [ diff --git a/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/v1.ts b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/v1.ts index d49ccb090d53d..95f07bf3f7bda 100644 --- a/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/v1.ts +++ b/x-pack/plugins/alerting/common/routes/rule/apis/aggregate/schemas/v1.ts @@ -24,6 +24,7 @@ export const aggregateRulesRequestBodySchema = schema.object({ ) ), filter: schema.maybe(schema.string()), + filter_consumers: schema.maybe(schema.arrayOf(schema.string())), }); export const aggregateRulesResponseBodySchema = schema.object({ diff --git a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts index 405f040d5b3ae..136c44b2cd513 100644 --- a/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts +++ b/x-pack/plugins/alerting/public/alert_navigation_registry/types.ts @@ -16,4 +16,4 @@ import { SanitizedRule } from '../../common'; * originally registered to {@link PluginSetupContract.registerNavigation}. * */ -export type AlertNavigationHandler = (rule: SanitizedRule) => string; +export type AlertNavigationHandler = (rule: SanitizedRule) => string | undefined; diff --git a/x-pack/plugins/alerting/public/plugin.ts b/x-pack/plugins/alerting/public/plugin.ts index b4cfc68bc267f..1e8f1721aa126 100644 --- a/x-pack/plugins/alerting/public/plugin.ts +++ b/x-pack/plugins/alerting/public/plugin.ts @@ -138,7 +138,8 @@ export class AlertingPublicPlugin if (this.alertNavigationRegistry!.has(rule.consumer, ruleType)) { const navigationHandler = this.alertNavigationRegistry!.get(rule.consumer, ruleType); - return navigationHandler(rule); + const navUrl = navigationHandler(rule); + if (navUrl) return navUrl; } if (rule.viewInAppRelativeUrl) { diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts index 16eaec0889ed4..b1df5609f811f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.test.ts @@ -61,6 +61,7 @@ const ruleType: jest.Mocked = { mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, shouldWrite: true, }, + validLegacyConsumers: [], }; const mockLegacyAlertsClient = legacyAlertsClientMock.create(); diff --git a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts index d723f0d0b64fc..72d222edf5593 100644 --- a/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/legacy_alerts_client.test.ts @@ -99,6 +99,7 @@ const ruleType: jest.Mocked = { validate: { params: schema.any(), }, + validLegacyConsumers: [], }; const testAlert1 = { diff --git a/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts b/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts index cb90b75d2c16d..6e8d28de01753 100644 --- a/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/lib/format_rule.test.ts @@ -29,6 +29,7 @@ const ruleType: jest.Mocked = { mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, shouldWrite: true, }, + validLegacyConsumers: [], }; describe('formatRule', () => { diff --git a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts index 18a80a0bae31d..c8f8e671952f6 100644 --- a/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts +++ b/x-pack/plugins/alerting/server/alerts_service/alerts_service.test.ts @@ -203,6 +203,7 @@ const ruleType: jest.Mocked = { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], }; const ruleTypeWithAlertDefinition: jest.Mocked = { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts index b8fbc0427b7f4..9bdac512cd28c 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.test.ts @@ -82,6 +82,7 @@ describe('aggregate()', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]); beforeEach(() => { @@ -166,6 +167,7 @@ describe('aggregate()', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]) ); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.ts index f992f5cead705..9011b971e629a 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/aggregate_rules.ts @@ -6,6 +6,7 @@ */ import { KueryNode, nodeBuilder } from '@kbn/es-query'; +import { isEmpty } from 'lodash'; import { findRulesSo } from '../../../../data/rule'; import { AlertingAuthorizationEntity } from '../../../../authorization'; import { ruleAuditEvent, RuleAuditAction } from '../../../../rules_client/common/audit_events'; @@ -21,13 +22,14 @@ export async function aggregateRules>( params: AggregateParams ): Promise { const { options = {}, aggs } = params; - const { filter, page = 1, perPage = 0, ...restOptions } = options; + const { filter, page = 1, perPage = 0, filterConsumers, ...restOptions } = options; let authorizationTuple; try { authorizationTuple = await context.authorization.getFindAuthorizationFilter( AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts + alertingAuthorizationFilterOpts, + isEmpty(filterConsumers) ? undefined : new Set(filterConsumers) ); validateRuleAggregationFields(aggs); aggregateOptionsSchema.validate(options); diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/aggregate_options_schema.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/aggregate_options_schema.ts index 3ac244b808752..7250094160a04 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/aggregate_options_schema.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/schemas/aggregate_options_schema.ts @@ -16,6 +16,7 @@ export const aggregateOptionsSchema = schema.object({ id: schema.string(), }) ), + filterConsumers: schema.maybe(schema.arrayOf(schema.string())), // filter type is `string | KueryNode`, but `KueryNode` has no schema to import yet filter: schema.maybe( schema.oneOf([schema.string(), schema.recordOf(schema.string(), schema.any())]) diff --git a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/types/index.ts b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/types/index.ts index 3733a49003bba..2156fb91e8778 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/aggregate/types/index.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/aggregate/types/index.ts @@ -19,6 +19,7 @@ export type AggregateOptions = TypeOf & { filter?: string | KueryNode; page?: AggregateOptionsSchemaTypes['page']; perPage?: AggregateOptionsSchemaTypes['perPage']; + filterConsumers?: string[]; }; export interface AggregateParams { diff --git a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts index af51009d71da1..1a2faed2e0e66 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/bulk_edit/bulk_edit_rules.test.ts @@ -243,6 +243,7 @@ describe('bulkEdit()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], }); (migrateLegacyActions as jest.Mock).mockResolvedValue(migrateLegacyActionsMock); @@ -745,6 +746,7 @@ describe('bulkEdit()', () => { mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, shouldWrite: true, }, + validLegacyConsumers: [], }); const existingAction = { frequency: { @@ -2351,6 +2353,7 @@ describe('bulkEdit()', () => { return { state: {} }; }, producer: 'alerts', + validLegacyConsumers: [], }); const result = await rulesClient.bulkEdit({ @@ -2395,6 +2398,7 @@ describe('bulkEdit()', () => { return { state: {} }; }, producer: 'alerts', + validLegacyConsumers: [], }); const result = await rulesClient.bulkEdit({ diff --git a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts index ef2d91ea484ff..2d7746f715a05 100644 --- a/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts +++ b/x-pack/plugins/alerting/server/application/rule/methods/create/create_rule.test.ts @@ -1551,6 +1551,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ params: ruleParams, @@ -1738,6 +1739,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ params: ruleParams, @@ -2557,6 +2559,7 @@ describe('create()', () => { return { state: {} }; }, producer: 'alerts', + validLegacyConsumers: [], }); await expect(rulesClient.create({ data })).rejects.toThrowErrorMatchingInlineSnapshot( `"params invalid: [param1]: expected value of type [string] but got [undefined]"` @@ -3031,6 +3034,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const createdAttributes = { ...data, @@ -3103,6 +3107,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ schedule: { interval: '1s' } }); @@ -3140,6 +3145,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ @@ -3232,6 +3238,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ @@ -3281,6 +3288,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ @@ -3343,6 +3351,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ @@ -3423,6 +3432,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ @@ -3627,6 +3637,7 @@ describe('create()', () => { mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, shouldWrite: true, }, + validLegacyConsumers: [], })); const data = getMockData({ @@ -3679,6 +3690,7 @@ describe('create()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const data = getMockData({ diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts index d6fb8d415e57f..c68532082f18a 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts @@ -22,7 +22,7 @@ import { } from './alerting_authorization'; import { v4 as uuidv4 } from 'uuid'; import { RecoveredActionGroup } from '../../common'; -import { RegistryRuleType } from '../rule_type_registry'; +import { NormalizedRuleType, RegistryRuleType } from '../rule_type_registry'; import { AlertingAuthorizationFilterType } from './alerting_authorization_kuery'; import { schema } from '@kbn/config-schema'; @@ -201,6 +201,7 @@ beforeEach(() => { validate: { params: schema.any(), }, + validLegacyConsumers: [], })); features.getKibanaFeatures.mockReturnValue([ myAppFeature, @@ -250,7 +251,7 @@ describe('AlertingAuthorization', () => { entity: AlertingAuthorizationEntity.Rule, }); - expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(0); + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); }); test('is a no-op when the security license is disabled', async () => { @@ -272,7 +273,7 @@ describe('AlertingAuthorization', () => { entity: AlertingAuthorizationEntity.Rule, }); - expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(0); + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); }); test('ensures the user has privileges to execute rules for the specified rule type and operation without consumer when producer and consumer are the same', async () => { @@ -305,7 +306,7 @@ describe('AlertingAuthorization', () => { expect(ruleTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(1); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', 'myApp', @@ -347,7 +348,7 @@ describe('AlertingAuthorization', () => { expect(ruleTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(1); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', 'myApp', @@ -389,13 +390,7 @@ describe('AlertingAuthorization', () => { expect(ruleTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); - expect(authorization.actions.alerting.get).toHaveBeenCalledWith( - 'myType', - 'alerts', - 'rule', - 'create' - ); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(1); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', 'myApp', @@ -437,13 +432,7 @@ describe('AlertingAuthorization', () => { expect(ruleTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); - expect(authorization.actions.alerting.get).toHaveBeenCalledWith( - 'myType', - 'alerts', - 'alert', - 'update' - ); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(1); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', 'myApp', @@ -485,13 +474,7 @@ describe('AlertingAuthorization', () => { expect(ruleTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); - expect(authorization.actions.alerting.get).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'rule', - 'create' - ); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(1); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', 'myOtherApp', @@ -499,10 +482,7 @@ describe('AlertingAuthorization', () => { 'create' ); expect(checkPrivileges).toHaveBeenCalledWith({ - kibana: [ - mockAuthorizationAction('myType', 'myOtherApp', 'rule', 'create'), - mockAuthorizationAction('myType', 'myApp', 'rule', 'create'), - ], + kibana: [mockAuthorizationAction('myType', 'myOtherApp', 'rule', 'create')], }); }); @@ -536,24 +516,57 @@ describe('AlertingAuthorization', () => { expect(ruleTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(1); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', - 'myApp', + 'myOtherApp', 'alert', 'update' ); + expect(checkPrivileges).toHaveBeenCalledWith({ + kibana: [mockAuthorizationAction('myType', 'myOtherApp', 'alert', 'update')], + }); + }); + + test('ensures the producer is used for authorization if the consumer is `alerts`', async () => { + const { authorization } = mockSecurity(); + const checkPrivileges: jest.MockedFunction< + ReturnType + > = jest.fn(); + authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); + checkPrivileges.mockResolvedValueOnce({ + username: 'some-user', + hasAllRequested: true, + privileges: { kibana: [] }, + }); + + const alertAuthorization = new AlertingAuthorization({ + request, + authorization, + ruleTypeRegistry, + features, + getSpace, + getSpaceId, + }); + + await alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); + + expect(ruleTypeRegistry.get).toHaveBeenCalledWith('myType'); + + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(1); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', - 'myOtherApp', - 'alert', - 'update' + 'myApp', + 'rule', + 'create' ); expect(checkPrivileges).toHaveBeenCalledWith({ - kibana: [ - mockAuthorizationAction('myType', 'myOtherApp', 'alert', 'update'), - mockAuthorizationAction('myType', 'myApp', 'alert', 'update'), - ], + kibana: [mockAuthorizationAction('myType', 'myApp', 'rule', 'create')], }); }); @@ -597,7 +610,7 @@ describe('AlertingAuthorization', () => { entity: AlertingAuthorizationEntity.Rule, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unauthorized to create a \\"myType\\" rule for \\"myOtherApp\\""` + `"Unauthorized by \\"myOtherApp\\" to create \\"myType\\" rule"` ); }); @@ -645,7 +658,7 @@ describe('AlertingAuthorization', () => { entity: AlertingAuthorizationEntity.Alert, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unauthorized to update a \\"myType\\" alert for \\"myAppRulesOnly\\""` + `"Unauthorized by \\"myAppRulesOnly\\" to update \\"myType\\" alert"` ); }); @@ -689,7 +702,7 @@ describe('AlertingAuthorization', () => { entity: AlertingAuthorizationEntity.Alert, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unauthorized to update a \\"myType\\" alert by \\"myApp\\""` + `"Unauthorized by \\"myOtherApp\\" to update \\"myType\\" alert"` ); }); @@ -733,7 +746,7 @@ describe('AlertingAuthorization', () => { entity: AlertingAuthorizationEntity.Alert, }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unauthorized to create a \\"myType\\" alert for \\"myOtherApp\\""` + `"Unauthorized by \\"myOtherApp\\" to create \\"myType\\" alert"` ); }); }); @@ -752,6 +765,7 @@ describe('AlertingAuthorization', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const myAppAlertType: RegistryRuleType = { actionGroups: [], @@ -766,6 +780,7 @@ describe('AlertingAuthorization', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const mySecondAppAlertType: RegistryRuleType = { actionGroups: [], @@ -780,6 +795,7 @@ describe('AlertingAuthorization', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType, mySecondAppAlertType]); test('omits filter when there is no authorization api', async () => { @@ -967,7 +983,7 @@ describe('AlertingAuthorization', () => { expect(() => { ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'alert'); }).toThrowErrorMatchingInlineSnapshot( - `"Unauthorized to find a \\"myAppAlertType\\" alert for \\"myOtherApp\\""` + `"Unauthorized by \\"myOtherApp\\" to find \\"myAppAlertType\\" alert"` ); }); test('creates an `ensureRuleTypeIsAuthorized` function which is no-op if type is authorized', async () => { @@ -1153,6 +1169,7 @@ describe('AlertingAuthorization', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const myAppAlertType: RegistryRuleType = { actionGroups: [], @@ -1167,6 +1184,7 @@ describe('AlertingAuthorization', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType]); beforeEach(() => { @@ -1233,6 +1251,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, Object { "actionGroups": Array [], @@ -1268,6 +1287,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, } `); @@ -1349,15 +1369,12 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, Object { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { - "all": true, - "read": true, - }, "myApp": Object { "all": true, "read": true, @@ -1380,6 +1397,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, } `); @@ -1430,10 +1448,6 @@ describe('AlertingAuthorization', () => { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { - "all": true, - "read": true, - }, "myApp": Object { "all": true, "read": true, @@ -1452,6 +1466,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, } `); @@ -1536,10 +1551,6 @@ describe('AlertingAuthorization', () => { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { - "all": false, - "read": true, - }, "myApp": Object { "all": true, "read": true, @@ -1562,15 +1573,12 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, Object { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { - "all": false, - "read": true, - }, "myApp": Object { "all": false, "read": true, @@ -1593,6 +1601,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, } `); @@ -1656,10 +1665,6 @@ describe('AlertingAuthorization', () => { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { - "all": true, - "read": true, - }, "myApp": Object { "all": true, "read": true, @@ -1682,6 +1687,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, } `); @@ -1702,6 +1708,7 @@ describe('AlertingAuthorization', () => { isExportable: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const myAppAlertType: RegistryRuleType = { actionGroups: [], @@ -1716,6 +1723,7 @@ describe('AlertingAuthorization', () => { isExportable: true, hasAlertsMappings: true, hasFieldsForAAD: true, + validLegacyConsumers: [], }; const mySecondAppAlertType: RegistryRuleType = { actionGroups: [], @@ -1730,6 +1738,7 @@ describe('AlertingAuthorization', () => { isExportable: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType, mySecondAppAlertType]); beforeEach(() => { @@ -1794,6 +1803,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, }, "hasAllRequested": false, @@ -1869,6 +1879,7 @@ describe('AlertingAuthorization', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, }, "hasAllRequested": false, @@ -1877,4 +1888,626 @@ describe('AlertingAuthorization', () => { `); }); }); + + describe('8.11+', () => { + let alertAuthorization: AlertingAuthorization; + + const setOfRuleTypes: RegistryRuleType[] = [ + { + actionGroups: [], + actionVariables: undefined, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + id: '.esQuery', + name: 'ES Query', + producer: 'stackAlerts', + enabledInLicense: true, + hasAlertsMappings: false, + hasFieldsForAAD: false, + validLegacyConsumers: ['discover', 'alerts'], + }, + { + actionGroups: [], + actionVariables: undefined, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + id: '.threshold-rule-o11y', + name: 'New threshold 011y', + producer: 'observability', + enabledInLicense: true, + hasAlertsMappings: false, + hasFieldsForAAD: false, + validLegacyConsumers: [], + }, + { + actionGroups: [], + actionVariables: undefined, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + id: '.infrastructure-threshold-o11y', + name: 'Metrics o11y', + producer: 'infrastructure', + enabledInLicense: true, + hasAlertsMappings: false, + hasFieldsForAAD: false, + validLegacyConsumers: ['alerts'], + }, + { + actionGroups: [], + actionVariables: undefined, + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + isExportable: true, + recoveryActionGroup: RecoveredActionGroup, + id: '.logs-threshold-o11y', + name: 'Logs o11y', + producer: 'logs', + enabledInLicense: true, + hasAlertsMappings: false, + hasFieldsForAAD: false, + validLegacyConsumers: ['alerts'], + }, + ]; + + const onlyStackAlertsKibanaPrivileges = [ + { + privilege: mockAuthorizationAction('.esQuery', 'stackAlerts', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.esQuery', 'stackAlerts', 'rule', 'find'), + authorized: true, + }, + ]; + const only011yKibanaPrivileges = [ + { + privilege: mockAuthorizationAction( + '.infrastructure-threshold-o11y', + 'infrastructure', + 'rule', + 'create' + ), + authorized: true, + }, + { + privilege: mockAuthorizationAction( + '.infrastructure-threshold-o11y', + 'infrastructure', + 'rule', + 'find' + ), + authorized: true, + }, + { + privilege: mockAuthorizationAction( + '.threshold-rule-o11y', + 'infrastructure', + 'rule', + 'create' + ), + authorized: true, + }, + { + privilege: mockAuthorizationAction( + '.threshold-rule-o11y', + 'infrastructure', + 'rule', + 'find' + ), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.logs-threshold-o11y', 'logs', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.logs-threshold-o11y', 'logs', 'rule', 'find'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.threshold-rule-o11y', 'logs', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.threshold-rule-o11y', 'logs', 'rule', 'find'), + authorized: true, + }, + ]; + const onlyLogsAndStackAlertsKibanaPrivileges = [ + { + privilege: mockAuthorizationAction('.esQuery', 'stackAlerts', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.esQuery', 'stackAlerts', 'rule', 'find'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.logs-threshold-o11y', 'logs', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.logs-threshold-o11y', 'logs', 'rule', 'find'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.threshold-rule-o11y', 'logs', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('.threshold-rule-o11y', 'logs', 'rule', 'find'), + authorized: true, + }, + ]; + + beforeEach(async () => { + ruleTypeRegistry.list.mockReturnValue(new Set(setOfRuleTypes)); + ruleTypeRegistry.get.mockImplementation((id: string) => { + if (setOfRuleTypes.some((rt) => rt.id === id)) { + const ruleType = setOfRuleTypes.find((rt) => rt.id === id); + return (ruleType ?? {}) as NormalizedRuleType<{}, {}, {}, {}, {}, '', '', {}>; + } + return {} as NormalizedRuleType<{}, {}, {}, {}, {}, '', '', {}>; + }); + }); + + describe('user only access to stack alerts + discover', () => { + beforeEach(() => { + const { authorization } = mockSecurity(); + const checkPrivileges: jest.MockedFunction< + ReturnType + > = jest.fn(); + authorization.mode.useRbacForRequest.mockReturnValue(true); + + features.getKibanaFeatures.mockReset(); + features.getKibanaFeatures.mockReturnValue([ + mockFeature('stackAlerts', ['.esQuery']), + mockFeature('discover', []), + ]); + checkPrivileges.mockReset(); + checkPrivileges.mockResolvedValue({ + username: 'onlyStack', + hasAllRequested: true, + privileges: { + kibana: onlyStackAlertsKibanaPrivileges, + }, + }); + authorization.checkPrivilegesDynamicallyWithRequest.mockReset(); + authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); + alertAuthorization = new AlertingAuthorization({ + request, + authorization, + ruleTypeRegistry, + features, + getSpace, + getSpaceId, + }); + }); + + describe('ensureAuthorized', () => { + test('should allow to create .esquery rule type with stackAlerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'stackAlerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .esquery rule type with discover consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'discover', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .esquery rule type with alerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .esquery rule type with logs consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'logs', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"logs\\" to create \\".esQuery\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .esquery rule type with infrastructure consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'infrastructure', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"infrastructure\\" to create \\".esQuery\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .threshold-rule-o11y rule type with alerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.threshold-rule-o11y', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"alerts\\" to create \\".threshold-rule-o11y\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .logs-threshold-o11y rule type with alerts infrastructure', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.logs-threshold-o11y', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"alerts\\" to create \\".logs-threshold-o11y\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + }); + test('creates a filter based on the privileged types', async () => { + expect( + ( + await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule_type_id', + consumer: 'consumer-field', + }, + }) + ).filter + ).toEqual( + fromKueryExpression( + `path.to.rule_type_id:.esQuery and consumer-field:(alerts or stackAlerts or discover)` + ) + ); + }); + }); + + describe('user only access to o11y', () => { + beforeEach(() => { + const { authorization } = mockSecurity(); + const checkPrivileges: jest.MockedFunction< + ReturnType + > = jest.fn(); + authorization.mode.useRbacForRequest.mockReturnValue(true); + + features.getKibanaFeatures.mockReset(); + features.getKibanaFeatures.mockReturnValue([ + mockFeature('infrastructure', [ + '.infrastructure-threshold-o11y', + '.threshold-rule-o11y', + '.esQuery', + ]), + mockFeature('logs', ['.threshold-rule-o11y', '.esQuery', '.logs-threshold-o11y']), + ]); + checkPrivileges.mockReset(); + checkPrivileges.mockResolvedValue({ + username: 'onlyO11y', + hasAllRequested: true, + privileges: { + kibana: only011yKibanaPrivileges, + }, + }); + authorization.checkPrivilegesDynamicallyWithRequest.mockReset(); + authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); + alertAuthorization = new AlertingAuthorization({ + request, + authorization, + ruleTypeRegistry, + features, + getSpace, + getSpaceId, + }); + }); + + describe('ensureAuthorized', () => { + test('should throw an error to create .esquery rule type with stackAlerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'stackAlerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"stackAlerts\\" to create \\".esQuery\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .esquery rule type with discover consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'discover', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"discover\\" to create \\".esQuery\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .threshold-rule-o11y rule type with alerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.threshold-rule-o11y', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"alerts\\" to create \\".threshold-rule-o11y\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .esquery rule type with logs consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'logs', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .esquery rule type with logs infrastructure', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'infrastructure', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .logs-threshold-o11y rule type with alerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.logs-threshold-o11y', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .threshold-rule-o11y rule type with logs consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.threshold-rule-o11y', + consumer: 'logs', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + }); + test('creates a filter based on the privileged types', async () => { + expect( + ( + await alertAuthorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule_type_id', + consumer: 'consumer-field', + }, + }, + new Set(['infrastructure', 'logs']) + ) + ).filter + ).toEqual( + fromKueryExpression( + `(path.to.rule_type_id:.infrastructure-threshold-o11y and consumer-field:(infrastructure or alerts)) or (path.to.rule_type_id:.threshold-rule-o11y and consumer-field:(infrastructure or logs)) or (path.to.rule_type_id:.logs-threshold-o11y and consumer-field:(logs or alerts))` + ) + ); + }); + }); + + describe('user only access to logs and stackAlerts', () => { + beforeEach(() => { + const { authorization } = mockSecurity(); + const checkPrivileges: jest.MockedFunction< + ReturnType + > = jest.fn(); + authorization.mode.useRbacForRequest.mockReturnValue(true); + + features.getKibanaFeatures.mockClear(); + features.getKibanaFeatures.mockReturnValue([ + mockFeature('stackAlerts', ['.esQuery']), + mockFeature('logs', ['.logs-threshold-o11y', '.threshold-rule-o11y', '.esQuery']), + ]); + checkPrivileges.mockClear(); + checkPrivileges.mockResolvedValue({ + username: 'stackAndLogs', + hasAllRequested: true, + privileges: { + kibana: onlyLogsAndStackAlertsKibanaPrivileges, + }, + }); + authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); + alertAuthorization = new AlertingAuthorization({ + request, + authorization, + ruleTypeRegistry, + features, + getSpace, + getSpaceId, + }); + }); + + describe('ensureAuthorized', () => { + test('should allow to create .esquery rule type with stackAlerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'stackAlerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .esquery rule type with discover consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'discover', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .esquery rule type with logs consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'logs', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should allow to create .logs-threshold-o11y rule type with alerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.logs-threshold-o11y', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .threshold-rule-o11y rule type with logs consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.threshold-rule-o11y', + consumer: 'logs', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).resolves.toEqual(undefined); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .esquery rule type with logs infrastructure', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'infrastructure', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"infrastructure\\" to create \\".esQuery\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .threshold-rule-o11y rule type with alerts consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.threshold-rule-o11y', + consumer: 'alerts', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"alerts\\" to create \\".threshold-rule-o11y\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + test('should throw an error to create .esquery rule type with infrastructure consumer', async () => { + await expect( + alertAuthorization.ensureAuthorized({ + ruleTypeId: '.esQuery', + consumer: 'infrastructure', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Unauthorized by \\"infrastructure\\" to create \\".esQuery\\" rule"` + ); + + expect(ruleTypeRegistry.get).toHaveBeenCalledTimes(1); + }); + }); + test('creates a filter based on the privileged types', async () => { + expect( + ( + await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule_type_id', + consumer: 'consumer-field', + }, + }) + ).filter + ).toEqual( + fromKueryExpression( + `(path.to.rule_type_id:.esQuery and consumer-field:(alerts or stackAlerts or logs or discover)) or (path.to.rule_type_id:.logs-threshold-o11y and consumer-field:(alerts or stackAlerts or logs or discover)) or (path.to.rule_type_id:.threshold-rule-o11y and consumer-field:(alerts or stackAlerts or logs or discover))` + ) + ); + }); + }); + }); }); diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index 051972b944261..90f4b189b1197 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -6,7 +6,7 @@ */ import Boom from '@hapi/boom'; -import { map, mapValues, fromPairs, has } from 'lodash'; +import { has, isEmpty } from 'lodash'; import { KibanaRequest } from '@kbn/core/server'; import { JsonObject } from '@kbn/utility-types'; import { KueryNode } from '@kbn/es-query'; @@ -167,42 +167,26 @@ export class AlertingAuthorization { ); } - public async ensureAuthorized({ ruleTypeId, consumer, operation, entity }: EnsureAuthorizedOpts) { + public async ensureAuthorized({ + ruleTypeId, + consumer: legacyConsumer, + operation, + entity, + }: EnsureAuthorizedOpts) { const { authorization } = this; + const ruleType = this.ruleTypeRegistry.get(ruleTypeId); + const consumer = getValidConsumer({ + validLegacyConsumers: ruleType.validLegacyConsumers, + legacyConsumer, + producer: ruleType.producer, + }); const isAvailableConsumer = has(await this.allPossibleConsumers, consumer); if (authorization && this.shouldCheckAuthorization()) { - const ruleType = this.ruleTypeRegistry.get(ruleTypeId); - const requiredPrivilegesByScope = { - consumer: authorization.actions.alerting.get(ruleTypeId, consumer, entity, operation), - producer: authorization.actions.alerting.get( - ruleTypeId, - ruleType.producer, - entity, - operation - ), - }; - - // Skip authorizing consumer if consumer is the Rules Management consumer (`alerts`) - // This means that rules and their derivative alerts created in the Rules Management UI - // will only be subject to checking if user has access to the rule producer. - const shouldAuthorizeConsumer = consumer !== ALERTS_FEATURE_ID; - const checkPrivileges = authorization.checkPrivilegesDynamicallyWithRequest(this.request); - const { hasAllRequested, privileges } = await checkPrivileges({ - kibana: - shouldAuthorizeConsumer && consumer !== ruleType.producer - ? [ - // check for access at consumer level - requiredPrivilegesByScope.consumer, - // check for access at producer level - requiredPrivilegesByScope.producer, - ] - : [ - // skip consumer privilege checks under `alerts` as all rule types can - // be created under `alerts` if you have producer level privileges - requiredPrivilegesByScope.producer, - ], + + const { hasAllRequested } = await checkPrivileges({ + kibana: [authorization.actions.alerting.get(ruleTypeId, consumer, entity, operation)], }); if (!isAvailableConsumer) { @@ -213,51 +197,31 @@ export class AlertingAuthorization { * as Privileged. * This check will ensure we don't accidentally let these through */ - throw Boom.forbidden( - getUnauthorizedMessage(ruleTypeId, ScopeType.Consumer, consumer, operation, entity) - ); + throw Boom.forbidden(getUnauthorizedMessage(ruleTypeId, legacyConsumer, operation, entity)); } if (!hasAllRequested) { - const authorizedPrivileges = map( - privileges.kibana.filter((privilege) => privilege.authorized), - 'privilege' - ); - const unauthorizedScopes = mapValues( - requiredPrivilegesByScope, - (privilege) => !authorizedPrivileges.includes(privilege) - ); - - const [unauthorizedScopeType, unauthorizedScope] = - shouldAuthorizeConsumer && unauthorizedScopes.consumer - ? [ScopeType.Consumer, consumer] - : [ScopeType.Producer, ruleType.producer]; - - throw Boom.forbidden( - getUnauthorizedMessage( - ruleTypeId, - unauthorizedScopeType, - unauthorizedScope, - operation, - entity - ) - ); + throw Boom.forbidden(getUnauthorizedMessage(ruleTypeId, consumer, operation, entity)); } } else if (!isAvailableConsumer) { - throw Boom.forbidden( - getUnauthorizedMessage(ruleTypeId, ScopeType.Consumer, consumer, operation, entity) - ); + throw Boom.forbidden(getUnauthorizedMessage(ruleTypeId, consumer, operation, entity)); } } public async getFindAuthorizationFilter( authorizationEntity: AlertingAuthorizationEntity, - filterOpts: AlertingAuthorizationFilterOpts + filterOpts: AlertingAuthorizationFilterOpts, + featuresIds?: Set ): Promise<{ filter?: KueryNode | JsonObject; ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void; }> { - return this.getAuthorizationFilter(authorizationEntity, filterOpts, ReadOperations.Find); + return this.getAuthorizationFilter( + authorizationEntity, + filterOpts, + ReadOperations.Find, + featuresIds + ); } public async getAuthorizedRuleTypes( @@ -276,7 +240,8 @@ export class AlertingAuthorization { public async getAuthorizationFilter( authorizationEntity: AlertingAuthorizationEntity, filterOpts: AlertingAuthorizationFilterOpts, - operation: WriteOperations | ReadOperations + operation: WriteOperations | ReadOperations, + featuresIds?: Set ): Promise<{ filter?: KueryNode | JsonObject; ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void; @@ -285,7 +250,8 @@ export class AlertingAuthorization { const { authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization( this.ruleTypeRegistry.list(), [operation], - authorizationEntity + authorizationEntity, + featuresIds ); if (!authorizedRuleTypes.size) { @@ -311,13 +277,7 @@ export class AlertingAuthorization { ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => { if (!authorizedRuleTypeIdsToConsumers.has(`${ruleTypeId}/${consumer}/${authType}`)) { throw Boom.forbidden( - getUnauthorizedMessage( - ruleTypeId, - ScopeType.Consumer, - consumer, - 'find', - authorizationEntity - ) + getUnauthorizedMessage(ruleTypeId, consumer, 'find', authorizationEntity) ); } else { if (authorizedEntries.has(ruleTypeId)) { @@ -376,6 +336,9 @@ export class AlertingAuthorization { string, [RegistryAlertTypeWithAuth, string, HasPrivileges, IsAuthorizedAtProducerLevel] >(); + const allPossibleConsumers = await this.allPossibleConsumers; + const addLegacyConsumerPrivileges = (legacyConsumer: string) => + legacyConsumer === ALERTS_FEATURE_ID || isEmpty(featuresIds); for (const feature of fIds) { const featureDef = this.features .getKibanaFeatures() @@ -401,6 +364,31 @@ export class AlertingAuthorization { ruleTypeAuth.producer === feature, ] ); + // FUTURE ENGINEER + // We are just trying to add back the legacy consumers associated + // to the rule type to get back the privileges that was given at one point + if (!isEmpty(ruleTypeAuth.validLegacyConsumers)) { + ruleTypeAuth.validLegacyConsumers.forEach((legacyConsumer) => { + if (addLegacyConsumerPrivileges(legacyConsumer)) { + if (!allPossibleConsumers[legacyConsumer]) { + allPossibleConsumers[legacyConsumer] = { + read: true, + all: true, + }; + } + + privilegeToRuleType.set( + this.authorization!.actions.alerting.get( + ruleTypeId, + legacyConsumer, + authorizationEntity, + operation + ), + [ruleTypeAuth, legacyConsumer, hasPrivilegeByOperation(operation), false] + ); + } + }); + } } } } @@ -418,7 +406,7 @@ export class AlertingAuthorization { ? // has access to all features this.augmentWithAuthorizedConsumers( new Set(ruleTypesAuthorized.values()), - await this.allPossibleConsumers + allPossibleConsumers ) : // only has some of the required privileges privileges.kibana.reduce((authorizedRuleTypes, { authorized, privilege }) => { @@ -433,10 +421,14 @@ export class AlertingAuthorization { if (isAuthorizedAtProducerLevel) { // granting privileges under the producer automatically authorized the Rules Management UI as well - ruleType.authorizedConsumers[ALERTS_FEATURE_ID] = mergeHasPrivileges( - hasPrivileges, - ruleType.authorizedConsumers[ALERTS_FEATURE_ID] - ); + ruleType.validLegacyConsumers.forEach((legacyConsumer) => { + if (addLegacyConsumerPrivileges(legacyConsumer)) { + ruleType.authorizedConsumers[legacyConsumer] = mergeHasPrivileges( + hasPrivileges, + ruleType.authorizedConsumers[legacyConsumer] + ); + } + }); } authorizedRuleTypes.add(ruleType); } @@ -488,22 +480,30 @@ function asAuthorizedConsumers( consumers: string[], hasPrivileges: HasPrivileges ): AuthorizedConsumers { - return fromPairs(consumers.map((feature) => [feature, hasPrivileges])); -} - -enum ScopeType { - Consumer, - Producer, + return consumers.reduce((acc, feature) => { + acc[feature] = hasPrivileges; + return acc; + }, {}); } function getUnauthorizedMessage( alertTypeId: string, - scopeType: ScopeType, scope: string, operation: string, entity: string ): string { - return `Unauthorized to ${operation} a "${alertTypeId}" ${entity} ${ - scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"` - }`; + return `Unauthorized by "${scope}" to ${operation} "${alertTypeId}" ${entity}`; } + +export const getValidConsumer = ({ + validLegacyConsumers, + legacyConsumer, + producer, +}: { + validLegacyConsumers: string[]; + legacyConsumer: string; + producer: string; +}): string => + legacyConsumer === ALERTS_FEATURE_ID || validLegacyConsumers.includes(legacyConsumer) + ? producer + : legacyConsumer; diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts index c9e129085c3c0..bfa36ccfd46c5 100644 --- a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts @@ -34,6 +34,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -71,6 +72,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -111,6 +113,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, { actionGroups: [], @@ -130,6 +133,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, { actionGroups: [], @@ -149,6 +153,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -189,6 +194,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, { actionGroups: [], @@ -208,6 +214,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -249,6 +256,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, { actionGroups: [], @@ -268,6 +276,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -303,6 +312,7 @@ describe('asKqlFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -339,6 +349,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -403,6 +414,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -475,6 +487,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, { actionGroups: [], @@ -494,6 +507,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, { actionGroups: [], @@ -513,6 +527,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { @@ -678,6 +693,7 @@ describe('asEsDslFiltersByRuleTypeAndConsumer', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]), { diff --git a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts index c8c2e5f1943ec..609c860e4f7e9 100644 --- a/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts +++ b/x-pack/plugins/alerting/server/lib/alerting_event_logger/alerting_event_logger.test.ts @@ -46,6 +46,7 @@ const ruleType: jest.Mocked = { validate: { params: schema.any(), }, + validLegacyConsumers: [], }; const context: RuleContextOpts = { diff --git a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts index 6b18d1aac93dd..5aec760a1eadf 100644 --- a/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts +++ b/x-pack/plugins/alerting/server/lib/create_alert_event_log_record_object.test.ts @@ -26,6 +26,7 @@ describe('createAlertEventLogRecordObject', () => { validate: { params: schema.any(), }, + validLegacyConsumers: [], }; test('created alert event "execute-start"', async () => { diff --git a/x-pack/plugins/alerting/server/routes/find_rules.test.ts b/x-pack/plugins/alerting/server/routes/find_rules.test.ts index 6e8f4e5474dbf..afd621c6e7abd 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.test.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.test.ts @@ -79,6 +79,7 @@ describe('findRulesRoute', () => { "includeSnoozeData": true, "options": Object { "defaultSearchOperator": "OR", + "filterConsumers": undefined, "page": 1, "perPage": 1, }, diff --git a/x-pack/plugins/alerting/server/routes/find_rules.ts b/x-pack/plugins/alerting/server/routes/find_rules.ts index 04b18da1a1b0c..1eab92db82383 100644 --- a/x-pack/plugins/alerting/server/routes/find_rules.ts +++ b/x-pack/plugins/alerting/server/routes/find_rules.ts @@ -47,6 +47,7 @@ const querySchema = schema.object({ ), fields: schema.maybe(schema.arrayOf(schema.string())), filter: schema.maybe(schema.string()), + filter_consumers: schema.maybe(schema.arrayOf(schema.string())), }); const rewriteQueryReq: RewriteRequestCase = ({ @@ -56,11 +57,13 @@ const rewriteQueryReq: RewriteRequestCase = ({ per_page: perPage, sort_field: sortField, sort_order: sortOrder, + filter_consumers: filterConsumers, ...rest }) => ({ ...rest, defaultSearchOperator, perPage, + filterConsumers, ...(sortField ? { sortField } : {}), ...(sortOrder ? { sortOrder } : {}), ...(hasReference ? { hasReference } : {}), diff --git a/x-pack/plugins/alerting/server/routes/health.test.ts b/x-pack/plugins/alerting/server/routes/health.test.ts index b1c562ada8717..bb42109ef8502 100644 --- a/x-pack/plugins/alerting/server/routes/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/health.test.ts @@ -51,6 +51,7 @@ const ruleTypes = [ defaultScheduleInterval: '10m', hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; diff --git a/x-pack/plugins/alerting/server/routes/legacy/health.test.ts b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts index 9946f659a0744..e25485229dc0c 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/health.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/health.test.ts @@ -55,6 +55,7 @@ const ruleTypes = [ defaultScheduleInterval: '10m', hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; diff --git a/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts index 7262f42319c1f..ae094df73d093 100644 --- a/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/legacy/list_alert_types.test.ts @@ -64,6 +64,7 @@ describe('listAlertTypesRoute', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; rulesClient.listRuleTypes.mockResolvedValueOnce(new Set(listTypes)); @@ -98,6 +99,7 @@ describe('listAlertTypesRoute', () => { "id": "recovered", "name": "Recovered", }, + "validLegacyConsumers": Array [], }, ], } @@ -143,6 +145,7 @@ describe('listAlertTypesRoute', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; @@ -198,6 +201,7 @@ describe('listAlertTypesRoute', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; diff --git a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/v1.ts b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/v1.ts index 7b5879227ce97..baa6b9bb46f9c 100644 --- a/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/v1.ts +++ b/x-pack/plugins/alerting/server/routes/rule/apis/aggregate/transforms/transform_aggregate_query_request/v1.ts @@ -13,11 +13,13 @@ export const transformAggregateQueryRequest: RewriteRequestCase ({ defaultSearchOperator, ...(hasReference ? { hasReference } : {}), ...(searchFields ? { searchFields } : {}), ...(search ? { search } : {}), + ...(filterConsumers ? { filterConsumers } : {}), ...(filter ? { filter } : {}), }); diff --git a/x-pack/plugins/alerting/server/routes/rule_types.test.ts b/x-pack/plugins/alerting/server/routes/rule_types.test.ts index 2dab9284bb5ac..c52ce30e41dcd 100644 --- a/x-pack/plugins/alerting/server/routes/rule_types.test.ts +++ b/x-pack/plugins/alerting/server/routes/rule_types.test.ts @@ -62,9 +62,12 @@ describe('ruleTypesRoute', () => { doesSetRecoveryContext: false, hasAlertsMappings: true, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; - const expectedResult: Array> = [ + const expectedResult: Array< + AsApiContract> + > = [ { id: '1', name: 'name', @@ -172,6 +175,7 @@ describe('ruleTypesRoute', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; @@ -227,6 +231,7 @@ describe('ruleTypesRoute', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], } as RegistryAlertTypeWithAuth, ]; diff --git a/x-pack/plugins/alerting/server/routes/rule_types.ts b/x-pack/plugins/alerting/server/routes/rule_types.ts index 4c4a812d75684..4521bdfd167a2 100644 --- a/x-pack/plugins/alerting/server/routes/rule_types.ts +++ b/x-pack/plugins/alerting/server/routes/rule_types.ts @@ -8,10 +8,10 @@ import { IRouter } from '@kbn/core/server'; import { ILicenseState } from '../lib'; import { RegistryAlertTypeWithAuth } from '../authorization'; -import { RewriteResponseCase, verifyAccessAndContext } from './lib'; +import { verifyAccessAndContext } from './lib'; import { AlertingRequestHandlerContext, BASE_ALERTING_API_PATH } from '../types'; -const rewriteBodyRes: RewriteResponseCase = (results) => { +const rewriteBodyRes = (results: RegistryAlertTypeWithAuth[]) => { return results.map( ({ enabledInLicense, @@ -27,8 +27,9 @@ const rewriteBodyRes: RewriteResponseCase = (result doesSetRecoveryContext, hasAlertsMappings, hasFieldsForAAD, + validLegacyConsumers, ...rest - }) => ({ + }: RegistryAlertTypeWithAuth) => ({ ...rest, enabled_in_license: enabledInLicense, recovery_action_group: recoveryActionGroup, diff --git a/x-pack/plugins/alerting/server/rule_type_registry.test.ts b/x-pack/plugins/alerting/server/rule_type_registry.test.ts index 689741c7479ac..05aa84292ed8d 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.test.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.test.ts @@ -590,40 +590,41 @@ describe('Create Lifecycle', () => { }); const ruleType = registry.get('test'); expect(ruleType).toMatchInlineSnapshot(` - Object { - "actionGroups": Array [ - Object { - "id": "default", - "name": "Default", - }, - Object { - "id": "recovered", - "name": "Recovered", - }, - ], - "actionVariables": Object { - "context": Array [], - "params": Array [], - "state": Array [], - }, - "defaultActionGroupId": "default", - "executor": [MockFunction], - "id": "test", - "isExportable": true, - "minimumLicenseRequired": "basic", - "name": "Test", - "producer": "alerts", - "recoveryActionGroup": Object { - "id": "recovered", - "name": "Recovered", - }, - "validate": Object { - "params": Object { - "validate": [Function], - }, - }, - } - `); + Object { + "actionGroups": Array [ + Object { + "id": "default", + "name": "Default", + }, + Object { + "id": "recovered", + "name": "Recovered", + }, + ], + "actionVariables": Object { + "context": Array [], + "params": Array [], + "state": Array [], + }, + "defaultActionGroupId": "default", + "executor": [MockFunction], + "id": "test", + "isExportable": true, + "minimumLicenseRequired": "basic", + "name": "Test", + "producer": "alerts", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", + }, + "validLegacyConsumers": Array [], + "validate": Object { + "params": Object { + "validate": [Function], + }, + }, + } + `); }); test(`should throw an error if type isn't registered`, () => { @@ -713,6 +714,7 @@ describe('Create Lifecycle', () => { "name": "Recovered", }, "ruleTaskTimeout": "20m", + "validLegacyConsumers": Array [], }, } `); diff --git a/x-pack/plugins/alerting/server/rule_type_registry.ts b/x-pack/plugins/alerting/server/rule_type_registry.ts index c0339275bcf35..68e3e1d3d590b 100644 --- a/x-pack/plugins/alerting/server/rule_type_registry.ts +++ b/x-pack/plugins/alerting/server/rule_type_registry.ts @@ -38,6 +38,7 @@ import { getRuleTypeFeatureUsageName } from './lib/get_rule_type_feature_usage_n import { InMemoryMetrics } from './monitoring'; import { AlertingRulesConfig } from '.'; import { AlertsService } from './alerts_service/alerts_service'; +import { getRuleTypeIdValidLegacyConsumers } from './rule_type_registry_deprecated_consumers'; export interface ConstructorOptions { logger: Logger; @@ -70,6 +71,7 @@ export interface RegistryRuleType enabledInLicense: boolean; hasFieldsForAAD: boolean; hasAlertsMappings: boolean; + validLegacyConsumers: string[]; } /** @@ -102,6 +104,7 @@ export type NormalizedRuleType< RecoveryActionGroupId extends string, AlertData extends RuleAlertData > = { + validLegacyConsumers: string[]; actionGroups: Array>; } & Omit< RuleType< @@ -386,6 +389,7 @@ export class RuleTypeRegistry { doesSetRecoveryContext, alerts, fieldsForAAD, + validLegacyConsumers, }, ]) => { // KEEP the type here to be safe if not the map is ignoring it for some reason @@ -409,6 +413,7 @@ export class RuleTypeRegistry { ).isValid, hasFieldsForAAD: Boolean(fieldsForAAD), hasAlertsMappings: !!alerts, + validLegacyConsumers, ...(alerts ? { alerts } : {}), }; return ruleType; @@ -499,5 +504,6 @@ function augmentActionGroupsWithReserved< ...ruleType, actionGroups: [...actionGroups, ...reservedActionGroups], recoveryActionGroup: recoveryActionGroup ?? RecoveredActionGroup, + validLegacyConsumers: getRuleTypeIdValidLegacyConsumers(id), }; } diff --git a/x-pack/plugins/alerting/server/rule_type_registry_deprecated_consumers.test.ts b/x-pack/plugins/alerting/server/rule_type_registry_deprecated_consumers.test.ts new file mode 100644 index 0000000000000..e1a7828d85042 --- /dev/null +++ b/x-pack/plugins/alerting/server/rule_type_registry_deprecated_consumers.test.ts @@ -0,0 +1,103 @@ +/* + * 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 { + getRuleTypeIdValidLegacyConsumers, + ruleTypeIdWithValidLegacyConsumers, +} from './rule_type_registry_deprecated_consumers'; + +describe('rule_type_registry_deprecated_consumers', () => { + describe('ruleTypeIdWithValidLegacyConsumers', () => { + test('Only these rule type ids should be in the list', () => { + expect(Object.keys(ruleTypeIdWithValidLegacyConsumers)).toMatchInlineSnapshot(` + Array [ + "example.always-firing", + "transform_health", + ".index-threshold", + ".geo-containment", + ".es-query", + "xpack.ml.anomaly_detection_alert", + "xpack.ml.anomaly_detection_jobs_health", + "xpack.synthetics.alerts.monitorStatus", + "xpack.synthetics.alerts.tls", + "xpack.uptime.alerts.monitorStatus", + "xpack.uptime.alerts.tlsCertificate", + "xpack.uptime.alerts.durationAnomaly", + "xpack.uptime.alerts.tls", + "siem.eqlRule", + "siem.savedQueryRule", + "siem.indicatorRule", + "siem.mlRule", + "siem.queryRule", + "siem.thresholdRule", + "siem.newTermsRule", + "siem.notifications", + "slo.rules.burnRate", + "metrics.alert.anomaly", + "logs.alert.document.count", + "metrics.alert.inventory.threshold", + "metrics.alert.threshold", + "monitoring_alert_cluster_health", + "monitoring_alert_license_expiration", + "monitoring_alert_cpu_usage", + "monitoring_alert_missing_monitoring_data", + "monitoring_alert_disk_usage", + "monitoring_alert_thread_pool_search_rejections", + "monitoring_alert_thread_pool_write_rejections", + "monitoring_alert_jvm_memory_usage", + "monitoring_alert_nodes_changed", + "monitoring_alert_logstash_version_mismatch", + "monitoring_alert_kibana_version_mismatch", + "monitoring_alert_elasticsearch_version_mismatch", + "monitoring_ccr_read_exceptions", + "monitoring_shard_size", + "apm.transaction_duration", + "apm.anomaly", + "apm.error_rate", + "apm.transaction_error_rate", + "test.always-firing", + "test.always-firing-alert-as-data", + "test.authorization", + "test.cancellableRule", + "test.cumulative-firing", + "test.exceedsAlertLimit", + "test.failing", + "test.gold.noop", + "test.longRunning", + "test.multipleSearches", + "test.never-firing", + "test.noop", + "test.onlyContextVariables", + "test.onlyStateVariables", + "test.patternFiring", + "test.patternFiringAad", + "test.patternFiringAutoRecoverFalse", + "test.patternLongRunning", + "test.patternLongRunning.cancelAlertsOnRuleTimeout", + "test.patternSuccessOrFailure", + "test.restricted-noop", + "test.throw", + "test.unrestricted-noop", + "test.validation", + ] + `); + }); + }); + describe('getRuleTypeIdValidLegacyConsumers', () => { + test('".es-query" should have "alerts" & "discover" as legacy consumers', () => { + expect(getRuleTypeIdValidLegacyConsumers('.es-query')).toEqual(['alerts', 'discover']); + }); + + test('All other rule types except ".es-query" should have "alerts" as legacy consumer', () => { + for (const ruleTypeId of Object.keys(ruleTypeIdWithValidLegacyConsumers).filter( + (rt) => rt !== '.es-query' + )) { + expect(getRuleTypeIdValidLegacyConsumers(ruleTypeId)).toEqual(['alerts']); + } + }); + }); +}); diff --git a/x-pack/plugins/alerting/server/rule_type_registry_deprecated_consumers.ts b/x-pack/plugins/alerting/server/rule_type_registry_deprecated_consumers.ts new file mode 100644 index 0000000000000..d6a238c414243 --- /dev/null +++ b/x-pack/plugins/alerting/server/rule_type_registry_deprecated_consumers.ts @@ -0,0 +1,88 @@ +/* + * 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 { ALERTS_FEATURE_ID } from './types'; + +export const ruleTypeIdWithValidLegacyConsumers: Record = { + 'example.always-firing': [ALERTS_FEATURE_ID], + transform_health: [ALERTS_FEATURE_ID], + '.index-threshold': [ALERTS_FEATURE_ID], + '.geo-containment': [ALERTS_FEATURE_ID], + '.es-query': [ALERTS_FEATURE_ID, 'discover'], + 'xpack.ml.anomaly_detection_alert': [ALERTS_FEATURE_ID], + 'xpack.ml.anomaly_detection_jobs_health': [ALERTS_FEATURE_ID], + 'xpack.synthetics.alerts.monitorStatus': [ALERTS_FEATURE_ID], + 'xpack.synthetics.alerts.tls': [ALERTS_FEATURE_ID], + 'xpack.uptime.alerts.monitorStatus': [ALERTS_FEATURE_ID], + 'xpack.uptime.alerts.tlsCertificate': [ALERTS_FEATURE_ID], + 'xpack.uptime.alerts.durationAnomaly': [ALERTS_FEATURE_ID], + 'xpack.uptime.alerts.tls': [ALERTS_FEATURE_ID], + 'siem.eqlRule': [ALERTS_FEATURE_ID], + 'siem.savedQueryRule': [ALERTS_FEATURE_ID], + 'siem.indicatorRule': [ALERTS_FEATURE_ID], + 'siem.mlRule': [ALERTS_FEATURE_ID], + 'siem.queryRule': [ALERTS_FEATURE_ID], + 'siem.thresholdRule': [ALERTS_FEATURE_ID], + 'siem.newTermsRule': [ALERTS_FEATURE_ID], + 'siem.notifications': [ALERTS_FEATURE_ID], + 'slo.rules.burnRate': [ALERTS_FEATURE_ID], + 'metrics.alert.anomaly': [ALERTS_FEATURE_ID], + 'logs.alert.document.count': [ALERTS_FEATURE_ID], + 'metrics.alert.inventory.threshold': [ALERTS_FEATURE_ID], + 'metrics.alert.threshold': [ALERTS_FEATURE_ID], + monitoring_alert_cluster_health: [ALERTS_FEATURE_ID], + monitoring_alert_license_expiration: [ALERTS_FEATURE_ID], + monitoring_alert_cpu_usage: [ALERTS_FEATURE_ID], + monitoring_alert_missing_monitoring_data: [ALERTS_FEATURE_ID], + monitoring_alert_disk_usage: [ALERTS_FEATURE_ID], + monitoring_alert_thread_pool_search_rejections: [ALERTS_FEATURE_ID], + monitoring_alert_thread_pool_write_rejections: [ALERTS_FEATURE_ID], + monitoring_alert_jvm_memory_usage: [ALERTS_FEATURE_ID], + monitoring_alert_nodes_changed: [ALERTS_FEATURE_ID], + monitoring_alert_logstash_version_mismatch: [ALERTS_FEATURE_ID], + monitoring_alert_kibana_version_mismatch: [ALERTS_FEATURE_ID], + monitoring_alert_elasticsearch_version_mismatch: [ALERTS_FEATURE_ID], + monitoring_ccr_read_exceptions: [ALERTS_FEATURE_ID], + monitoring_shard_size: [ALERTS_FEATURE_ID], + 'apm.transaction_duration': [ALERTS_FEATURE_ID], + 'apm.anomaly': [ALERTS_FEATURE_ID], + 'apm.error_rate': [ALERTS_FEATURE_ID], + 'apm.transaction_error_rate': [ALERTS_FEATURE_ID], + 'test.always-firing': [ALERTS_FEATURE_ID], + 'test.always-firing-alert-as-data': [ALERTS_FEATURE_ID], + 'test.authorization': [ALERTS_FEATURE_ID], + 'test.cancellableRule': [ALERTS_FEATURE_ID], + 'test.cumulative-firing': [ALERTS_FEATURE_ID], + 'test.exceedsAlertLimit': [ALERTS_FEATURE_ID], + 'test.failing': [ALERTS_FEATURE_ID], + 'test.gold.noop': [ALERTS_FEATURE_ID], + 'test.longRunning': [ALERTS_FEATURE_ID], + 'test.multipleSearches': [ALERTS_FEATURE_ID], + 'test.never-firing': [ALERTS_FEATURE_ID], + 'test.noop': [ALERTS_FEATURE_ID], + 'test.onlyContextVariables': [ALERTS_FEATURE_ID], + 'test.onlyStateVariables': [ALERTS_FEATURE_ID], + 'test.patternFiring': [ALERTS_FEATURE_ID], + 'test.patternFiringAad': [ALERTS_FEATURE_ID], + 'test.patternFiringAutoRecoverFalse': [ALERTS_FEATURE_ID], + 'test.patternLongRunning': [ALERTS_FEATURE_ID], + 'test.patternLongRunning.cancelAlertsOnRuleTimeout': [ALERTS_FEATURE_ID], + 'test.patternSuccessOrFailure': [ALERTS_FEATURE_ID], + 'test.restricted-noop': [ALERTS_FEATURE_ID], + 'test.throw': [ALERTS_FEATURE_ID], + 'test.unrestricted-noop': [ALERTS_FEATURE_ID], + 'test.validation': [ALERTS_FEATURE_ID], +}; + +const getRuleTypeIdValidLegacyConsumers = (ruleTypeId: string): string[] => { + if (ruleTypeIdWithValidLegacyConsumers[ruleTypeId]) { + return ruleTypeIdWithValidLegacyConsumers[ruleTypeId]; + } + return []; +}; + +export { getRuleTypeIdValidLegacyConsumers }; diff --git a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts index e0bbdb0d770ec..5e6e958f3bbc0 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/siem_legacy_actions/migrate_legacy_actions.test.ts @@ -46,6 +46,7 @@ const ruleType: jest.Mocked = { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], }; const context = { diff --git a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts index 84f0a89e72c01..d01d45abc9759 100644 --- a/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/lib/validate_actions.test.ts @@ -32,6 +32,7 @@ describe('validateActions', () => { context: 'context', mappings: { fieldMap: { field: { type: 'fieldType', required: false } } }, }, + validLegacyConsumers: [], }; const data = { diff --git a/x-pack/plugins/alerting/server/rules_client/methods/find.ts b/x-pack/plugins/alerting/server/rules_client/methods/find.ts index 6493174a61b6a..537dfef55aff0 100644 --- a/x-pack/plugins/alerting/server/rules_client/methods/find.ts +++ b/x-pack/plugins/alerting/server/rules_client/methods/find.ts @@ -7,7 +7,7 @@ import Boom from '@hapi/boom'; import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { pick } from 'lodash'; +import { isEmpty, pick } from 'lodash'; import { KueryNode, nodeBuilder } from '@kbn/es-query'; import { AlertConsumers } from '@kbn/rule-data-utils'; import { RawRule, RuleTypeParams, SanitizedRule, Rule } from '../../types'; @@ -34,6 +34,7 @@ export interface FindParams { options?: FindOptions; excludeFromPublicApi?: boolean; includeSnoozeData?: boolean; + featuresIds?: string[]; } export interface FindOptions extends IndexType { @@ -50,6 +51,7 @@ export interface FindOptions extends IndexType { }; fields?: string[]; filter?: string | KueryNode; + filterConsumers?: string[]; } export interface FindResult { @@ -62,7 +64,7 @@ export interface FindResult { export async function find( context: RulesClientContext, { - options: { fields, ...options } = {}, + options: { fields, filterConsumers, ...options } = {}, excludeFromPublicApi = false, includeSnoozeData = false, }: FindParams = {} @@ -71,7 +73,8 @@ export async function find( try { authorizationTuple = await context.authorization.getFindAuthorizationFilter( AlertingAuthorizationEntity.Rule, - alertingAuthorizationFilterOpts + alertingAuthorizationFilterOpts, + isEmpty(filterConsumers) ? undefined : new Set(filterConsumers) ); } catch (error) { context.auditLogger?.log( @@ -84,7 +87,6 @@ export async function find( } const { filter: authorizationFilter, ensureRuleTypeIsAuthorized } = authorizationTuple; - const filterKueryNode = buildKueryNodeFilter(options.filter); let sortField = mapSortField(options.sortField); if (excludeFromPublicApi) { diff --git a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts index febf66a1d2f60..8d478b12d65a9 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/bulk_delete.test.ts @@ -181,6 +181,7 @@ describe('bulkDelete', () => { validate: { params: schema.any(), }, + validLegacyConsumers: [], }); }); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts index 227988aaa6da0..3d697b08dc2f1 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/find.test.ts @@ -91,6 +91,7 @@ describe('find()', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]); beforeEach(() => { @@ -153,6 +154,7 @@ describe('find()', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]) ); @@ -466,6 +468,7 @@ describe('find()', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]) ); @@ -484,6 +487,7 @@ describe('find()', () => { validate: { params: schema.any(), }, + validLegacyConsumers: [], })); ruleTypeRegistry.get.mockImplementationOnce(() => ({ id: '123', @@ -504,6 +508,7 @@ describe('find()', () => { validate: { params: schema.any(), }, + validLegacyConsumers: [], })); unsecuredSavedObjectsClient.find.mockResolvedValue({ total: 2, @@ -674,6 +679,7 @@ describe('find()', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]) ); @@ -692,6 +698,7 @@ describe('find()', () => { validate: { params: schema.any(), }, + validLegacyConsumers: [], })); ruleTypeRegistry.get.mockImplementationOnce(() => ({ id: '123', @@ -712,6 +719,7 @@ describe('find()', () => { validate: { params: schema.any(), }, + validLegacyConsumers: [], })); unsecuredSavedObjectsClient.find.mockResolvedValue({ total: 2, diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts index a5259a2ddd1e0..36a5510a03a3b 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get.test.ts @@ -319,6 +319,7 @@ describe('get()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ @@ -445,6 +446,7 @@ describe('get()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.get.mockResolvedValueOnce({ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts index ca1ef9c275779..6d55fe7f40725 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/get_tags.test.ts @@ -70,6 +70,7 @@ const listedTypes = new Set([ enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]); @@ -123,6 +124,7 @@ describe('getTags()', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]) ); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts index 25afdedf54d3c..ee84a789454ef 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/lib.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/lib.ts @@ -122,6 +122,7 @@ export function getBeforeSetup( validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); rulesClientParams.getEventLogClient.mockResolvedValue( eventLogClient ?? eventLogClientMock.create() diff --git a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts index 8aa5615269404..09aed8b28bfb4 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/list_rule_types.test.ts @@ -76,6 +76,7 @@ describe('listRuleTypes', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const myAppAlertType: RegistryRuleType = { actionGroups: [], @@ -90,6 +91,7 @@ describe('listRuleTypes', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }; const setOfAlertTypes = new Set([myAppAlertType, alertingAlertType]); @@ -134,6 +136,7 @@ describe('listRuleTypes', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, { id: 'myOtherType', @@ -147,6 +150,7 @@ describe('listRuleTypes', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]); beforeEach(() => { @@ -170,6 +174,7 @@ describe('listRuleTypes', () => { enabledInLicense: true, hasAlertsMappings: false, hasFieldsForAAD: false, + validLegacyConsumers: [], }, ]); authorization.filterByRuleTypeAuthorization.mockResolvedValue(authorizedTypes); diff --git a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts index 4733fd81786e9..5dc753f5d4ade 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/resolve.test.ts @@ -294,6 +294,7 @@ describe('resolve()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ @@ -430,6 +431,7 @@ describe('resolve()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); const rulesClient = new RulesClient(rulesClientParams); unsecuredSavedObjectsClient.resolve.mockResolvedValueOnce({ diff --git a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts index 76b62cdad887e..cd97ea0ac1bbd 100644 --- a/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/rules_client/tests/update.test.ts @@ -186,6 +186,7 @@ describe('update()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], }); (migrateLegacyActions as jest.Mock).mockResolvedValue({ hasLegacyActions: false, @@ -1008,6 +1009,7 @@ describe('update()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); unsecuredSavedObjectsClient.create.mockResolvedValueOnce({ id: '1', @@ -1523,6 +1525,7 @@ describe('update()', () => { return { state: {} }; }, producer: 'alerts', + validLegacyConsumers: [], }); await expect( rulesClient.update({ @@ -1907,6 +1910,7 @@ describe('update()', () => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], }); encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: alertId, diff --git a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts index 6b4ce4c16fe8b..c2c1f73bbe5fb 100644 --- a/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/rules_client_conflict_retries.test.ts @@ -373,6 +373,7 @@ beforeEach(() => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], })); ruleTypeRegistry.get.mockReturnValue({ @@ -390,6 +391,7 @@ beforeEach(() => { validate: { params: { validate: (params) => params }, }, + validLegacyConsumers: [], }); rulesClient = new RulesClient(rulesClientParams); diff --git a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts index 22d056f489104..2c1810b0d219e 100644 --- a/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/execution_handler.test.ts @@ -81,6 +81,7 @@ const ruleType: NormalizedRuleType< mappings: { fieldMap: { field: { type: 'fieldType', required: false } } }, }, autoRecoverAlerts: false, + validLegacyConsumers: [], }; const rule = { id: '1', diff --git a/x-pack/plugins/alerting/server/task_runner/fixtures.ts b/x-pack/plugins/alerting/server/task_runner/fixtures.ts index 64c798b868db1..50f26248499e1 100644 --- a/x-pack/plugins/alerting/server/task_runner/fixtures.ts +++ b/x-pack/plugins/alerting/server/task_runner/fixtures.ts @@ -152,6 +152,7 @@ export const ruleType: jest.Mocked = { context: 'test', mappings: { fieldMap: { field: { type: 'keyword', required: false } } }, }, + validLegacyConsumers: [], }; export const mockRunNowResponse = { diff --git a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts index 3cfa7dde78341..786a0fe4a338f 100644 --- a/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts +++ b/x-pack/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -63,6 +63,7 @@ const ruleType: UntypedNormalizedRuleType = { validate: { params: schema.any(), }, + validLegacyConsumers: [], }; let fakeTimer: sinon.SinonFakeTimers; diff --git a/x-pack/plugins/apm/server/feature.ts b/x-pack/plugins/apm/server/feature.ts index b34a202376b2a..79136af2f8856 100644 --- a/x-pack/plugins/apm/server/feature.ts +++ b/x-pack/plugins/apm/server/feature.ts @@ -12,17 +12,14 @@ import { LicensingPluginSetup, LicensingApiRequestHandlerContext, } from '@kbn/licensing-plugin/server'; + import { APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE } from '@kbn/apm-data-access-plugin/server/saved_objects/apm_indices'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { ApmRuleType, APM_SERVER_FEATURE_ID, } from '../common/rules/apm_rule_types'; -const ruleTypes = [ - ...Object.values(ApmRuleType), - OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, -]; +const ruleTypes = Object.values(ApmRuleType); export const APM_FEATURE = { id: APM_SERVER_FEATURE_ID, diff --git a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap index 5ab73d95c15de..3edffdda86868 100644 --- a/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap +++ b/x-pack/plugins/features/server/__snapshots__/oss_features.test.ts.snap @@ -680,15 +680,11 @@ Array [ "privilege": Object { "alerting": Object { "alert": Object { - "all": Array [ - ".es-query", - ], + "all": Array [], "read": Array [], }, "rule": Object { - "all": Array [ - ".es-query", - ], + "all": Array [], "read": Array [], }, }, @@ -739,18 +735,6 @@ Array [ }, Object { "privilege": Object { - "alerting": Object { - "alert": Object { - "all": Array [ - ".es-query", - ], - }, - "rule": Object { - "all": Array [ - ".es-query", - ], - }, - }, "app": Array [ "discover", "kibana", @@ -1284,15 +1268,11 @@ Array [ "privilege": Object { "alerting": Object { "alert": Object { - "all": Array [ - ".es-query", - ], + "all": Array [], "read": Array [], }, "rule": Object { - "all": Array [ - ".es-query", - ], + "all": Array [], "read": Array [], }, }, @@ -1343,18 +1323,6 @@ Array [ }, Object { "privilege": Object { - "alerting": Object { - "alert": Object { - "all": Array [ - ".es-query", - ], - }, - "rule": Object { - "all": Array [ - ".es-query", - ], - }, - }, "app": Array [ "discover", "kibana", diff --git a/x-pack/plugins/features/server/oss_features.ts b/x-pack/plugins/features/server/oss_features.ts index 076ef4941c530..4097780cdfe10 100644 --- a/x-pack/plugins/features/server/oss_features.ts +++ b/x-pack/plugins/features/server/oss_features.ts @@ -32,7 +32,6 @@ export const buildOSSFeatures = ({ category: DEFAULT_APP_CATEGORIES.kibana, app: ['discover', 'kibana'], catalogue: ['discover'], - alerting: ['.es-query'], privileges: { all: { app: ['discover', 'kibana'], @@ -43,14 +42,6 @@ export const buildOSSFeatures = ({ read: ['index-pattern'], }, ui: ['show', 'save', 'saveQuery'], - alerting: { - rule: { - all: ['.es-query'], - }, - alert: { - all: ['.es-query'], - }, - }, }, read: { app: ['discover', 'kibana'], @@ -60,14 +51,6 @@ export const buildOSSFeatures = ({ read: ['index-pattern', 'search', 'query'], }, ui: ['show'], - alerting: { - rule: { - all: ['.es-query'], - }, - alert: { - all: ['.es-query'], - }, - }, }, }, subFeatures: [ diff --git a/x-pack/plugins/infra/server/features.ts b/x-pack/plugins/infra/server/features.ts index 57a9653816e80..5e6f809645ac1 100644 --- a/x-pack/plugins/infra/server/features.ts +++ b/x-pack/plugins/infra/server/features.ts @@ -8,8 +8,9 @@ import { i18n } from '@kbn/i18n'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { logViewSavedObjectName } from '@kbn/logs-shared-plugin/server'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { ES_QUERY_ID } from '@kbn/rule-data-utils'; import { metricsDataSourceSavedObjectName } from '@kbn/metrics-data-access-plugin/server'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../common/alerting/logs/log_threshold/types'; import { METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, @@ -21,6 +22,7 @@ import { infraSourceConfigurationSavedObjectName } from './lib/sources/saved_obj const metricRuleTypes = [ METRIC_THRESHOLD_ALERT_TYPE_ID, METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID, + ES_QUERY_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, ]; @@ -83,7 +85,11 @@ export const METRICS_FEATURE = { }, }; -const logsRuleTypes = [LOG_DOCUMENT_COUNT_RULE_TYPE_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID]; +const logsRuleTypes = [ + LOG_DOCUMENT_COUNT_RULE_TYPE_ID, + ES_QUERY_ID, + OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, +]; export const LOGS_FEATURE = { id: LOGS_FEATURE_ID, diff --git a/x-pack/plugins/observability/common/constants.ts b/x-pack/plugins/observability/common/constants.ts index 97f4341168fd9..11f23808d8fa3 100644 --- a/x-pack/plugins/observability/common/constants.ts +++ b/x-pack/plugins/observability/common/constants.ts @@ -8,9 +8,9 @@ import { i18n } from '@kbn/i18n'; import { AlertConsumers } from '@kbn/rule-data-utils'; import type { ValidFeatureId } from '@kbn/rule-data-utils'; +import type { RuleCreationValidConsumer } from '@kbn/triggers-actions-ui-plugin/public'; export const SLO_BURN_RATE_RULE_TYPE_ID = 'slo.rules.burnRate'; -export const OBSERVABILITY_THRESHOLD_RULE_TYPE_ID = 'observability.rules.custom_threshold'; export const INVALID_EQUATION_REGEX = /[^A-Z|+|\-|\s|\d+|\.|\(|\)|\/|\*|>|<|=|\?|\:|&|\!|\|]+/g; export const ALERT_STATUS_ALL = 'all'; @@ -56,3 +56,8 @@ export const observabilityAlertFeatureIds: ValidFeatureId[] = [ AlertConsumers.SLO, AlertConsumers.OBSERVABILITY, ]; + +export const observabilityRuleCreationValidConsumers: RuleCreationValidConsumer[] = [ + AlertConsumers.INFRASTRUCTURE, + AlertConsumers.LOGS, +]; diff --git a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx index 824d5b41d14ca..ba39bcd7c4861 100644 --- a/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx +++ b/x-pack/plugins/observability/public/components/custom_threshold/components/alert_flyout.tsx @@ -6,12 +6,14 @@ */ import React, { useCallback, useContext, useMemo } from 'react'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; + +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { MetricsExplorerSeries } from '../../../../common/custom_threshold_rule/metrics_explorer'; import { TriggerActionsContext } from './triggers_actions_context'; import { useAlertPrefillContext } from '../helpers/use_alert_prefill'; import { MetricsExplorerOptions } from '../hooks/use_metrics_explorer_options'; +import { observabilityRuleCreationValidConsumers } from '../../../../common/constants'; interface Props { visible?: boolean; @@ -28,7 +30,7 @@ export function AlertFlyout(props: Props) { () => triggersActionsUI && triggersActionsUI.getAddRuleFlyout({ - consumer: 'alerts', + consumer: 'logs', onClose: onCloseFlyout, canChangeTrigger: false, ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, @@ -36,6 +38,7 @@ export function AlertFlyout(props: Props) { currentOptions: props.options, series: props.series, }, + validConsumers: observabilityRuleCreationValidConsumers, }), // eslint-disable-next-line react-hooks/exhaustive-deps [triggersActionsUI, onCloseFlyout] diff --git a/x-pack/plugins/observability/public/hooks/use_get_filtered_rule_types.ts b/x-pack/plugins/observability/public/hooks/use_get_filtered_rule_types.ts index d72118182ed65..834cc31f39d2e 100644 --- a/x-pack/plugins/observability/public/hooks/use_get_filtered_rule_types.ts +++ b/x-pack/plugins/observability/public/hooks/use_get_filtered_rule_types.ts @@ -6,10 +6,13 @@ */ import { useMemo } from 'react'; +import { ES_QUERY_ID } from '@kbn/rule-data-utils'; import { usePluginContext } from './use_plugin_context'; export function useGetFilteredRuleTypes() { const { observabilityRuleTypeRegistry } = usePluginContext(); - return useMemo(() => observabilityRuleTypeRegistry.list(), [observabilityRuleTypeRegistry]); + return useMemo(() => { + return [...observabilityRuleTypeRegistry.list(), ES_QUERY_ID]; + }, [observabilityRuleTypeRegistry]); } diff --git a/x-pack/plugins/observability/public/pages/alerts/alerts.tsx b/x-pack/plugins/observability/public/pages/alerts/alerts.tsx index afd71d888143e..27ae07cbf768d 100644 --- a/x-pack/plugins/observability/public/pages/alerts/alerts.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/alerts.tsx @@ -19,6 +19,7 @@ import { useKibana } from '../../utils/kibana_react'; import { useHasData } from '../../hooks/use_has_data'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useTimeBuckets } from '../../hooks/use_time_buckets'; +import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; import { useToasts } from '../../hooks/use_toast'; import { LoadingObservability } from '../../components/loading_observability'; import { renderRuleStats, RuleStatsState } from './components/rule_stats'; @@ -33,7 +34,6 @@ import { getAlertSummaryTimeRange } from '../../utils/alert_summary_widget'; import { observabilityAlertFeatureIds } from '../../../common/constants'; import { ALERTS_URL_STORAGE_KEY } from '../../../common/constants'; import { HeaderMenu } from '../overview/components/header_menu/header_menu'; -import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; const ALERTS_SEARCH_BAR_ID = 'alerts-search-bar-o11y'; const ALERTS_PER_PAGE = 50; @@ -130,6 +130,7 @@ function InternalAlertsPage() { const response = await loadRuleAggregations({ http, typesFilter: filteredRuleTypes, + filterConsumers: observabilityAlertFeatureIds, }); const { ruleExecutionStatus, ruleMutedStatus, ruleEnabledStatus, ruleSnoozedStatus } = response; diff --git a/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx b/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx index 1ba58e56aa77e..567bb32b07f8c 100644 --- a/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx +++ b/x-pack/plugins/observability/public/pages/alerts/components/alert_actions.tsx @@ -20,8 +20,7 @@ import { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public'; import { AttachmentType } from '@kbn/cases-plugin/common'; import { EcsSecurityExtension as Ecs } from '@kbn/securitysolution-ecs'; import { TimelineNonEcsData } from '@kbn/timelines-plugin/common'; - -import { ALERT_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { ALERT_RULE_TYPE_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { useKibana } from '../../../utils/kibana_react'; import { useGetUserCasesPermissions } from '../../../hooks/use_get_user_cases_permissions'; @@ -32,7 +31,6 @@ import { RULE_DETAILS_PAGE_ID } from '../../rule_details/constants'; import type { ObservabilityRuleTypeRegistry } from '../../..'; import type { ConfigSchema } from '../../../plugin'; import type { TopAlert } from '../../../typings/alerts'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '../../../../common/constants'; const ALERT_DETAILS_PAGE_ID = 'alert-details-o11y'; diff --git a/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx b/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx index 6e14506604a61..5585ccdeb7099 100644 --- a/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx +++ b/x-pack/plugins/observability/public/pages/rule_details/rule_details.tsx @@ -17,6 +17,7 @@ import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useFetchRule } from '../../hooks/use_fetch_rule'; import { useFetchRuleTypes } from '../../hooks/use_fetch_rule_types'; +import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; import { PageTitle } from './components/page_title'; import { DeleteConfirmationModal } from './components/delete_confirmation_modal'; import { CenterJustifiedSpinner } from '../../components/center_justified_spinner'; @@ -39,7 +40,6 @@ import { } from '../../utils/alert_summary_widget'; import type { AlertStatus } from '../../../common/typings'; import { HeaderMenu } from '../overview/components/header_menu/header_menu'; -import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; export type TabId = typeof RULE_DETAILS_ALERTS_TAB | typeof RULE_DETAILS_EXECUTION_TAB; diff --git a/x-pack/plugins/observability/public/pages/rules/rules.tsx b/x-pack/plugins/observability/public/pages/rules/rules.tsx index af3eba0b67636..700f348842757 100644 --- a/x-pack/plugins/observability/public/pages/rules/rules.tsx +++ b/x-pack/plugins/observability/public/pages/rules/rules.tsx @@ -18,6 +18,7 @@ import { RULES_LOGS_PATH, RULES_PATH } from '../../../common/locators/paths'; import { useKibana } from '../../utils/kibana_react'; import { usePluginContext } from '../../hooks/use_plugin_context'; import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; +import { observabilityRuleCreationValidConsumers } from '../../../common/constants'; import { HeaderMenu } from '../overview/components/header_menu/header_menu'; import { RulesTab } from './rules_tab'; @@ -141,6 +142,7 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) { { setAddRuleFlyoutVisibility(false); }} @@ -148,6 +150,7 @@ export function RulesPage({ activeTab = RULES_TAB_NAME }: RulesPageProps) { setRefresh(new Date()); return Promise.resolve(); }} + useRuleProducer={true} /> )} diff --git a/x-pack/plugins/observability/public/pages/rules/rules_tab.tsx b/x-pack/plugins/observability/public/pages/rules/rules_tab.tsx index 317091e81fb29..a2939bb4876e9 100644 --- a/x-pack/plugins/observability/public/pages/rules/rules_tab.tsx +++ b/x-pack/plugins/observability/public/pages/rules/rules_tab.tsx @@ -11,6 +11,7 @@ import { createKbnUrlStateStorage } from '@kbn/kibana-utils-plugin/public'; import { RuleStatus } from '@kbn/triggers-actions-ui-plugin/public'; import { useKibana } from '../../utils/kibana_react'; import { useGetFilteredRuleTypes } from '../../hooks/use_get_filtered_rule_types'; +import { observabilityAlertFeatureIds } from '../../../common/constants'; interface RulesTabProps { setRefresh: React.Dispatch>; @@ -73,6 +74,7 @@ export function RulesTab({ setRefresh, stateRefresh }: RulesTabProps) { return ( { private logger: Logger; diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts index 16781a17962c9..47eeb6e46c406 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts @@ -10,7 +10,6 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { Filter, buildEsQuery, EsQueryConfig } from '@kbn/es-query'; import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils'; import { - AlertConsumers, ALERT_TIME_RANGE, ALERT_STATUS, getEsQueryConfig, @@ -29,7 +28,7 @@ import { InlineScript, QueryDslQueryContainer, } from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import { RuleTypeParams } from '@kbn/alerting-plugin/server'; +import { RuleTypeParams, PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/server'; import { ReadOperations, AlertingAuthorization, @@ -50,7 +49,7 @@ import { SPACE_IDS, } from '../../common/technical_rule_data_field_names'; import { ParsedTechnicalFields } from '../../common/parse_technical_fields'; -import { Dataset, IRuleDataService } from '../rule_data_plugin_service'; +import { IRuleDataService } from '../rule_data_plugin_service'; import { getAuthzFilter, getSpacesFilter } from '../lib'; import { fieldDescriptorToBrowserFieldMapper } from './browser_fields'; @@ -81,6 +80,7 @@ export interface ConstructorOptions { esClient: ElasticsearchClient; ruleDataService: IRuleDataService; getRuleType: RuleTypeRegistry['get']; + getAlertIndicesAlias: AlertingStart['getAlertIndicesAlias']; } export interface UpdateOptions { @@ -137,6 +137,7 @@ interface SingleSearchAfterAndAudit { operation: WriteOperations.Update | ReadOperations.Find | ReadOperations.Get; sort?: estypes.SortOptions[] | undefined; lastSortIds?: Array | undefined; + featureIds?: string[]; } /** @@ -152,6 +153,7 @@ export class AlertsClient { private readonly spaceId: string | undefined; private readonly ruleDataService: IRuleDataService; private readonly getRuleType: RuleTypeRegistry['get']; + private getAlertIndicesAlias!: AlertingStart['getAlertIndicesAlias']; constructor(options: ConstructorOptions) { this.logger = options.logger; @@ -163,6 +165,7 @@ export class AlertsClient { this.spaceId = this.authorization.getSpaceId(); this.ruleDataService = options.ruleDataService; this.getRuleType = options.getRuleType; + this.getAlertIndicesAlias = options.getAlertIndicesAlias; } private getOutcome( @@ -281,6 +284,7 @@ export class AlertsClient { operation, sort, lastSortIds = [], + featureIds, }: SingleSearchAfterAndAudit) { try { const alertSpaceId = this.spaceId; @@ -294,7 +298,14 @@ export class AlertsClient { let queryBody: estypes.SearchRequest['body'] = { fields: [ALERT_RULE_TYPE_ID, ALERT_RULE_CONSUMER, ALERT_WORKFLOW_STATUS, SPACE_IDS], - query: await this.buildEsQueryWithAuthz(query, id, alertSpaceId, operation, config), + query: await this.buildEsQueryWithAuthz( + query, + id, + alertSpaceId, + operation, + config, + featureIds ? new Set(featureIds) : undefined + ), aggs, _source, track_total_hits: trackTotalHits, @@ -433,10 +444,15 @@ export class AlertsClient { id: string | null | undefined, alertSpaceId: string, operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find, - config: EsQueryConfig + config: EsQueryConfig, + featuresIds?: Set ) { try { - const authzFilter = (await getAuthzFilter(this.authorization, operation)) as Filter; + const authzFilter = (await getAuthzFilter( + this.authorization, + operation, + featuresIds + )) as Filter; const spacesFilter = getSpacesFilter(alertSpaceId) as unknown as Filter; let esQuery; if (id != null) { @@ -681,6 +697,7 @@ export class AlertsClient { }, }, size: 0, + featureIds, }); let activeAlertCount = 0; @@ -1006,35 +1023,16 @@ export class AlertsClient { public async getAuthorizedAlertsIndices(featureIds: string[]): Promise { try { - // ATTENTION FUTURE DEVELOPER when you are a super user the augmentedRuleTypes.authorizedRuleTypes will - // return all of the features that you can access and does not care about your featureIds - const augmentedRuleTypes = await this.authorization.getAugmentedRuleTypesWithAuthorization( - featureIds, - [ReadOperations.Find, ReadOperations.Get, WriteOperations.Update], - AlertingAuthorizationEntity.Alert + const authorizedRuleTypes = await this.authorization.getAuthorizedRuleTypes( + AlertingAuthorizationEntity.Alert, + new Set(featureIds) ); - // As long as the user can read a minimum of one type of rule type produced by the provided feature, - // the user should be provided that features' alerts index. - // Limiting which alerts that user can read on that index will be done via the findAuthorizationFilter - const authorizedFeatures = new Set(); - for (const ruleType of augmentedRuleTypes.authorizedRuleTypes) { - authorizedFeatures.add(ruleType.producer); - } - const validAuthorizedFeatures = Array.from(authorizedFeatures).filter( - (feature): feature is ValidFeatureId => - featureIds.includes(feature) && isValidFeatureId(feature) + const indices = this.getAlertIndicesAlias( + authorizedRuleTypes.map((art: { id: any }) => art.id), + this.spaceId ); - const toReturn = validAuthorizedFeatures.map((feature) => { - const index = this.ruleDataService.findIndexByFeature(feature, Dataset.alerts); - if (index == null) { - throw new Error(`This feature id ${feature} should be associated to an alert index`); - } - return ( - index?.getPrimaryAlias(feature === AlertConsumers.SIEM ? this.spaceId ?? '*' : '*') ?? '' - ); - }); - return toReturn; + return indices; } catch (exc) { const errMessage = `getAuthorizedAlertsIndices failed to get authorized rule types: ${exc}`; this.logger.error(errMessage); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts index bbb33244e2975..43966d1207004 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.test.ts @@ -26,6 +26,7 @@ const alertsClientFactoryParams: AlertsClientFactoryProps = { esClient: {} as ElasticsearchClient, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; const auditLogger = auditLoggerMock.create(); @@ -53,6 +54,7 @@ describe('AlertsClientFactory', () => { esClient: {}, ruleDataService: alertsClientFactoryParams.ruleDataService, getRuleType: alertsClientFactoryParams.getRuleType, + getAlertIndicesAlias: alertsClientFactoryParams.getAlertIndicesAlias, }); }); diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts index 9a171514b588a..de0afb5a0b226 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client_factory.ts @@ -8,7 +8,10 @@ import { PublicMethodsOf } from '@kbn/utility-types'; import { ElasticsearchClient, KibanaRequest, Logger } from '@kbn/core/server'; import type { RuleTypeRegistry } from '@kbn/alerting-plugin/server/types'; -import { AlertingAuthorization } from '@kbn/alerting-plugin/server'; +import { + AlertingAuthorization, + PluginStartContract as AlertingStart, +} from '@kbn/alerting-plugin/server'; import { SecurityPluginSetup } from '@kbn/security-plugin/server'; import { IRuleDataService } from '../rule_data_plugin_service'; import { AlertsClient } from './alerts_client'; @@ -20,6 +23,7 @@ export interface AlertsClientFactoryProps { securityPluginSetup: SecurityPluginSetup | undefined; ruleDataService: IRuleDataService | null; getRuleType: RuleTypeRegistry['get']; + getAlertIndicesAlias: AlertingStart['getAlertIndicesAlias']; } export class AlertsClientFactory { @@ -32,6 +36,7 @@ export class AlertsClientFactory { private securityPluginSetup!: SecurityPluginSetup | undefined; private ruleDataService!: IRuleDataService | null; private getRuleType!: RuleTypeRegistry['get']; + private getAlertIndicesAlias!: AlertingStart['getAlertIndicesAlias']; public initialize(options: AlertsClientFactoryProps) { /** @@ -48,6 +53,7 @@ export class AlertsClientFactory { this.securityPluginSetup = options.securityPluginSetup; this.ruleDataService = options.ruleDataService; this.getRuleType = options.getRuleType; + this.getAlertIndicesAlias = options.getAlertIndicesAlias; } public async create(request: KibanaRequest): Promise { @@ -60,6 +66,7 @@ export class AlertsClientFactory { esClient: this.esClient, ruleDataService: this.ruleDataService!, getRuleType: this.getRuleType, + getAlertIndicesAlias: this.getAlertIndicesAlias, }); } } diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts index fb3acbd5e26fd..4229ae23793fc 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update.test.ts @@ -31,6 +31,7 @@ const alertsClientParams: jest.Mocked = { auditLogger, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; const DEFAULT_SPACE = 'test_default_space_id'; @@ -334,10 +335,10 @@ describe('bulkUpdate()', () => { status: 'closed', }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update - Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update - Error: Error: Unauthorized for fake.rule and apm" - `); + "queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update + Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update + Error: Error: Unauthorized for fake.rule and apm" + `); expect(auditLogger.log).toHaveBeenNthCalledWith(1, { message: `Failed attempt to update alert [id=${fakeAlertId}]`, @@ -401,10 +402,10 @@ describe('bulkUpdate()', () => { status: 'closed', }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update - Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update - Error: Error: Unauthorized for fake.rule and apm" - `); + "queryAndAuditAllAlerts threw an error: Unable to retrieve alerts with query \\"kibana.alert.status: active\\" and operation update + Error: Unable to retrieve alert details for alert with id of \\"null\\" or with query \\"kibana.alert.status: active\\" and operation update + Error: Error: Unauthorized for fake.rule and apm" + `); expect(auditLogger.log).toHaveBeenCalledTimes(2); expect(auditLogger.log).toHaveBeenNthCalledWith(1, { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update_cases.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update_cases.test.ts index 20db7747fc898..4047a3ecadd27 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update_cases.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/bulk_update_cases.test.ts @@ -37,6 +37,7 @@ describe('bulkUpdateCases', () => { auditLogger, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts index b287ba863e5fa..37ad46a523a70 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/find_alerts.test.ts @@ -30,6 +30,7 @@ const alertsClientParams: jest.Mocked = { auditLogger, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; const DEFAULT_SPACE = 'test_default_space_id'; @@ -420,9 +421,9 @@ describe('find()', () => { index: '.alerts-observability.apm.alerts', }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find - Error: Error: Unauthorized for fake.rule and apm" - `); + "Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find + Error: Error: Unauthorized for fake.rule and apm" + `); expect(auditLogger.log).toHaveBeenNthCalledWith(1, { message: `Failed attempt to access alert [id=${fakeAlertId}]`, @@ -450,9 +451,9 @@ describe('find()', () => { index: '.alerts-observability.apm.alerts', }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find - Error: Error: something went wrong" - `); + "Unable to retrieve alert details for alert with id of \\"undefined\\" or with query \\"[object Object]\\" and operation find + Error: Error: something went wrong" + `); }); describe('authorization', () => { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts index c7b342f0fa548..fb1e0eef432ef 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts @@ -31,6 +31,7 @@ const alertsClientParams: jest.Mocked = { auditLogger, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; const DEFAULT_SPACE = 'test_default_space_id'; @@ -266,9 +267,9 @@ describe('get()', () => { await expect(alertsClient.get({ id: fakeAlertId, index: '.alerts-observability.apm.alerts' })) .rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation get - Error: Error: Unauthorized for fake.rule and apm" - `); + "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation get + Error: Error: Unauthorized for fake.rule and apm" + `); expect(auditLogger.log).toHaveBeenNthCalledWith(1, { message: `Failed attempt to access alert [id=${fakeAlertId}]`, @@ -293,9 +294,9 @@ describe('get()', () => { await expect( alertsClient.get({ id: 'NoxgpHkBqbdrfX07MqXV', index: '.alerts-observability.apm.alerts' }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation get - Error: Error: something went wrong" - `); + "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation get + Error: Error: something went wrong" + `); }); describe('authorization', () => { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts index 08f0c3c21ea37..2611200afd85f 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/remove_cases_from_alerts.test.ts @@ -32,6 +32,7 @@ describe('remove cases from alerts', () => { auditLogger, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; beforeEach(() => { @@ -89,6 +90,7 @@ describe('remove cases from alerts', () => { auditLogger, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; beforeEach(() => { diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts index 4db890d93b326..bca5e7d967f3a 100644 --- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts +++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts @@ -30,6 +30,7 @@ const alertsClientParams: jest.Mocked = { auditLogger, ruleDataService: ruleDataServiceMock.create(), getRuleType: jest.fn(), + getAlertIndicesAlias: jest.fn(), }; const DEFAULT_SPACE = 'test_default_space_id'; @@ -257,9 +258,9 @@ describe('update()', () => { index: '.alerts-observability.apm.alerts', }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation update - Error: Error: Unauthorized for fake.rule and apm" - `); + "Unable to retrieve alert details for alert with id of \\"myfakeid1\\" or with query \\"undefined\\" and operation update + Error: Error: Unauthorized for fake.rule and apm" + `); expect(auditLogger.log).toHaveBeenNthCalledWith(1, { message: `Failed attempt to update alert [id=${fakeAlertId}]`, @@ -289,9 +290,9 @@ describe('update()', () => { index: '.alerts-observability.apm.alerts', }) ).rejects.toThrowErrorMatchingInlineSnapshot(` - "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation update - Error: Error: something went wrong on update" - `); + "Unable to retrieve alert details for alert with id of \\"NoxgpHkBqbdrfX07MqXV\\" or with query \\"undefined\\" and operation update + Error: Error: something went wrong on update" + `); }); test(`throws an error if ES client update fails`, async () => { diff --git a/x-pack/plugins/rule_registry/server/lib/get_authz_filter.ts b/x-pack/plugins/rule_registry/server/lib/get_authz_filter.ts index 35bab55c7c4e3..e1524b99f88d9 100644 --- a/x-pack/plugins/rule_registry/server/lib/get_authz_filter.ts +++ b/x-pack/plugins/rule_registry/server/lib/get_authz_filter.ts @@ -19,7 +19,8 @@ import { export async function getAuthzFilter( authorization: PublicMethodsOf, - operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find + operation: WriteOperations.Update | ReadOperations.Get | ReadOperations.Find, + featuresIds?: Set ) { const { filter } = await authorization.getAuthorizationFilter( AlertingAuthorizationEntity.Alert, @@ -27,7 +28,8 @@ export async function getAuthzFilter( type: AlertingAuthorizationFilterType.ESDSL, fieldNames: { consumer: ALERT_RULE_CONSUMER, ruleTypeId: ALERT_RULE_TYPE_ID }, }, - operation + operation, + featuresIds ); return filter; } diff --git a/x-pack/plugins/rule_registry/server/plugin.ts b/x-pack/plugins/rule_registry/server/plugin.ts index 0b17e237057e6..6fba837a10c1a 100644 --- a/x-pack/plugins/rule_registry/server/plugin.ts +++ b/x-pack/plugins/rule_registry/server/plugin.ts @@ -166,6 +166,7 @@ export class RuleRegistryPlugin securityPluginSetup: security, ruleDataService, getRuleType: plugins.alerting.getType, + getAlertIndicesAlias: plugins.alerting.getAlertIndicesAlias, }); const getRacClientWithRequest = (request: KibanaRequest) => { diff --git a/x-pack/plugins/rule_registry/server/routes/get_browser_fields_by_feature_id.ts b/x-pack/plugins/rule_registry/server/routes/get_browser_fields_by_feature_id.ts index 3441a8571e81f..259ca03478745 100644 --- a/x-pack/plugins/rule_registry/server/routes/get_browser_fields_by_feature_id.ts +++ b/x-pack/plugins/rule_registry/server/routes/get_browser_fields_by_feature_id.ts @@ -35,11 +35,13 @@ export const getBrowserFieldsByFeatureId = (router: IRouter fId !== 'siem' ); const o11yIndices = - indices?.filter((index) => index.startsWith('.alerts-observability')) ?? []; + (onlyO11yFeatureIds + ? await alertsClient.getAuthorizedAlertsIndices(onlyO11yFeatureIds) + : []) ?? []; if (o11yIndices.length === 0) { return response.notFound({ body: { diff --git a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts index 76c1168d50d0b..f8da02ad0666d 100644 --- a/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts +++ b/x-pack/plugins/rule_registry/server/search_strategy/search_strategy.ts @@ -72,20 +72,18 @@ export const ruleRegistrySearchStrategyProvider = ( alerting.getAlertingAuthorizationWithRequest(deps.request), ]); let authzFilter; - - if (!siemRequest) { + const fIds = new Set(featureIds); + if (!siemRequest && featureIds.length > 0) { authzFilter = (await getAuthzFilter( authorization, - ReadOperations.Find + ReadOperations.Find, + fIds )) as estypes.QueryDslQueryContainer; } const authorizedRuleTypes = featureIds.length > 0 - ? await authorization.getAuthorizedRuleTypes( - AlertingAuthorizationEntity.Alert, - new Set(featureIds) - ) + ? await authorization.getAuthorizedRuleTypes(AlertingAuthorizationEntity.Alert, fIds) : []; return { space, authzFilter, authorizedRuleTypes }; }; diff --git a/x-pack/plugins/session_view/server/routes/alerts_client_mock.test.ts b/x-pack/plugins/session_view/server/routes/alerts_client_mock.test.ts index 282d4297d3670..10f1c48977c34 100644 --- a/x-pack/plugins/session_view/server/routes/alerts_client_mock.test.ts +++ b/x-pack/plugins/session_view/server/routes/alerts_client_mock.test.ts @@ -57,6 +57,7 @@ const getResponse = async () => { }; const esClientMock = elasticsearchServiceMock.createElasticsearchClient(getResponse()); +const getAlertIndicesAliasMock = jest.fn(); const alertsClientParams: jest.Mocked = { logger: loggingSystemMock.create().get(), authorization: alertingAuthMock, @@ -64,6 +65,7 @@ const alertsClientParams: jest.Mocked = { ruleDataService: ruleDataServiceMock.create(), esClient: esClientMock, getRuleType: jest.fn(), + getAlertIndicesAlias: getAlertIndicesAliasMock, }; export function getAlertsClientMockInstance(esClient?: ElasticsearchClient) { @@ -86,6 +88,20 @@ export function resetAlertingAuthMock() { authorizedRuleTypes.add({ producer: 'apm' }); return Promise.resolve({ authorizedRuleTypes }); }); + // @ts-expect-error + alertingAuthMock.getAuthorizedRuleTypes.mockImplementation(async () => { + const authorizedRuleTypes = [ + { + producer: 'apm', + id: 'apm.error_rate', + alerts: { + context: 'observability.apm', + }, + }, + ]; + return Promise.resolve(authorizedRuleTypes); + }); + getAlertIndicesAliasMock.mockReturnValue(['.alerts-observability.apm-default']); alertingAuthMock.ensureAuthorized.mockImplementation( // @ts-expect-error diff --git a/x-pack/plugins/stack_alerts/common/constants.ts b/x-pack/plugins/stack_alerts/common/constants.ts index e846b249081e0..a2be12ee63867 100644 --- a/x-pack/plugins/stack_alerts/common/constants.ts +++ b/x-pack/plugins/stack_alerts/common/constants.ts @@ -5,6 +5,4 @@ * 2.0. */ -export const STACK_ALERTS_FEATURE_ID = 'stackAlerts'; - export const MAX_SELECTABLE_GROUP_BY_TERMS = 4; diff --git a/x-pack/plugins/stack_alerts/common/index.ts b/x-pack/plugins/stack_alerts/common/index.ts index afafef61eb76b..60537366f26cb 100644 --- a/x-pack/plugins/stack_alerts/common/index.ts +++ b/x-pack/plugins/stack_alerts/common/index.ts @@ -11,7 +11,6 @@ export { ComparatorFnNames, getHumanReadableComparator, } from './comparator'; -export { STACK_ALERTS_FEATURE_ID } from './constants'; export type { EsqlTable } from './esql_query_utils'; export { rowToDocument, transformDatatableToEsqlTable, toEsQueryHits } from './esql_query_utils'; diff --git a/x-pack/plugins/stack_alerts/kibana.jsonc b/x-pack/plugins/stack_alerts/kibana.jsonc index 73b81c6dfd352..5c7bec1a37a0a 100644 --- a/x-pack/plugins/stack_alerts/kibana.jsonc +++ b/x-pack/plugins/stack_alerts/kibana.jsonc @@ -24,6 +24,7 @@ "requiredBundles": [ "esUiShared", "textBasedLanguages" - ] + ], + "extraPublicDirs": ["common"] } } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx index b6c4cdd72bf62..7117455d30cc1 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/expression/search_source_expression_form.tsx @@ -24,9 +24,9 @@ import { isGroupAggregation, parseAggregationResults, } from '@kbn/triggers-actions-ui-plugin/public/common'; +import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; import { getComparatorScript } from '../../../../common'; import { Comparator } from '../../../../common/comparator_types'; -import { STACK_ALERTS_FEATURE_ID } from '../../../../common'; import { CommonRuleParams, EsQueryRuleMetaData, EsQueryRuleParams, SearchType } from '../types'; import { DEFAULT_VALUES } from '../constants'; import { DataViewSelectPopover } from '../../components/data_view_select_popover'; diff --git a/x-pack/plugins/stack_alerts/public/rule_types/es_query/index.ts b/x-pack/plugins/stack_alerts/public/rule_types/es_query/index.ts index 6ee6079e6683c..7ffd10adce528 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/es_query/index.ts +++ b/x-pack/plugins/stack_alerts/public/rule_types/es_query/index.ts @@ -10,17 +10,17 @@ import { i18n } from '@kbn/i18n'; import { RuleTypeModel } from '@kbn/triggers-actions-ui-plugin/public'; import { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/public'; import { SanitizedRule } from '@kbn/alerting-plugin/common'; +import { ES_QUERY_ID, STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; import { EsQueryRuleParams, SearchType } from './types'; import { validateExpression } from './validation'; +import { isSearchSourceRule } from './util'; const PLUGIN_ID = 'discover'; -const ES_QUERY_ALERT_TYPE = '.es-query'; export function getRuleType(alerting: AlertingSetup): RuleTypeModel { registerNavigation(alerting); - return { - id: ES_QUERY_ALERT_TYPE, + id: ES_QUERY_ID, description: i18n.translate('xpack.stackAlerts.esQuery.ui.alertType.descriptionText', { defaultMessage: 'Alert when matches are found during the latest query run.', }), @@ -46,9 +46,33 @@ export function getRuleType(alerting: AlertingSetup): RuleTypeModel>) => { - return `/app/discover#/viewAlert/${alert.id}`; + ES_QUERY_ID, + (rule: SanitizedRule>) => { + return `/app/discover#/viewAlert/${rule.id}`; + } + ); + alerting.registerNavigation( + STACK_ALERTS_FEATURE_ID, + ES_QUERY_ID, + (rule: SanitizedRule>) => { + if (isSearchSourceRule(rule.params)) return `/app/discover#/viewAlert/${rule.id}`; + return; + } + ); + alerting.registerNavigation( + 'logs', + ES_QUERY_ID, + (rule: SanitizedRule>) => { + if (isSearchSourceRule(rule.params)) return `/app/discover#/viewAlert/${rule.id}`; + return; + } + ); + alerting.registerNavigation( + 'infrastructure', + ES_QUERY_ID, + (rule: SanitizedRule>) => { + if (isSearchSourceRule(rule.params)) return `/app/discover#/viewAlert/${rule.id}`; + return; } ); } diff --git a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx index c07ce2ec8e090..5a578276dbdd0 100644 --- a/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx +++ b/x-pack/plugins/stack_alerts/public/rule_types/geo_containment/rule_form/query_input.tsx @@ -19,7 +19,7 @@ import type { IStorageWrapper } from '@kbn/kibana-utils-plugin/public'; import type { UsageCollectionStart } from '@kbn/usage-collection-plugin/public'; import type { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public'; import type { DataPublicPluginStart } from '@kbn/data-plugin/public'; -import { STACK_ALERTS_FEATURE_ID } from '../../../../common/constants'; +import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; function validateQuery(query: Query) { try { diff --git a/x-pack/plugins/stack_alerts/server/feature.ts b/x-pack/plugins/stack_alerts/server/feature.ts index 7392ee2e5eb9e..1a54134d2cdc1 100644 --- a/x-pack/plugins/stack_alerts/server/feature.ts +++ b/x-pack/plugins/stack_alerts/server/feature.ts @@ -9,10 +9,10 @@ import { i18n } from '@kbn/i18n'; import { KibanaFeatureConfig } from '@kbn/features-plugin/common'; import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; import { TRANSFORM_RULE_TYPE } from '@kbn/transform-plugin/common'; +import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; +import { ES_QUERY_ID as ElasticsearchQuery } from '@kbn/rule-data-utils'; import { ID as IndexThreshold } from './rule_types/index_threshold/rule_type'; import { GEO_CONTAINMENT_ID as GeoContainment } from './rule_types/geo_containment'; -import { ES_QUERY_ID as ElasticsearchQuery } from './rule_types/es_query/constants'; -import { STACK_ALERTS_FEATURE_ID } from '../common'; const TransformHealth = TRANSFORM_RULE_TYPE.TRANSFORM_HEALTH; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/constants.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/constants.ts index 700cba4680bff..598fe60f58c84 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/constants.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/constants.ts @@ -5,6 +5,5 @@ * 2.0. */ -export const ES_QUERY_ID = '.es-query'; export const ActionGroupId = 'query matched'; export const ConditionMetAlertInstanceId = 'query matched'; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_es_query.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_es_query.ts index 8c44c6e673ad2..f44ad3f470106 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_es_query.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/lib/fetch_es_query.ts @@ -12,10 +12,10 @@ import { parseAggregationResults, } from '@kbn/triggers-actions-ui-plugin/common'; import { isGroupAggregation } from '@kbn/triggers-actions-ui-plugin/common'; +import { ES_QUERY_ID } from '@kbn/rule-data-utils'; import { getComparatorScript } from '../../../../common'; import { OnlyEsQueryRuleParams } from '../types'; import { buildSortedEventsQuery } from '../../../../common/build_sorted_events_query'; -import { ES_QUERY_ID } from '../constants'; import { getSearchParams } from './get_search_params'; export interface FetchEsQueryOpts { diff --git a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts index fc70403b174d3..f4657e28e644a 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/es_query/rule_type.ts @@ -8,6 +8,7 @@ import { i18n } from '@kbn/i18n'; import { CoreSetup } from '@kbn/core/server'; import { extractReferences, injectReferences } from '@kbn/data-plugin/common'; +import { ES_QUERY_ID, STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; import { StackAlert } from '@kbn/alerts-as-data-utils'; import { STACK_ALERTS_AAD_CONFIG } from '..'; import { RuleType } from '../../types'; @@ -18,9 +19,8 @@ import { EsQueryRuleParamsSchema, EsQueryRuleState, } from './rule_type_params'; -import { STACK_ALERTS_FEATURE_ID } from '../../../common'; import { ExecutorOptions } from './types'; -import { ActionGroupId, ES_QUERY_ID } from './constants'; +import { ActionGroupId } from './constants'; import { executor } from './executor'; import { isSearchSourceRule } from './util'; diff --git a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts index 938edad8cb394..f6d1668151843 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/geo_containment/rule_type.ts @@ -9,7 +9,7 @@ import { i18n } from '@kbn/i18n'; import { schema } from '@kbn/config-schema'; import { SavedObjectReference } from '@kbn/core/server'; import { RuleParamsAndRefs } from '@kbn/alerting-plugin/server'; -import { STACK_ALERTS_FEATURE_ID } from '../../../common'; +import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; import type { GeoContainmentRuleType, GeoContainmentExtractedRuleParams, diff --git a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts index caec66b632b5d..a2e27231c76f9 100644 --- a/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts +++ b/x-pack/plugins/stack_alerts/server/rule_types/index_threshold/rule_type.ts @@ -12,15 +12,14 @@ import { } from '@kbn/triggers-actions-ui-plugin/server'; import { isGroupAggregation } from '@kbn/triggers-actions-ui-plugin/common'; import { StackAlert } from '@kbn/alerts-as-data-utils'; -import { ALERT_EVALUATION_VALUE, ALERT_REASON } from '@kbn/rule-data-utils'; -import { expandFlattenedAlert } from '@kbn/alerting-plugin/server/alerts_client/lib'; -import { ALERT_EVALUATION_CONDITIONS, ALERT_TITLE, STACK_ALERTS_AAD_CONFIG } from '..'; import { - ComparatorFns, - getComparatorScript, - getHumanReadableComparator, + ALERT_EVALUATION_VALUE, + ALERT_REASON, STACK_ALERTS_FEATURE_ID, -} from '../../../common'; +} from '@kbn/rule-data-utils'; +import { expandFlattenedAlert } from '@kbn/alerting-plugin/server/alerts_client/lib'; +import { ALERT_EVALUATION_CONDITIONS, ALERT_TITLE, STACK_ALERTS_AAD_CONFIG } from '..'; +import { ComparatorFns, getComparatorScript, getHumanReadableComparator } from '../../../common'; import { ActionContext, BaseActionContext, addMessages } from './action_context'; import { Params, ParamsSchema } from './rule_type_params'; import { RuleType, RuleExecutorOptions, StackAlertsStartDeps } from '../../types'; diff --git a/x-pack/plugins/stack_alerts/tsconfig.json b/x-pack/plugins/stack_alerts/tsconfig.json index 08a4f0ca99e9c..8eb5d48f39048 100644 --- a/x-pack/plugins/stack_alerts/tsconfig.json +++ b/x-pack/plugins/stack_alerts/tsconfig.json @@ -32,12 +32,6 @@ "@kbn/i18n-react", "@kbn/charts-plugin", "@kbn/es-ui-shared-plugin", - "@kbn/core-http-browser", - "@kbn/core-doc-links-browser", - "@kbn/core-ui-settings-server", - "@kbn/kibana-utils-plugin", - "@kbn/usage-collection-plugin", - "@kbn/react-field", "@kbn/core-elasticsearch-server-mocks", "@kbn/logging-mocks", "@kbn/share-plugin", @@ -47,6 +41,12 @@ "@kbn/text-based-languages", "@kbn/text-based-editor", "@kbn/expressions-plugin", + "@kbn/core-http-browser", + "@kbn/core-doc-links-browser", + "@kbn/core-ui-settings-server", + "@kbn/kibana-utils-plugin", + "@kbn/usage-collection-plugin", + "@kbn/react-field", ], "exclude": [ "target/**/*", diff --git a/x-pack/plugins/synthetics/server/feature.ts b/x-pack/plugins/synthetics/server/feature.ts index 1a19baa01dcbe..951fdaf33c536 100644 --- a/x-pack/plugins/synthetics/server/feature.ts +++ b/x-pack/plugins/synthetics/server/feature.ts @@ -6,7 +6,6 @@ */ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; import { i18n } from '@kbn/i18n'; import { SubFeaturePrivilegeGroupConfig, @@ -26,11 +25,7 @@ const UPTIME_RULE_TYPES = [ 'xpack.uptime.alerts.durationAnomaly', ]; -const ruleTypes = [ - ...UPTIME_RULE_TYPES, - ...SYNTHETICS_RULE_TYPES, - OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, -]; +const ruleTypes = [...UPTIME_RULE_TYPES, ...SYNTHETICS_RULE_TYPES]; const elasticManagedLocationsEnabledPrivilege: SubFeaturePrivilegeGroupConfig = { groupType: 'independent' as SubFeaturePrivilegeGroupType, diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts index 11e93713cbbc1..bf4cafcf63da9 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/index.ts @@ -6,7 +6,7 @@ */ import { i18n } from '@kbn/i18n'; - +import { ES_QUERY_ID, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; export { BASE_ALERTING_API_PATH, INTERNAL_BASE_ALERTING_API_PATH, @@ -119,3 +119,5 @@ export const CONNECTOR_LOCKED_COLUMNS = ['timestamp', 'status', 'connector_name' export const GLOBAL_CONNECTOR_EXECUTION_DEFAULT_INITIAL_VISIBLE_COLUMNS = [ ...CONNECTOR_LOCKED_COLUMNS, ]; + +export const MULTI_CONSUMER_RULE_TYPE_IDS = [OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, ES_QUERY_ID]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations_query.ts index a581fea1832b7..b7f5be0325729 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations_query.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rule_aggregations_query.ts @@ -25,11 +25,12 @@ const initializeAggregationResult = (values: readonly string[]) => { interface UseLoadRuleAggregationsQueryProps { filters: RulesListFilters; enabled: boolean; + filterConsumers?: string[]; refresh?: Date; } export const useLoadRuleAggregationsQuery = (props: UseLoadRuleAggregationsQueryProps) => { - const { filters, enabled, refresh } = props; + const { filters, enabled, refresh, filterConsumers } = props; const { http, @@ -46,6 +47,7 @@ export const useLoadRuleAggregationsQuery = (props: UseLoadRuleAggregationsQuery ruleLastRunOutcomesFilter: filters.ruleLastRunOutcomes, ruleStatusesFilter: filters.ruleStatuses, tagsFilter: filters.tags, + filterConsumers, }); }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts index d4e045ae038af..90bfea7719b07 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/hooks/use_load_rules_query.ts @@ -20,10 +20,11 @@ type UseLoadRulesQueryProps = Omit & { sort: LoadRulesProps['sort']; enabled: boolean; refresh?: Date; + filterConsumers?: string[]; }; export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => { - const { filters, page, sort, onPage, enabled, refresh } = props; + const { filterConsumers, filters, page, sort, onPage, enabled, refresh } = props; const { http, notifications: { toasts }, @@ -51,6 +52,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => { { refresh: refresh?.toISOString(), }, + filterConsumers, ], queryFn: () => { return loadRulesWithKueryFilter({ @@ -66,6 +68,7 @@ export const useLoadRulesQuery = (props: UseLoadRulesQueryProps) => { tagsFilter: filters.tags, kueryNode: filters.kueryNode, sort, + filterConsumers, }); }, onSuccess: (response) => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts index a995d697e8c54..c74b4b3fd6b90 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/capabilities.ts @@ -29,8 +29,20 @@ export function hasAllPrivilege( ): boolean { return ruleType?.authorizedConsumers[ruleConsumer]?.all ?? false; } + +export function hasAllPrivilegeWithProducerCheck( + ruleConsumer: InitialRule['consumer'], + ruleType?: RuleType +): boolean { + if (ruleConsumer === ruleType?.producer) { + return true; + } + return hasAllPrivilege(ruleConsumer, ruleType); +} + export function hasReadPrivilege(rule: InitialRule, ruleType?: RuleType): boolean { return ruleType?.authorizedConsumers[rule.consumer]?.read ?? false; } + export const hasManageApiKeysCapability = (capabilities: Capabilities) => capabilities?.management?.security?.api_keys; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts index a2f3e1eb80392..8edb10811bd8e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate.ts @@ -44,6 +44,7 @@ export async function loadRuleAggregations({ ruleExecutionStatusesFilter, ruleStatusesFilter, tagsFilter, + filterConsumers, }: LoadRuleAggregationsProps): Promise { const filters = mapFiltersToKql({ typesFilter, @@ -60,6 +61,7 @@ export async function loadRuleAggregations({ search: searchText, filter: filters.length ? filters.join(' and ') : undefined, default_search_operator: 'AND', + filter_consumers: filterConsumers, }), } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts index d093649f73740..29462f07d5eec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_helpers.ts @@ -67,6 +67,7 @@ export interface LoadRuleAggregationsProps { ruleLastRunOutcomesFilter?: string[]; ruleStatusesFilter?: RuleStatus[]; tagsFilter?: string[]; + filterConsumers?: string[]; } export interface LoadRuleTagsProps { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts index 23941ae36ccc5..a0e270ddf2f67 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/aggregate_kuery_filter.ts @@ -21,6 +21,7 @@ export async function loadRuleAggregationsWithKueryFilter({ ruleExecutionStatusesFilter, ruleStatusesFilter, tagsFilter, + filterConsumers, }: LoadRuleAggregationsProps): Promise { const filtersKueryNode = mapFiltersToKueryNode({ typesFilter, @@ -36,6 +37,7 @@ export async function loadRuleAggregationsWithKueryFilter({ { body: JSON.stringify({ ...(filtersKueryNode ? { filter: JSON.stringify(filtersKueryNode) } : {}), + filter_consumers: filterConsumers, default_search_operator: 'AND', }), } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts index ebab20adfb4a1..5effb3cd3c305 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_helpers.ts @@ -24,6 +24,7 @@ export interface LoadRulesProps { ruleStatusesFilter?: RuleStatus[]; sort?: Sorting; kueryNode?: KueryNode; + filterConsumers?: string[]; } export const rewriteRulesResponseRes = (results: Array>): Rule[] => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts index ee3a798e50738..9d38716d35028 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/rule_api/rules_kuery_filter.ts @@ -24,6 +24,7 @@ export async function loadRulesWithKueryFilter({ tagsFilter, sort = { field: 'name', direction: 'asc' }, kueryNode, + filterConsumers, }: LoadRulesProps): Promise<{ page: number; perPage: number; @@ -56,6 +57,7 @@ export async function loadRulesWithKueryFilter({ ...(filtersKueryNode ? { filter: JSON.stringify(filtersKueryNode) } : {}), sort_field: sort.field, sort_order: sort.direction, + filter_consumers: filterConsumers, }), }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx index 92a1329b0e67a..4409be029092c 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.test.tsx @@ -16,12 +16,16 @@ import RuleAdd from './rule_add'; import { createRule } from '../../lib/rule_api/create'; import { alertingFrameworkHealth } from '../../lib/rule_api/health'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { AlertConsumers, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { Rule, RuleAddProps, RuleFlyoutCloseReason, GenericValidationResult, ValidationResult, + RuleCreationValidConsumer, + RuleType, + RuleTypeModel, } from '../../../types'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ReactWrapper } from 'enzyme'; @@ -84,17 +88,30 @@ describe('rule_add', () => { }); let wrapper: ReactWrapper; - async function setup( - initialValues?: Partial, - onClose: RuleAddProps['onClose'] = jest.fn(), - defaultScheduleInterval?: string, - ruleTypeId?: string, - actionsShow: boolean = false - ) { + async function setup({ + initialValues, + onClose = jest.fn(), + defaultScheduleInterval, + ruleTypeId, + actionsShow = false, + validConsumers, + ruleTypesOverwrite, + ruleTypeModelOverwrite, + }: { + initialValues?: Partial; + onClose?: RuleAddProps['onClose']; + defaultScheduleInterval?: string; + ruleTypeId?: string; + actionsShow?: boolean; + validConsumers?: RuleCreationValidConsumer[]; + ruleTypesOverwrite?: RuleType[]; + ruleTypeModelOverwrite?: RuleTypeModel; + }) { const useKibanaMock = useKibana as jest.Mocked; const mocks = coreMock.createSetup(); const { loadRuleTypes } = jest.requireMock('../../lib/rule_api/rule_types'); - const ruleTypes = [ + + const ruleTypes = ruleTypesOverwrite || [ { id: 'my-rule-type', name: 'Test', @@ -144,7 +161,7 @@ describe('rule_add', () => { hasPermanentEncryptionKey: true, }); - const ruleType = { + const ruleType = ruleTypeModelOverwrite || { id: 'my-rule-type', iconClass: 'test', description: 'test', @@ -186,6 +203,7 @@ describe('rule_add', () => { ruleTypeRegistry={ruleTypeRegistry} metadata={{ test: 'some value', fields: ['test'] }} ruleTypeId={ruleTypeId} + validConsumers={validConsumers} /> ); @@ -201,7 +219,10 @@ describe('rule_add', () => { minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); - await setup({}, onClose); + await setup({ + initialValues: {}, + onClose, + }); await act(async () => { await nextTick(); @@ -223,7 +244,10 @@ describe('rule_add', () => { minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); - await setup({}, onClose); + await setup({ + initialValues: {}, + onClose, + }); await act(async () => { await nextTick(); @@ -245,8 +269,8 @@ describe('rule_add', () => { minimumScheduleInterval: { value: '1m', enforce: false }, }); const onClose = jest.fn(); - await setup( - { + await setup({ + initialValues: { name: 'Simple status rule', tags: ['uptime', 'logs'], schedule: { @@ -254,9 +278,8 @@ describe('rule_add', () => { }, }, onClose, - undefined, - 'my-rule-type' - ); + ruleTypeId: 'my-rule-type', + }); expect(wrapper.find('input#ruleName').props().value).toBe('Simple status rule'); expect(wrapper.find('[data-test-subj="tagsComboBox"]').first().text()).toBe('uptimelogs'); @@ -266,7 +289,7 @@ describe('rule_add', () => { it('renders rule add flyout with DEFAULT_RULE_INTERVAL if no initialValues specified and no minimumScheduleInterval', async () => { (triggersActionsUiConfig as jest.Mock).mockResolvedValue({}); - await setup(undefined, undefined, undefined, 'my-rule-type'); + await setup({ ruleTypeId: 'my-rule-type' }); expect(wrapper.find('[data-test-subj="intervalInput"]').first().props().value).toEqual(1); expect(wrapper.find('[data-test-subj="intervalInputUnit"]').first().props().value).toBe('m'); @@ -276,7 +299,7 @@ describe('rule_add', () => { (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '5m', enforce: false }, }); - await setup(undefined, undefined, undefined, 'my-rule-type'); + await setup({ ruleTypeId: 'my-rule-type' }); expect(wrapper.find('[data-test-subj="intervalInput"]').first().props().value).toEqual(5); expect(wrapper.find('[data-test-subj="intervalInputUnit"]').first().props().value).toBe('m'); @@ -291,8 +314,8 @@ describe('rule_add', () => { (createRule as jest.MockedFunction).mockResolvedValue(rule); - await setup( - { + await setup({ + initialValues: { name: 'Simple status rule', ruleTypeId: 'my-rule-type', tags: ['uptime', 'logs'], @@ -300,8 +323,8 @@ describe('rule_add', () => { interval: '1h', }, }, - onClose - ); + onClose, + }); wrapper.find('[data-test-subj="saveRuleButton"]').last().simulate('click'); @@ -317,11 +340,177 @@ describe('rule_add', () => { }); }); + it('should NOT allow to save the rule if the consumer is not set', async () => { + (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + minimumScheduleInterval: { value: '1m', enforce: false }, + }); + const onClose = jest.fn(); + await setup({ + initialValues: { + name: 'Simple rule', + consumer: 'alerts', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + tags: ['uptime', 'logs'], + schedule: { + interval: '1h', + }, + }, + onClose, + ruleTypesOverwrite: [ + { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + name: 'Threshold Rule', + actionGroups: [ + { + id: 'testActionGroup', + name: 'Test Action Group', + }, + ], + enabledInLicense: true, + defaultActionGroupId: 'threshold.fired', + minimumLicenseRequired: 'basic', + recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + producer: ALERTS_FEATURE_ID, + authorizedConsumers: { + alerts: { read: true, all: true }, + apm: { read: true, all: true }, + discover: { read: true, all: true }, + infrastructure: { read: true, all: true }, + logs: { read: true, all: true }, + ml: { read: true, all: true }, + monitoring: { read: true, all: true }, + siem: { read: true, all: true }, + // Setting SLO all to false, this shouldn't show up + slo: { read: true, all: false }, + stackAlerts: { read: true, all: true }, + uptime: { read: true, all: true }, + }, + actionVariables: { + context: [], + state: [], + params: [], + }, + }, + ], + ruleTypeModelOverwrite: { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + iconClass: 'test', + description: 'test', + documentationUrl: null, + validate: (): ValidationResult => { + return { errors: {} }; + }, + ruleParamsExpression: TestExpression, + requiresAppContext: false, + }, + validConsumers: [AlertConsumers.INFRASTRUCTURE, AlertConsumers.LOGS], + }); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + wrapper.find('[data-test-subj="saveRuleButton"]').last().simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(createRule).toBeCalledTimes(0); + }); + + it('should set consumer automatically if only 1 authorized consumer exists', async () => { + (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ + minimumScheduleInterval: { value: '1m', enforce: false }, + }); + const onClose = jest.fn(); + await setup({ + initialValues: { + name: 'Simple rule', + consumer: 'alerts', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + tags: ['uptime', 'logs'], + schedule: { + interval: '1h', + }, + }, + onClose, + ruleTypesOverwrite: [ + { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + name: 'Threshold Rule', + actionGroups: [ + { + id: 'testActionGroup', + name: 'Test Action Group', + }, + ], + enabledInLicense: true, + defaultActionGroupId: 'threshold.fired', + minimumLicenseRequired: 'basic', + recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + producer: ALERTS_FEATURE_ID, + authorizedConsumers: { + logs: { read: true, all: true }, + }, + actionVariables: { + context: [], + state: [], + params: [], + }, + }, + ], + ruleTypeModelOverwrite: { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + iconClass: 'test', + description: 'test', + documentationUrl: null, + validate: (): ValidationResult => { + return { errors: {} }; + }, + ruleParamsExpression: TestExpression, + requiresAppContext: false, + }, + validConsumers: [AlertConsumers.INFRASTRUCTURE, AlertConsumers.LOGS], + }); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="addRuleFlyoutTitle"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="saveRuleButton"]').exists()).toBeTruthy(); + + wrapper.find('[data-test-subj="saveRuleButton"]').last().simulate('click'); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(createRule).toHaveBeenLastCalledWith( + expect.objectContaining({ + rule: expect.objectContaining({ + consumer: 'logs', + }), + }) + ); + }); + it('should enforce any default interval', async () => { (triggersActionsUiConfig as jest.Mock).mockResolvedValue({ minimumScheduleInterval: { value: '1m', enforce: false }, }); - await setup({ ruleTypeId: 'my-rule-type' }, jest.fn(), '3h', 'my-rule-type', true); + await setup({ + initialValues: { ruleTypeId: 'my-rule-type' }, + onClose: jest.fn(), + defaultScheduleInterval: '3h', + ruleTypeId: 'my-rule-type', + actionsShow: true, + }); // Wait for handlers to fire await act(async () => { @@ -344,7 +533,12 @@ describe('rule_add', () => { minimumScheduleInterval: { value: '1m', enforce: false }, }); - await setup({}, jest.fn(), undefined, 'my-rule-type', true); + await setup({ + initialValues: {}, + onClose: jest.fn(), + ruleTypeId: 'my-rule-type', + actionsShow: true, + }); expect(triggersActionsUiHealth).toHaveBeenCalledTimes(1); expect(alertingFrameworkHealth).toHaveBeenCalledTimes(1); @@ -361,7 +555,12 @@ describe('rule_add', () => { hasPermanentEncryptionKey: false, }); - await setup({}, jest.fn(), undefined, 'my-rule-type', true); + await setup({ + initialValues: {}, + onClose: jest.fn(), + ruleTypeId: 'my-rule-type', + actionsShow: true, + }); expect(triggersActionsUiHealth).toHaveBeenCalledTimes(1); expect(alertingFrameworkHealth).toHaveBeenCalledTimes(1); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx index d27abe0831441..777639d962307 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_add.tsx @@ -19,6 +19,7 @@ import { RuleAddProps, RuleTypeIndex, TriggersActionsUiConfig, + RuleCreationValidConsumer, } from '../../../types'; import { RuleForm } from './rule_form'; import { getRuleActionErrors, getRuleErrors, isValidRule } from './rule_errors'; @@ -51,6 +52,8 @@ const RuleAdd = ({ hideInterval, metadata: initialMetadata, filteredRuleTypes, + validConsumers, + useRuleProducer, ...props }: RuleAddProps) => { const onSaveHandler = onSave ?? reloadRules; @@ -83,6 +86,9 @@ const RuleAdd = ({ props.ruleTypeIndex ); const [changedFromDefaultInterval, setChangedFromDefaultInterval] = useState(false); + const [selectedConsumer, setSelectedConsumer] = useState< + RuleCreationValidConsumer | null | undefined + >(); const setRule = (value: InitialRule) => { dispatch({ command: { type: 'setRule' }, payload: { key: 'rule', value } }); @@ -196,10 +202,17 @@ const RuleAdd = ({ }; const ruleType = rule.ruleTypeId ? ruleTypeRegistry.get(rule.ruleTypeId) : null; - const { ruleBaseErrors, ruleErrors, ruleParamsErrors } = useMemo( - () => getRuleErrors(rule as Rule, ruleType, config), - [rule, ruleType, config] + () => + getRuleErrors( + { + ...rule, + ...(selectedConsumer !== undefined ? { consumer: selectedConsumer } : {}), + } as Rule, + ruleType, + config + ), + [rule, selectedConsumer, ruleType, config] ); // Confirm before saving if user is able to add actions but hasn't added any to this rule @@ -207,7 +220,13 @@ const RuleAdd = ({ async function onSaveRule(): Promise { try { - const newRule = await createRule({ http, rule: rule as RuleUpdates }); + const newRule = await createRule({ + http, + rule: { + ...rule, + ...(selectedConsumer ? { consumer: selectedConsumer } : {}), + } as RuleUpdates, + }); toasts.addSuccess( i18n.translate('xpack.triggersActionsUI.sections.ruleAdd.saveSuccessNotificationText', { defaultMessage: 'Created rule "{ruleName}"', @@ -250,6 +269,7 @@ const RuleAdd = ({ { 'schedule.interval': [], ruleTypeId: [], actionConnectors: [], + consumer: [], }); }); @@ -41,6 +42,7 @@ describe('rule_errors', () => { 'schedule.interval': ['Check interval is required.'], ruleTypeId: [], actionConnectors: [], + consumer: [], }); }); @@ -53,6 +55,7 @@ describe('rule_errors', () => { 'schedule.interval': [], ruleTypeId: [], actionConnectors: [], + consumer: [], }); }); @@ -68,6 +71,7 @@ describe('rule_errors', () => { 'schedule.interval': ['Interval must be at least 1 minute.'], ruleTypeId: [], actionConnectors: [], + consumer: [], }); }); @@ -80,6 +84,33 @@ describe('rule_errors', () => { 'schedule.interval': [], ruleTypeId: ['Rule type is required.'], actionConnectors: [], + consumer: [], + }); + }); + + it('should get an error when consumer is null', () => { + const rule = mockRule(); + rule.consumer = null as unknown as string; + const result = validateBaseProperties(rule, config); + expect(result.errors).toStrictEqual({ + name: [], + 'schedule.interval': [], + ruleTypeId: [], + actionConnectors: [], + consumer: ['Scope is required.'], + }); + }); + + it('should not get an error when consumer is undefined', () => { + const rule = mockRule(); + rule.consumer = undefined as unknown as string; + const result = validateBaseProperties(rule, config); + expect(result.errors).toStrictEqual({ + name: [], + 'schedule.interval': [], + ruleTypeId: [], + actionConnectors: [], + consumer: [], }); }); @@ -101,6 +132,7 @@ describe('rule_errors', () => { 'schedule.interval': [], ruleTypeId: [], actionConnectors: ['Action for myActionType connector is required.'], + consumer: [], }); }); }); @@ -127,6 +159,7 @@ describe('rule_errors', () => { 'schedule.interval': [], ruleTypeId: [], actionConnectors: [], + consumer: [], }, ruleErrors: { name: ['Name is required.'], @@ -134,6 +167,7 @@ describe('rule_errors', () => { 'schedule.interval': [], ruleTypeId: [], actionConnectors: [], + consumer: [], }, }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts index 02e2e7c084009..0b5c511843472 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_errors.ts @@ -27,6 +27,7 @@ export function validateBaseProperties( const errors = { name: new Array(), 'schedule.interval': new Array(), + consumer: new Array(), ruleTypeId: new Array(), actionConnectors: new Array(), }; @@ -38,6 +39,13 @@ export function validateBaseProperties( }) ); } + if (ruleObject.consumer === null) { + errors.consumer.push( + i18n.translate('xpack.triggersActionsUI.sections.ruleForm.error.requiredConsumerText', { + defaultMessage: 'Scope is required.', + }) + ); + } if (ruleObject.schedule.interval.length < 2) { errors['schedule.interval'].push( i18n.translate('xpack.triggersActionsUI.sections.ruleForm.error.requiredIntervalText', { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx index f0de7e6e4ed60..59d3c4f1f8c3b 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.test.tsx @@ -5,19 +5,24 @@ * 2.0. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; +import { EuiFormLabel } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n-react'; import { mountWithIntl, nextTick } from '@kbn/test-jest-helpers'; import { ReactWrapper } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { actionTypeRegistryMock } from '../../action_type_registry.mock'; import { ruleTypeRegistryMock } from '../../rule_type_registry.mock'; import { ActionForm } from '../action_connector_form'; +import { AlertConsumers, OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; +import { RuleFormConsumerSelection } from './rule_form_consumer_selection'; import { ValidationResult, Rule, RuleType, RuleTypeModel, GenericValidationResult, + RuleCreationValidConsumer, } from '../../../types'; import { RuleForm } from './rule_form'; import { coreMock } from '@kbn/core/public/mocks'; @@ -27,6 +32,20 @@ import { useKibana } from '../../../common/lib/kibana'; const actionTypeRegistry = actionTypeRegistryMock.create(); const ruleTypeRegistry = ruleTypeRegistryMock.create(); +const mockSetConsumer = jest.fn(); + +export const TestExpression: FunctionComponent = () => { + return ( + + + + ); +}; + jest.mock('../../hooks/use_load_rule_types', () => ({ useLoadRuleTypes: jest.fn(), })); @@ -233,15 +252,32 @@ describe('rule_form', () => { describe('rule_form create rule', () => { let wrapper: ReactWrapper; - async function setup( - showRulesList = false, - enforceMinimum = false, - schedule = '1m', - featureId = 'alerting' - ) { + async function setup(options?: { + showRulesList?: boolean; + enforceMinimum?: boolean; + schedule?: string; + featureId?: string; + initialRuleOverwrite?: Partial; + validConsumers?: RuleCreationValidConsumer[]; + ruleTypesOverwrite?: RuleType[]; + ruleTypeModelOverwrite?: RuleTypeModel; + useRuleProducer?: boolean; + }) { + const { + showRulesList = false, + enforceMinimum = false, + schedule = '1m', + featureId = 'alerting', + initialRuleOverwrite, + validConsumers, + ruleTypesOverwrite, + ruleTypeModelOverwrite, + useRuleProducer = false, + } = options || {}; + const mocks = coreMock.createSetup(); const { useLoadRuleTypes } = jest.requireMock('../../hooks/use_load_rule_types'); - const ruleTypes: RuleType[] = [ + const ruleTypes: RuleType[] = ruleTypesOverwrite || [ { id: 'my-rule-type', name: 'Test', @@ -305,10 +341,11 @@ describe('rule_form', () => { }, }; ruleTypeRegistry.list.mockReturnValue([ - ruleType, + ruleTypeModelOverwrite || ruleType, ruleTypeNonEditable, disabledByLicenseRuleType, ]); + ruleTypeRegistry.get.mockReturnValue(ruleTypeModelOverwrite || ruleType); ruleTypeRegistry.has.mockReturnValue(true); actionTypeRegistry.list.mockReturnValue([actionType]); actionTypeRegistry.has.mockReturnValue(true); @@ -330,7 +367,11 @@ describe('rule_form', () => { wrapper = mountWithIntl( { ruleTypeRegistry={ruleTypeRegistry} connectorFeatureId={featureId} onChangeMetaData={jest.fn()} + validConsumers={validConsumers} + setConsumer={mockSetConsumer} + useRuleProducer={useRuleProducer} /> ); @@ -359,20 +403,20 @@ describe('rule_form', () => { }); it('renders registered selected rule type', async () => { - await setup(true); + await setup({ showRulesList: true }); const ruleTypeSelectOptions = wrapper.find('[data-test-subj="my-rule-type-SelectOption"]'); expect(ruleTypeSelectOptions.exists()).toBeTruthy(); }); it('renders minimum schedule interval helper text when enforce = true', async () => { - await setup(false, true); + await setup({ enforceMinimum: true }); expect(wrapper.find('[data-test-subj="intervalFormRow"]').first().prop('helpText')).toEqual( `Interval must be at least 1 minute.` ); }); it('renders minimum schedule interval helper suggestion when enforce = false and schedule is less than configuration', async () => { - await setup(false, false, '10s'); + await setup({ schedule: '10s' }); expect(wrapper.find('[data-test-subj="intervalFormRow"]').first().prop('helpText')).toEqual( `Intervals less than 1 minute are not recommended due to performance considerations.` ); @@ -440,7 +484,7 @@ describe('rule_form', () => { }); it('renders uses feature id to load action types', async () => { - await setup(false, false, '1m', 'anotherFeature'); + await setup({ schedule: '1m', featureId: 'anotherFeature' }); const ruleTypeSelectOptions = wrapper.find( '[data-test-subj=".server-log-anotherFeature-ActionTypeSelectOption"]' ); @@ -448,7 +492,7 @@ describe('rule_form', () => { }); it('renders rule type description', async () => { - await setup(true); + await setup({ showRulesList: true }); wrapper.find('button[data-test-subj="my-rule-type-SelectOption"]').first().simulate('click'); const ruleDescription = wrapper.find('[data-test-subj="ruleDescription"]'); expect(ruleDescription.exists()).toBeTruthy(); @@ -456,7 +500,7 @@ describe('rule_form', () => { }); it('renders rule type documentation link', async () => { - await setup(true); + await setup({ showRulesList: true }); wrapper.find('button[data-test-subj="my-rule-type-SelectOption"]').first().simulate('click'); const ruleDocumentationLink = wrapper.find('[data-test-subj="ruleDocumentationLink"]'); expect(ruleDocumentationLink.exists()).toBeTruthy(); @@ -464,13 +508,164 @@ describe('rule_form', () => { }); it('renders rule types disabled by license', async () => { - await setup(true); + await setup({ showRulesList: true }); const actionOption = wrapper.find(`[data-test-subj="disabled-by-license-SelectOption"]`); expect(actionOption.exists()).toBeTruthy(); expect( wrapper.find('[data-test-subj="disabled-by-license-disabledTooltip"]').exists() ).toBeTruthy(); }); + + it('should select the only one available consumer', async () => { + await setup({ + initialRuleOverwrite: { + name: 'Simple rule', + consumer: 'alerts', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + schedule: { + interval: '1h', + }, + }, + ruleTypesOverwrite: [ + { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + name: 'Threshold Rule 1', + actionGroups: [ + { + id: 'testActionGroup', + name: 'Test Action Group', + }, + ], + enabledInLicense: true, + defaultActionGroupId: 'threshold.fired', + minimumLicenseRequired: 'basic', + recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + producer: ALERTS_FEATURE_ID, + authorizedConsumers: { + alerts: { read: true, all: true }, + apm: { read: true, all: true }, + discover: { read: true, all: true }, + infrastructure: { read: true, all: true }, + // Setting logs all to false, this shouldn't show up + logs: { read: true, all: false }, + ml: { read: true, all: true }, + monitoring: { read: true, all: true }, + siem: { read: true, all: true }, + slo: { read: true, all: false }, + stackAlerts: { read: true, all: true }, + uptime: { read: true, all: true }, + }, + actionVariables: { + context: [], + state: [], + params: [], + }, + }, + ], + ruleTypeModelOverwrite: { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + iconClass: 'test', + description: 'test', + documentationUrl: null, + validate: (): ValidationResult => { + return { errors: {} }; + }, + ruleParamsExpression: TestExpression, + requiresAppContext: false, + }, + validConsumers: [AlertConsumers.INFRASTRUCTURE, AlertConsumers.LOGS], + }); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('[data-test-subj="ruleFormConsumerSelect"]').exists()).toBeFalsy(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(mockSetConsumer).toHaveBeenLastCalledWith('infrastructure'); + }); + + it('should be able to select multiple consumer', async () => { + await setup({ + initialRuleOverwrite: { + name: 'Simple rule', + consumer: 'alerts', + ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + schedule: { + interval: '1h', + }, + }, + ruleTypesOverwrite: [ + { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + name: 'Threshold Rule', + actionGroups: [ + { + id: 'testActionGroup', + name: 'Test Action Group', + }, + ], + enabledInLicense: true, + defaultActionGroupId: 'threshold.fired', + minimumLicenseRequired: 'basic', + recoveryActionGroup: { id: 'recovered', name: 'Recovered' }, + producer: ALERTS_FEATURE_ID, + authorizedConsumers: { + infrastructure: { read: true, all: true }, + logs: { read: true, all: true }, + }, + actionVariables: { + context: [], + state: [], + params: [], + }, + }, + ], + ruleTypeModelOverwrite: { + id: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, + iconClass: 'test', + description: 'test', + documentationUrl: null, + validate: (): ValidationResult => { + return { errors: {} }; + }, + ruleParamsExpression: TestExpression, + requiresAppContext: false, + }, + }); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="ruleFormConsumerSelect"]').exists()).toBeTruthy(); + expect(wrapper.find(RuleFormConsumerSelection).props().consumers).toEqual([ + 'infrastructure', + 'logs', + ]); + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(mockSetConsumer).toHaveBeenLastCalledWith(null); + }); + + it('should not display the consumer select for invalid rule types', async () => { + await setup(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find('[data-test-subj="ruleFormConsumerSelect"]').exists()).toBeFalsy(); + }); }); describe('rule_form create rule non ruleing consumer and producer', () => { @@ -605,14 +800,6 @@ describe('rule_form', () => { ); expect(ruleTypeSelectOptions.exists()).toBeTruthy(); }); - - it('does not render rule type options which producer does not correspond to the rule consumer', async () => { - await setup(); - const ruleTypeSelectOptions = wrapper.find( - '[data-test-subj="other-consumer-producer-rule-type-SelectOption"]' - ); - expect(ruleTypeSelectOptions.exists()).toBeFalsy(); - }); }); describe('rule_form edit rule', () => { diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx index c72dced42a2d9..88e3639c2dbcf 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { Fragment, useState, useEffect, useCallback, Suspense } from 'react'; +import React, { Fragment, useState, useEffect, useCallback, Suspense, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { @@ -61,6 +61,7 @@ import { RuleTypeRegistryContract, ActionTypeRegistryContract, TriggersActionsUiConfig, + RuleCreationValidConsumer, } from '../../../types'; import { getTimeOptions } from '../../../common/lib/get_time_options'; import { ActionForm } from '../action_connector_form'; @@ -73,7 +74,9 @@ import { IsEnabledResult, IsDisabledResult } from '../../lib/check_rule_type_ena import { checkRuleTypeEnabled } from '../../lib/check_rule_type_enabled'; import { ruleTypeCompare, ruleTypeGroupCompare } from '../../lib/rule_type_compare'; import { VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants'; +import { MULTI_CONSUMER_RULE_TYPE_IDS } from '../../constants'; import { SectionLoading } from '../../components/section_loading'; +import { RuleFormConsumerSelection, VALID_CONSUMERS } from './rule_form_consumer_selection'; import { useLoadRuleTypes } from '../../hooks/use_load_rule_types'; import { getInitialInterval } from './get_initial_interval'; @@ -81,10 +84,44 @@ const ENTER_KEY = 13; const INTEGER_REGEX = /^[1-9][0-9]*$/; +const NOOP = () => {}; + function getProducerFeatureName(producer: string, kibanaFeatures: KibanaFeature[]) { return kibanaFeatures.find((featureItem) => featureItem.id === producer)?.name; } +const authorizedToDisplayRuleType = ({ + rule, + ruleType, + validConsumers, +}: { + rule: InitialRule; + ruleType: RuleType; + validConsumers?: RuleCreationValidConsumer[]; +}) => { + if (!ruleType) { + return false; + } + // If we have a generic threshold/ES query rule... + if (MULTI_CONSUMER_RULE_TYPE_IDS.includes(ruleType.id)) { + // And an array of valid consumers are passed in, we will show it + // if the rule type has at least one of the consumers as authorized + if (Array.isArray(validConsumers)) { + return validConsumers.some((consumer) => hasAllPrivilege(consumer, ruleType)); + } + // If no array was passed in, then we will show it if at least one of its + // authorized consumers allows it to be shown. + return Object.entries(ruleType.authorizedConsumers).some(([_, privilege]) => { + return privilege.all; + }); + } + // For non-generic threshold/ES query rules, we will still do the check + // against `alerts` since we are still setting rule consumers to `alerts` + return hasAllPrivilege(rule.consumer, ruleType); +}; + +export type RuleTypeItems = Array<{ ruleTypeModel: RuleTypeModel; ruleType: RuleType }>; + interface RuleFormProps> { rule: InitialRule; config: TriggersActionsUiConfig; @@ -94,23 +131,29 @@ interface RuleFormProps> { actionTypeRegistry: ActionTypeRegistryContract; operation: string; canChangeTrigger?: boolean; // to hide Change trigger button + canShowConsumerSelection?: boolean; setHasActionsDisabled?: (value: boolean) => void; setHasActionsWithBrokenConnector?: (value: boolean) => void; + setConsumer?: (consumer: RuleCreationValidConsumer | null) => void; metadata?: MetaData; filteredRuleTypes?: string[]; hideInterval?: boolean; connectorFeatureId?: string; + validConsumers?: RuleCreationValidConsumer[]; onChangeMetaData: (metadata: MetaData) => void; + useRuleProducer?: boolean; } export const RuleForm = ({ rule, config, canChangeTrigger = true, + canShowConsumerSelection = false, dispatch, errors, setHasActionsDisabled, setHasActionsWithBrokenConnector, + setConsumer = NOOP, operation, ruleTypeRegistry, actionTypeRegistry, @@ -118,7 +161,9 @@ export const RuleForm = ({ filteredRuleTypes: ruleTypeToFilter, hideInterval, connectorFeatureId = AlertingConnectorFeatureId, + validConsumers, onChangeMetaData, + useRuleProducer, }: RuleFormProps) => { const { notifications: { toasts }, @@ -150,12 +195,8 @@ export const RuleForm = ({ ); const [defaultActionGroupId, setDefaultActionGroupId] = useState(undefined); - const [availableRuleTypes, setAvailableRuleTypes] = useState< - Array<{ ruleTypeModel: RuleTypeModel; ruleType: RuleType }> - >([]); - const [filteredRuleTypes, setFilteredRuleTypes] = useState< - Array<{ ruleTypeModel: RuleTypeModel; ruleType: RuleType }> - >([]); + const [availableRuleTypes, setAvailableRuleTypes] = useState([]); + const [filteredRuleTypes, setFilteredRuleTypes] = useState([]); const [searchText, setSearchText] = useState(); const [inputText, setInputText] = useState(); const [solutions, setSolutions] = useState | undefined>(undefined); @@ -177,23 +218,23 @@ export const RuleForm = ({ const getAvailableRuleTypes = (ruleTypesResult: RuleType[]) => ruleTypeRegistry .list() - .reduce( - ( - arr: Array<{ ruleType: RuleType; ruleTypeModel: RuleTypeModel }>, - ruleTypeRegistryItem: RuleTypeModel - ) => { - const ruleType = ruleTypesResult.find((item) => ruleTypeRegistryItem.id === item.id); - if (ruleType) { - arr.push({ - ruleType, - ruleTypeModel: ruleTypeRegistryItem, - }); - } - return arr; - }, - [] + .reduce((arr: RuleTypeItems, ruleTypeRegistryItem: RuleTypeModel) => { + const ruleType = ruleTypesResult.find((item) => ruleTypeRegistryItem.id === item.id); + if (ruleType) { + arr.push({ + ruleType, + ruleTypeModel: ruleTypeRegistryItem, + }); + } + return arr; + }, []) + .filter(({ ruleType }) => + authorizedToDisplayRuleType({ + rule, + ruleType, + validConsumers, + }) ) - .filter((item) => item.ruleType && hasAllPrivilege(rule.consumer, item.ruleType)) .filter((item) => rule.consumer === ALERTS_FEATURE_ID ? !item.ruleTypeModel.requiresAppContext @@ -221,7 +262,16 @@ export const RuleForm = ({ setSolutions( new Map([...solutionsResult.entries()].sort(([, a], [, b]) => a.localeCompare(b))) ); - }, [ruleTypes, ruleTypeIndex, rule.ruleTypeId, kibanaFeatures, rule.consumer, ruleTypeRegistry]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + ruleTypes, + ruleTypeIndex, + rule.ruleTypeId, + kibanaFeatures, + rule.consumer, + ruleTypeRegistry, + validConsumers, + ]); useEffect(() => { if (loadRuleTypesError) { @@ -319,6 +369,44 @@ export const RuleForm = ({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [ruleTypeRegistry, availableRuleTypes, searchText, JSON.stringify(solutionsFilter)]); + const authorizedConsumers = useMemo(() => { + // If the app context provides a consumer, we assume that consumer is + // is what we set for all rules that is created in that context + if (rule.consumer !== ALERTS_FEATURE_ID) { + return []; + } + + const selectedRuleType = availableRuleTypes.find( + ({ ruleType: availableRuleType }) => availableRuleType.id === rule.ruleTypeId + ); + if (!selectedRuleType?.ruleType?.authorizedConsumers) { + return []; + } + return Object.entries(selectedRuleType.ruleType.authorizedConsumers).reduce< + RuleCreationValidConsumer[] + >((result, [authorizedConsumer, privilege]) => { + if ( + privilege.all && + (validConsumers || VALID_CONSUMERS).includes( + authorizedConsumer as RuleCreationValidConsumer + ) + ) { + result.push(authorizedConsumer as RuleCreationValidConsumer); + } + return result; + }, []); + }, [availableRuleTypes, rule, validConsumers]); + + const shouldShowConsumerSelect = useMemo(() => { + if (!canShowConsumerSelection) { + return false; + } + if (!authorizedConsumers.length) { + return false; + } + return !!rule.ruleTypeId && MULTI_CONSUMER_RULE_TYPE_IDS.includes(rule.ruleTypeId); + }, [authorizedConsumers, rule, canShowConsumerSelection]); + const selectedRuleType = rule?.ruleTypeId ? ruleTypeIndex?.get(rule?.ruleTypeId) : undefined; const recoveryActionGroup = selectedRuleType?.recoveryActionGroup?.id; @@ -436,6 +524,10 @@ export const RuleForm = ({ if (ruleTypeIndex && ruleTypeIndex.has(item.id)) { setDefaultActionGroupId(ruleTypeIndex.get(item.id)!.defaultActionGroupId); } + + if (useRuleProducer && !MULTI_CONSUMER_RULE_TYPE_IDS.includes(item.id)) { + setConsumer(solution as RuleCreationValidConsumer); + } }} /> ); @@ -639,6 +731,18 @@ export const RuleForm = ({ )} + {shouldShowConsumerSelect && ( + <> + + + + + + )} {canShowActions && defaultActionGroupId && diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.test.tsx new file mode 100644 index 0000000000000..ee7af0446c9ec --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { RuleFormConsumerSelection } from './rule_form_consumer_selection'; +import { RuleCreationValidConsumer } from '../../../types'; + +const mockConsumers: RuleCreationValidConsumer[] = ['logs', 'infrastructure', 'stackAlerts']; + +const mockOnChange = jest.fn(); + +describe('RuleFormConsumerSelectionModal', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders correctly', async () => { + render( + + ); + + expect(screen.getByTestId('ruleFormConsumerSelect')).toBeInTheDocument(); + fireEvent.click(screen.getByTestId('comboBoxToggleListButton')); + expect(screen.getByText('Logs')).toBeInTheDocument(); + expect(screen.getByText('Metrics')).toBeInTheDocument(); + expect(screen.getByText('Stack Rules')).toBeInTheDocument(); + }); + + it('should initialize dropdown to null', () => { + render( + + ); + + // Selects first option if no initial value is provided + expect(mockOnChange).toHaveBeenLastCalledWith(null); + mockOnChange.mockClear(); + }); + + it('should be able to select infrastructure and call onChange', () => { + render( + + ); + + fireEvent.click(screen.getByTestId('comboBoxToggleListButton')); + fireEvent.click(screen.getByTestId('infrastructure')); + expect(mockOnChange).toHaveBeenLastCalledWith('infrastructure'); + }); + + it('should be able to select logs and call onChange', () => { + render( + + ); + + fireEvent.click(screen.getByTestId('comboBoxToggleListButton')); + fireEvent.click(screen.getByTestId('logs')); + expect(mockOnChange).toHaveBeenLastCalledWith('logs'); + }); + + it('should be able to show errors when there is one', () => { + render( + + ); + expect(screen.queryAllByText('Scope is required')).toHaveLength(1); + }); + + it('should display nothing if there is only 1 consumer to select', () => { + render( + + ); + + expect(mockOnChange).toHaveBeenLastCalledWith('stackAlerts'); + expect(screen.queryByTestId('ruleFormConsumerSelect')).not.toBeInTheDocument(); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.tsx new file mode 100644 index 0000000000000..f5f92c72fa521 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rule_form/rule_form_consumer_selection.tsx @@ -0,0 +1,157 @@ +/* + * 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 React, { useMemo, useState, useCallback, useEffect } from 'react'; +import { EuiComboBox, EuiFormRow, EuiComboBoxOptionOption } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { AlertConsumers } from '@kbn/rule-data-utils'; +import { IErrorObject, RuleCreationValidConsumer } from '../../../types'; + +const SELECT_LABEL: string = i18n.translate( + 'xpack.triggersActionsUI.sections.ruleFormConsumerSelectionModal.selectLabel', + { + defaultMessage: 'Select role visibility', + } +); + +const featureNameMap: Record = { + [AlertConsumers.LOGS]: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleFormConsumerSelectionModal.logs', + { + defaultMessage: 'Logs', + } + ), + [AlertConsumers.INFRASTRUCTURE]: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleFormConsumerSelectionModal.infrastructure', + { + defaultMessage: 'Metrics', + } + ), + [AlertConsumers.APM]: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleFormConsumerSelectionModal.apm', + { + defaultMessage: 'APM and User Experience', + } + ), + [AlertConsumers.UPTIME]: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleFormConsumerSelectionModal.uptime', + { + defaultMessage: 'Synthetics and Uptime', + } + ), + [AlertConsumers.SLO]: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleFormConsumerSelectionModal.slo', + { + defaultMessage: 'SLOs', + } + ), + stackAlerts: i18n.translate( + 'xpack.triggersActionsUI.sections.ruleFormConsumerSelectionModal.stackAlerts', + { + defaultMessage: 'Stack Rules', + } + ), +}; + +export const VALID_CONSUMERS: RuleCreationValidConsumer[] = [ + AlertConsumers.LOGS, + AlertConsumers.INFRASTRUCTURE, + 'stackAlerts', +]; + +export interface RuleFormConsumerSelectionProps { + consumers: RuleCreationValidConsumer[]; + onChange: (consumer: RuleCreationValidConsumer | null) => void; + errors: IErrorObject; +} + +const SINGLE_SELECTION = { asPlainText: true }; + +export const RuleFormConsumerSelection = (props: RuleFormConsumerSelectionProps) => { + const { consumers, errors, onChange } = props; + const [selectedConsumer, setSelectedConsumer] = useState(); + const isInvalid = errors?.consumer?.length > 0; + const handleOnChange = useCallback( + (selected: Array>) => { + if (selected.length > 0) { + const newSelectedConsumer = selected[0]; + setSelectedConsumer(newSelectedConsumer.value); + onChange(newSelectedConsumer.value!); + } else { + setSelectedConsumer(undefined); + onChange(null); + } + }, + [onChange] + ); + const selectedOptions = useMemo( + () => + selectedConsumer + ? [{ value: selectedConsumer, label: featureNameMap[selectedConsumer] }] + : [], + [selectedConsumer] + ); + + const formattedSelectOptions: Array> = + useMemo(() => { + return consumers + .reduce>>((result, consumer) => { + if (featureNameMap[consumer]) { + result.push({ + value: consumer, + 'data-test-subj': consumer, + label: featureNameMap[consumer], + }); + } + return result; + }, []) + .sort((a, b) => { + return a.value!.localeCompare(b.value!); + }); + }, [consumers]); + + useEffect(() => { + // At initialization we set to NULL to know that nobody selected anything + onChange(null); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (formattedSelectOptions.length === 1) { + onChange(formattedSelectOptions[0].value as RuleCreationValidConsumer); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [formattedSelectOptions]); + + if (formattedSelectOptions.length <= 1) { + return null; + } + return ( + + + + ); +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx index 197077daceaf3..83285163aeee3 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/rules_list/components/rules_list.tsx @@ -107,6 +107,7 @@ const RuleAdd = lazy(() => import('../../rule_form/rule_add')); const RuleEdit = lazy(() => import('../../rule_form/rule_edit')); export interface RulesListProps { + filterConsumers?: string[]; filteredRuleTypes?: string[]; lastResponseFilter?: string[]; lastRunOutcomeFilter?: string[]; @@ -146,6 +147,7 @@ const initialPercentileOptions = Object.values(Percentiles).map((percentile) => const EMPTY_ARRAY: string[] = []; export const RulesList = ({ + filterConsumers, filteredRuleTypes = EMPTY_ARRAY, lastResponseFilter, lastRunOutcomeFilter, @@ -263,6 +265,7 @@ export const RulesList = ({ // Fetch rules const { rulesState, loadRules, hasData, lastUpdate } = useLoadRulesQuery({ + filterConsumers, filters: computedFilter, hasDefaultRuleTypesFiltersOn, page, @@ -275,6 +278,7 @@ export const RulesList = ({ // Fetch status aggregation const { loadRuleAggregations, rulesStatusesTotal, rulesLastRunOutcomesTotal } = useLoadRuleAggregationsQuery({ + filterConsumers, filters: computedFilter, enabled: canLoadRules, refresh, diff --git a/x-pack/plugins/triggers_actions_ui/public/index.ts b/x-pack/plugins/triggers_actions_ui/public/index.ts index a13df899ec3cf..6d3c3bbee0d5f 100644 --- a/x-pack/plugins/triggers_actions_ui/public/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/index.ts @@ -43,6 +43,7 @@ export type { RulesListVisibleColumns, AlertSummaryTimeRange, NotifyWhenSelectOptions, + RuleCreationValidConsumer, } from './types'; export type { diff --git a/x-pack/plugins/triggers_actions_ui/public/types.ts b/x-pack/plugins/triggers_actions_ui/public/types.ts index 7c6454c8cc3e4..7716aef340406 100644 --- a/x-pack/plugins/triggers_actions_ui/public/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/types.ts @@ -26,6 +26,7 @@ import type { EuiSuperSelectOption, EuiDataGridOnColumnResizeHandler, } from '@elastic/eui'; +import type { AlertConsumers, STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils'; import { EuiDataGridColumn, EuiDataGridControlColumn, EuiDataGridSorting } from '@elastic/eui'; import { HttpSetup } from '@kbn/core/public'; import { KueryNode } from '@kbn/es-query'; @@ -457,6 +458,8 @@ export interface RuleAddProps> { metadata?: MetaData; ruleTypeIndex?: RuleTypeIndex; filteredRuleTypes?: string[]; + validConsumers?: RuleCreationValidConsumer[]; + useRuleProducer?: boolean; } export interface RuleDefinitionProps { rule: Rule; @@ -819,3 +822,8 @@ export interface NotifyWhenSelectOptions { isForEachAlertOption?: boolean; value: EuiSuperSelectOption; } + +export type RuleCreationValidConsumer = + | typeof AlertConsumers.LOGS + | typeof AlertConsumers.INFRASTRUCTURE + | typeof STACK_ALERTS_FEATURE_ID; diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index 5b4aeb496b125..3307211694cc0 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -469,20 +469,8 @@ export class AlertUtils { } } -export function getConsumerUnauthorizedErrorMessage( - operation: string, - alertType: string, - consumer: string -) { - return `Unauthorized to ${operation} a "${alertType}" rule for "${consumer}"`; -} - -export function getProducerUnauthorizedErrorMessage( - operation: string, - alertType: string, - producer: string -) { - return `Unauthorized to ${operation} a "${alertType}" rule by "${producer}"`; +export function getUnauthorizedErrorMessage(operation: string, alertType: string, scope: string) { + return `Unauthorized by "${scope}" to ${operation} "${alertType}" rule`; } function getDefaultAlwaysFiringAlertData( diff --git a/x-pack/test/alerting_api_integration/common/lib/index.ts b/x-pack/test/alerting_api_integration/common/lib/index.ts index 2c0651f3f962a..cc7ba4fcb083f 100644 --- a/x-pack/test/alerting_api_integration/common/lib/index.ts +++ b/x-pack/test/alerting_api_integration/common/lib/index.ts @@ -8,11 +8,7 @@ export { ObjectRemover } from './object_remover'; export { getUrlPrefix } from './space_test_utils'; export { getTestRuleData } from './get_test_rule_data'; -export { - AlertUtils, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, -} from './alert_utils'; +export { AlertUtils, getUnauthorizedErrorMessage } from './alert_utils'; export type { TaskManagerDoc } from './task_manager_utils'; export { TaskManagerUtils } from './task_manager_utils'; export * from './test_assertions'; diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts index 8df0f20e88de7..fd5a4054f0895 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_fired.ts @@ -12,7 +12,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; import { createDataView, deleteDataView } from '../helpers/data_view'; import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; @@ -52,7 +52,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await deleteDataView({ supertest, @@ -73,7 +73,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await createRule({ supertest, tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -142,7 +142,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts index d57398def6381..4d2475d6b0457 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -11,7 +11,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; import { createDataView, deleteDataView } from '../helpers/data_view'; @@ -48,7 +48,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await deleteDataView({ supertest, @@ -67,7 +67,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await createRule({ supertest, tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -136,7 +136,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts index b4570771b9f2f..4359385e79534 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/avg_us_fired.ts @@ -14,7 +14,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../common/ftr_provider_context'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; import { createDataView, deleteDataView } from '../helpers/data_view'; @@ -68,7 +68,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await synthtraceEsClient.clean(); await deleteDataView({ @@ -88,7 +88,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await createRule({ supertest, tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -162,7 +162,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index 7602c9454a55e..7f7e66d050593 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -18,7 +18,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; import { createDataView, deleteDataView } from '../helpers/data_view'; import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; @@ -58,7 +58,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await deleteDataView({ supertest, @@ -79,7 +79,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await createRule({ supertest, tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -150,7 +150,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts index 99f313960fa6b..6a17340094e80 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/documents_count_fired.ts @@ -12,7 +12,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; import { createDataView, deleteDataView } from '../helpers/data_view'; import { waitForAlertInIndex, waitForRuleStatus } from '../helpers/alerting_wait_for_helpers'; @@ -52,7 +52,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await deleteDataView({ supertest, @@ -73,7 +73,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await createRule({ supertest, tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -140,7 +140,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts index 5925907b471b6..da18b429c45c0 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule/group_by_fired.ts @@ -19,7 +19,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { createIndexConnector, createRule } from '../helpers/alerting_api_helper'; import { createDataView, deleteDataView } from '../helpers/data_view'; import { @@ -65,7 +65,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await deleteDataView({ supertest, @@ -86,7 +86,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await createRule({ supertest, tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -162,7 +162,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts index 3119a0ae46e63..ada064c133ffc 100644 --- a/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts +++ b/x-pack/test/alerting_api_integration/observability/custom_threshold_rule_data_view.ts @@ -10,7 +10,7 @@ import { Aggregators, Comparator, } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../common/ftr_provider_context'; import { getUrlPrefix, ObjectRemover } from '../common/lib'; @@ -67,7 +67,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await createRule({ supertest, tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts index 5f2b892a416a3..b7cf41d07e823 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/create.ts @@ -10,10 +10,9 @@ import { UserAtSpaceScenarios } from '../../../scenarios'; import { checkAAD, getTestRuleData, - getConsumerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, getUrlPrefix, ObjectRemover, - getProducerUnauthorizedErrorMessage, TaskManagerDoc, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -75,11 +74,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'create', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('create', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -181,7 +176,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -218,7 +213,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.unrestricted-noop', 'alertsFixture' @@ -228,17 +223,6 @@ export default function createAlertTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'create', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); @@ -267,7 +251,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage('create', 'test.noop', 'alerts'), + message: getUnauthorizedErrorMessage('create', 'test.noop', 'alerts'), statusCode: 403, }); break; @@ -275,11 +259,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'create', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('create', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -318,7 +298,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.noop', 'some consumer patrick invented' @@ -345,11 +325,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'create', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('create', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -384,11 +360,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'create', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('create', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -481,11 +453,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'create', - 'test.validation', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('create', 'test.validation', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/delete.ts index 2b6086cf38d92..d6cb57261a558 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/delete.ts @@ -10,8 +10,7 @@ import { UserAtSpaceScenarios } from '../../../scenarios'; import { getUrlPrefix, getTestRuleData, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, ObjectRemover, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -57,11 +56,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'delete', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('delete', 'test.noop', 'alertsFixture'), statusCode: 403, }); objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); @@ -112,7 +107,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'delete', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -163,7 +158,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'delete', 'test.unrestricted-noop', 'alertsFixture' @@ -176,20 +171,6 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'delete', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); - // Ensure task still exists - await getScheduledTask(createdAlert.scheduled_task_id); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -229,7 +210,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage('delete', 'test.noop', 'alerts'), + message: getUnauthorizedErrorMessage('delete', 'test.noop', 'alerts'), statusCode: 403, }); objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); @@ -238,11 +219,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'delete', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('delete', 'test.noop', 'alertsFixture'), statusCode: 403, }); objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); @@ -333,11 +310,7 @@ export default function createDeleteTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'delete', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('delete', 'test.noop', 'alertsFixture'), statusCode: 403, }); objectRemover.add(space.id, createdAlert.id, 'rule', 'alerting'); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts index df9fc34e17014..8539b1a7b87c3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/disable.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, TaskManagerDoc, } from '../../../../common/lib'; @@ -83,11 +82,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'disable', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('disable', 'test.noop', 'alertsFixture'), statusCode: 403, }); // Ensure task still exists and is still enabled @@ -158,7 +153,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'disable', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -210,7 +205,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'disable', 'test.unrestricted-noop', 'alertsFixture' @@ -220,17 +215,6 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'disable', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -273,7 +257,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage('disable', 'test.noop', 'alerts'), + message: getUnauthorizedErrorMessage('disable', 'test.noop', 'alerts'), statusCode: 403, }); break; @@ -281,11 +265,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'disable', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('disable', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -344,11 +324,7 @@ export default function createDisableAlertTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'disable', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('disable', 'test.noop', 'alertsFixture'), statusCode: 403, }); // Ensure task still exists and is still enabled diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/enable.ts index 73842073a542b..8c6e3ecd5ea5a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/enable.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, TaskManagerDoc, } from '../../../../common/lib'; @@ -82,11 +81,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'enable', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('enable', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -94,11 +89,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'enable', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('enable', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -168,7 +159,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'enable', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -215,7 +206,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'enable', 'test.unrestricted-noop', 'alertsFixture' @@ -225,17 +216,6 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'enable', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -268,7 +248,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage('enable', 'test.noop', 'alerts'), + message: getUnauthorizedErrorMessage('enable', 'test.noop', 'alerts'), statusCode: 403, }); break; @@ -276,11 +256,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'enable', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('enable', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -333,11 +309,7 @@ export default function createEnableAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'enable', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('enable', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts index d3117ed4ce209..edacff0fbfd13 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get.ts @@ -12,8 +12,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -50,7 +49,7 @@ const getTestUtils = ( expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage('get', 'test.noop', 'alertsFixture'), + message: getUnauthorizedErrorMessage('get', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -134,7 +133,7 @@ const getTestUtils = ( expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'get', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -179,7 +178,7 @@ const getTestUtils = ( expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'get', 'test.unrestricted-noop', 'alertsFixture' @@ -189,17 +188,6 @@ const getTestUtils = ( break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'get', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'global_read at space1': case 'space_1_all_with_restricted_fixture at space1': @@ -237,11 +225,7 @@ const getTestUtils = ( expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'get', - 'test.restricted-noop', - 'alerts' - ), + message: getUnauthorizedErrorMessage('get', 'test.restricted-noop', 'alerts'), statusCode: 403, }); break; @@ -250,7 +234,7 @@ const getTestUtils = ( expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'get', 'test.restricted-noop', 'alertsRestrictedFixture' diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_state.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_state.ts index e3da329c1cbaf..65fe412e177d3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_state.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_state.ts @@ -10,8 +10,7 @@ import { getUrlPrefix, ObjectRemover, getTestRuleData, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { UserAtSpaceScenarios } from '../../../scenarios'; @@ -47,7 +46,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage('get', 'test.noop', 'alertsFixture'), + message: getUnauthorizedErrorMessage('get', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -87,7 +86,7 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'get', 'test.unrestricted-noop', 'alertsFixture' @@ -97,17 +96,6 @@ export default function createGetAlertStateTests({ getService }: FtrProviderCont break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'get', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'global_read at space1': case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_summary.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_summary.ts index 7ad1a54de2485..9ba49b93e0b39 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_summary.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group1/tests/alerting/get_alert_summary.ts @@ -11,8 +11,7 @@ import { getUrlPrefix, ObjectRemover, getTestRuleData, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; import { UserAtSpaceScenarios } from '../../../scenarios'; @@ -50,7 +49,7 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage('get', 'test.noop', 'alertsFixture'), + message: getUnauthorizedErrorMessage('get', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -119,7 +118,7 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'get', 'test.unrestricted-noop', 'alertsFixture' @@ -129,17 +128,6 @@ export default function createGetAlertSummaryTests({ getService }: FtrProviderCo break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'get', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'global_read at space1': case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts index de1bb08f62772..90ddf77f888c3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/alerts.ts @@ -20,7 +20,7 @@ import { getTestRuleData, ObjectRemover, AlertUtils, - getConsumerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, TaskManagerUtils, getEventLog, } from '../../../../common/lib'; @@ -130,7 +130,7 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -254,7 +254,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -500,7 +500,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -601,7 +601,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.authorization', 'alertsFixture' @@ -719,7 +719,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -815,7 +815,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -873,7 +873,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -953,7 +953,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -1018,7 +1018,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -1074,7 +1074,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -1133,7 +1133,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -1192,7 +1192,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing', 'alertsFixture' @@ -1256,7 +1256,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing-alert-as-data', 'alertsFixture' @@ -1318,7 +1318,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing-alert-as-data', 'alertsFixture' @@ -1392,7 +1392,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing-alert-as-data', 'alertsFixture' @@ -1447,7 +1447,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing-alert-as-data', 'alertsFixture' @@ -1676,7 +1676,7 @@ instanceStateValue: true expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.always-firing-alert-as-data', 'alertsFixture' diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts index ca3570a511d17..bba117642e3c3 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_all.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export @@ -72,11 +71,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'muteAll', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('muteAll', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -137,7 +132,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'muteAll', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -191,7 +186,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'muteAll', 'test.unrestricted-noop', 'alertsFixture' @@ -201,17 +196,6 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'muteAll', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -257,11 +241,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'muteAll', - 'test.restricted-noop', - 'alerts' - ), + message: getUnauthorizedErrorMessage('muteAll', 'test.restricted-noop', 'alerts'), statusCode: 403, }); break; @@ -271,7 +251,7 @@ export default function createMuteAlertTests({ getService }: FtrProviderContext) expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'muteAll', 'test.restricted-noop', 'alertsRestrictedFixture' diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_instance.ts index 63a285e0f4cb8..3a308f47b4812 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/mute_instance.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export @@ -72,11 +71,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'muteAlert', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('muteAlert', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -137,7 +132,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'muteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -191,7 +186,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'muteAlert', 'test.unrestricted-noop', 'alertsFixture' @@ -201,17 +196,6 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'muteAlert', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -257,11 +241,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'muteAlert', - 'test.restricted-noop', - 'alerts' - ), + message: getUnauthorizedErrorMessage('muteAlert', 'test.restricted-noop', 'alerts'), statusCode: 403, }); break; @@ -271,7 +251,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'muteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -324,11 +304,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'muteAlert', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('muteAlert', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts index ce30fbc283034..8cca92320cfa7 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/rbac_legacy.ts @@ -150,7 +150,7 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(failedUpdateKeyDueToAlertsPrivilegesResponse.body).to.eql({ error: 'Forbidden', message: - 'Unauthorized to updateApiKey a "test.always-firing" rule for "alertsFixture"', + 'Unauthorized by "alertsFixture" to updateApiKey "test.always-firing" rule', statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts index 2376e05635e9c..8073ef48fbefe 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/snooze.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; const NOW = new Date().toISOString(); @@ -84,11 +83,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'snooze', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('snooze', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -155,7 +150,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'snooze', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -215,7 +210,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'snooze', 'test.unrestricted-noop', 'alertsFixture' @@ -225,17 +220,6 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'snooze', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -287,11 +271,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'snooze', - 'test.restricted-noop', - 'alerts' - ), + message: getUnauthorizedErrorMessage('snooze', 'test.restricted-noop', 'alerts'), statusCode: 403, }); break; @@ -301,7 +281,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'snooze', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -380,11 +360,7 @@ export default function createSnoozeRuleTests({ getService }: FtrProviderContext expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'snooze', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('snooze', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts index b4576650c58c8..9623462b8ae45 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_all.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export @@ -77,11 +76,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'unmuteAll', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('unmuteAll', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -147,7 +142,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unmuteAll', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -206,7 +201,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unmuteAll', 'test.unrestricted-noop', 'alertsFixture' @@ -216,17 +211,6 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'unmuteAll', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -277,11 +261,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'unmuteAll', - 'test.restricted-noop', - 'alerts' - ), + message: getUnauthorizedErrorMessage('unmuteAll', 'test.restricted-noop', 'alerts'), statusCode: 403, }); break; @@ -291,7 +271,7 @@ export default function createUnmuteAlertTests({ getService }: FtrProviderContex expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unmuteAll', 'test.restricted-noop', 'alertsRestrictedFixture' diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_instance.ts index 1aa84f64a7e79..3d9f49eb3cffb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unmute_instance.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export @@ -77,11 +76,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'unmuteAlert', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('unmuteAlert', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -147,7 +142,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unmuteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -206,7 +201,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unmuteAlert', 'test.unrestricted-noop', 'alertsFixture' @@ -216,17 +211,6 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'unmuteAlert', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -277,7 +261,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unmuteAlert', 'test.restricted-noop', 'alerts' @@ -291,7 +275,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unmuteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unsnooze.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unsnooze.ts index 39b5b720ccda3..2f697b0ee3aff 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unsnooze.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/unsnooze.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export @@ -71,11 +70,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'unsnooze', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('unsnooze', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -137,7 +132,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unsnooze', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -192,7 +187,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unsnooze', 'test.unrestricted-noop', 'alertsFixture' @@ -202,17 +197,6 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'unsnooze', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -259,11 +243,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'unsnooze', - 'test.restricted-noop', - 'alerts' - ), + message: getUnauthorizedErrorMessage('unsnooze', 'test.restricted-noop', 'alerts'), statusCode: 403, }); break; @@ -273,7 +253,7 @@ export default function createUnsnoozeRuleTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'unsnooze', 'test.restricted-noop', 'alertsRestrictedFixture' diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts index b6972730cc780..31250862265ea 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update.ts @@ -14,8 +14,7 @@ import { getTestRuleData, ObjectRemover, ensureDatetimeIsWithinRange, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -89,11 +88,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('update', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -199,7 +194,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'update', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -289,7 +284,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'update', 'test.unrestricted-noop', 'alertsFixture' @@ -299,17 +294,6 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'update', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(200); @@ -391,11 +375,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.restricted-noop', - 'alerts' - ), + message: getUnauthorizedErrorMessage('update', 'test.restricted-noop', 'alerts'), statusCode: 403, }); break; @@ -405,7 +385,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'update', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -504,11 +484,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('update', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -591,11 +567,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('update', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -763,11 +735,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.validation', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('update', 'test.validation', 'alertsFixture'), statusCode: 403, }); break; @@ -864,11 +832,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('update', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -941,11 +905,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.longRunning', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('update', 'test.longRunning', 'alertsFixture'), statusCode: 403, }); break; @@ -1009,11 +969,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'update', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('update', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update_api_key.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update_api_key.ts index 6a594316796d4..7e2f4e74aa023 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update_api_key.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group2/tests/alerting/update_api_key.ts @@ -14,8 +14,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; // eslint-disable-next-line import/no-default-export @@ -71,11 +70,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'updateApiKey', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('updateApiKey', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; @@ -136,7 +131,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'updateApiKey', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -190,7 +185,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'updateApiKey', 'test.unrestricted-noop', 'alertsFixture' @@ -200,17 +195,6 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.statusCode).to.eql(403); - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'updateApiKey', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.statusCode).to.eql(204); @@ -256,7 +240,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'updateApiKey', 'test.restricted-noop', 'alerts' @@ -270,7 +254,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'updateApiKey', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -334,11 +318,7 @@ export default function createUpdateApiKeyTests({ getService }: FtrProviderConte expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'updateApiKey', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('updateApiKey', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts index 4febd9b866f15..ef50420bb6561 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_delete.ts @@ -8,7 +8,12 @@ import expect from '@kbn/expect'; import { UserAtSpaceScenarios, SuperuserAtSpace1 } from '../../../scenarios'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { + getUrlPrefix, + getTestRuleData, + ObjectRemover, + getUnauthorizedErrorMessage, +} from '../../../../common/lib'; const getDefaultRules = (response: any) => ({ id: response.body.rules[0].id, @@ -130,7 +135,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDelete', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -195,8 +200,11 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: - 'Unauthorized to bulkDelete a "test.restricted-noop" rule for "alertsRestrictedFixture"', + message: getUnauthorizedErrorMessage( + 'bulkDelete', + 'test.restricted-noop', + 'alertsRestrictedFixture' + ), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -342,7 +350,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDelete a "test.noop" rule by "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDelete', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -409,7 +417,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDelete', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -481,7 +489,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDelete', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -550,7 +558,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDelete a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDelete', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts index 13a89de8381ca..8ea0b913620aa 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_disable.ts @@ -8,7 +8,12 @@ import expect from '@kbn/expect'; import { UserAtSpaceScenarios, SuperuserAtSpace1 } from '../../../scenarios'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { + getUrlPrefix, + getTestRuleData, + ObjectRemover, + getUnauthorizedErrorMessage, +} from '../../../../common/lib'; const getDefaultRules = (response: any) => ({ id: response.body.rules[0].id, @@ -95,7 +100,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDisable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDisable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -112,7 +117,7 @@ export default ({ getService }: FtrProviderContext) => { } }); - it('should handle bulk disable of one rule appropriately based on id when consumer is the same as producer', async () => { + it('should handle bulk disable of one rule appropriately based on id when consumer is the same as producer', async () => { const { body: createdRule } = await supertest .post(`${getUrlPrefix(space.id)}/api/alerting/rule`) .set('kbn-xsrf', 'foo') @@ -144,8 +149,11 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: - 'Unauthorized to bulkDisable a "test.restricted-noop" rule for "alertsRestrictedFixture"', + message: getUnauthorizedErrorMessage( + 'bulkDisable', + 'test.restricted-noop', + 'alertsRestrictedFixture' + ), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -269,7 +277,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDisable a "test.noop" rule by "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDisable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -328,7 +336,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDisable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDisable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -378,7 +386,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDisable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDisable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -414,7 +422,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkDisable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkDisable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts index 2f3f36b9dc34b..c948791e5ea49 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_edit.ts @@ -13,8 +13,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, - getProducerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -85,7 +84,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkEdit a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkEdit', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -178,7 +177,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkEdit a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkEdit', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -249,7 +248,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'bulkEdit', 'test.restricted-noop', 'alertsRestrictedFixture' @@ -310,7 +309,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'bulkEdit', 'test.unrestricted-noop', 'alertsFixture' @@ -321,17 +320,6 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { break; case 'space_1_all at space1': case 'space_1_all_alerts_none_actions at space1': - expect(response.body).to.eql({ - error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( - 'bulkEdit', - 'test.unrestricted-noop', - 'alertsRestrictedFixture' - ), - statusCode: 403, - }); - expect(response.statusCode).to.eql(403); - break; case 'superuser at space1': case 'space_1_all_with_restricted_fixture at space1': expect(response.body.rules[0].tags).to.eql(['foo', 'tag-A', 'tag-B']); @@ -390,7 +378,7 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: getProducerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'bulkEdit', 'test.restricted-noop', 'alertsRestrictedFixture' diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts index 3f1239558c5eb..0366eca2ad24d 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/bulk_enable.ts @@ -8,7 +8,12 @@ import expect from '@kbn/expect'; import { UserAtSpaceScenarios, SuperuserAtSpace1 } from '../../../scenarios'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import { getUrlPrefix, getTestRuleData, ObjectRemover } from '../../../../common/lib'; +import { + getUrlPrefix, + getTestRuleData, + ObjectRemover, + getUnauthorizedErrorMessage, +} from '../../../../common/lib'; const defaultSuccessfulResponse = { total: 1, rules: [], errors: [], taskIdsFailedToBeEnabled: [] }; @@ -61,7 +66,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkEnable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkEnable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -147,8 +152,11 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: - 'Unauthorized to bulkEnable a "test.restricted-noop" rule for "alertsRestrictedFixture"', + message: getUnauthorizedErrorMessage( + 'bulkEnable', + 'test.restricted-noop', + 'alertsRestrictedFixture' + ), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -213,7 +221,6 @@ export default ({ getService }: FtrProviderContext) => { expect(response.statusCode).to.eql(400); break; case 'superuser at space1': - expect(response.body).to.eql(defaultSuccessfulResponse); expect(response.statusCode).to.eql(200); break; default: @@ -253,7 +260,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkEnable a "test.noop" rule by "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkEnable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -303,7 +310,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkEnable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkEnable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -353,7 +360,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkEnable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkEnable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); @@ -393,7 +400,7 @@ export default ({ getService }: FtrProviderContext) => { case 'global_read at space1': expect(response.body).to.eql({ error: 'Forbidden', - message: 'Unauthorized to bulkEnable a "test.noop" rule for "alertsFixture"', + message: getUnauthorizedErrorMessage('bulkEnable', 'test.noop', 'alertsFixture'), statusCode: 403, }); expect(response.statusCode).to.eql(403); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts index 46aef808c0877..2cbec3853f02e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/clone.ts @@ -10,7 +10,7 @@ import { Spaces, UserAtSpaceScenarios } from '../../../scenarios'; import { checkAAD, getTestRuleData, - getConsumerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, getUrlPrefix, ObjectRemover, TaskManagerDoc, @@ -121,11 +121,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'create', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('create', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/run_soon.ts b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/run_soon.ts index 831edffbae51a..461d603734d10 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/run_soon.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/group3/tests/alerting/run_soon.ts @@ -9,7 +9,7 @@ import expect from '@kbn/expect'; import { UserAtSpaceScenarios } from '../../../scenarios'; import { getTestRuleData, - getConsumerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, getUrlPrefix, ObjectRemover, } from '../../../../common/lib'; @@ -50,11 +50,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( - 'runSoon', - 'test.noop', - 'alertsFixture' - ), + message: getUnauthorizedErrorMessage('runSoon', 'test.noop', 'alertsFixture'), statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts index f3e7d8891cde3..eb9f90cb41f2a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/create.ts @@ -15,7 +15,7 @@ import { getUrlPrefix, getTestRuleData, ObjectRemover, - getConsumerUnauthorizedErrorMessage, + getUnauthorizedErrorMessage, TaskManagerDoc, } from '../../../../common/lib'; import { FtrProviderContext } from '../../../../common/ftr_provider_context'; @@ -444,7 +444,7 @@ export default function createAlertTests({ getService }: FtrProviderContext) { expect(response.status).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: getConsumerUnauthorizedErrorMessage( + message: getUnauthorizedErrorMessage( 'create', 'test.noop', 'some consumer patrick invented' diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/rule_types.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/rule_types.ts index ae2e2d61267c4..8f9b7566b7640 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/rule_types.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/group1/rule_types.ts @@ -137,6 +137,7 @@ export default function listRuleTypes({ getService }: FtrProviderContext) { hasFieldsForAAD: false, hasAlertsMappings: false, ruleTaskTimeout: '5m', + validLegacyConsumers: ['alerts'], }); expect(Object.keys(authorizedConsumers)).to.contain('alertsFixture'); }); diff --git a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts index 4883f01b5ac6e..d67cd7051860c 100644 --- a/x-pack/test/api_integration/apis/maps/maps_telemetry.ts +++ b/x-pack/test/api_integration/apis/maps/maps_telemetry.ts @@ -36,8 +36,8 @@ export default function ({ getService }: FtrProviderContext) { return fieldStat.name === 'geo_point'; } ); - expect(geoPointFieldStats.count).to.be(31); - expect(geoPointFieldStats.index_count).to.be(9); + expect(geoPointFieldStats.count).to.be(39); + expect(geoPointFieldStats.index_count).to.be(10); const geoShapeFieldStats = apiResponse.cluster_stats.indices.mappings.field_types.find( (fieldStat: estypes.ClusterStatsFieldTypes) => { diff --git a/x-pack/test/functional/es_archives/rule_registry/o11y_alerts/data.json b/x-pack/test/functional/es_archives/rule_registry/o11y_alerts/data.json new file mode 100644 index 0000000000000..c3a19cfbcf81a --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_registry/o11y_alerts/data.json @@ -0,0 +1,104 @@ +{ + "type": "doc", + "value": { + "index": ".alerts-observability.apm.alerts-default-000001", + "id": "NoxgpHkBqbdrfX07MqXV", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "apm.error_rate", + "message": "hello world 1", + "kibana.alert.rule.consumer": "apm", + "kibana.alert.workflow_status": "open", + "kibana.alert.time_range": { + "gte": "2020-12-16T15:16:18.570Z" + }, + "kibana.alert.status": "active", + "kibana.space_ids": ["space1", "space2"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".alerts-observability.apm.alerts-default-000001", + "id": "space1alert", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "apm.error_rate", + "message": "hello world 1", + "kibana.alert.rule.consumer": "apm", + "kibana.alert.workflow_status": "recovered", + "kibana.alert.time_range": { + "gte": "2020-12-16T15:16:18.570Z", + "lte": "2020-12-16T15:16:19.570Z" + }, + "kibana.alert.status": "recovered", + "kibana.space_ids": ["space1"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".alerts-observability.apm.alerts-default-000001", + "id": "space2alert", + "source": { + "event.kind" : "signal", + "@timestamp": "2020-12-16T15:16:19.570Z", + "kibana.alert.rule.rule_type_id": "apm.error_rate", + "message": "hello world 1", + "kibana.alert.rule.consumer": "apm", + "kibana.alert.workflow_status": "open", + "kibana.alert.status": "active", + "kibana.space_ids": ["space2"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".alerts-observability.logs.alerts-default-000001", + "id": "123456789XYZ", + "source": { + "event.kind": "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "logs.alert.document.count", + "message": "hello world 1", + "kibana.alert.rule.consumer": "logs", + "kibana.alert.workflow_status": "open", + "kibana.alert.time_range": { + "gte": "2020-12-16T15:16:18.570Z" + }, + "kibana.alert.status": "active", + "kibana.space_ids": ["space1", "space2"] + } + } +} + +{ + "type": "doc", + "value": { + "index": ".alerts-observability.logs.alerts-default-000001", + "id": "space1alertLogs", + "source": { + "event.kind": "signal", + "@timestamp": "2020-12-16T15:16:18.570Z", + "kibana.alert.rule.rule_type_id": "logs.alert.document.count", + "message": "hello world 1", + "kibana.alert.rule.consumer": "logs", + "kibana.alert.workflow_status": "recovered", + "kibana.alert.time_range": { + "gte": "2020-12-16T15:16:18.570Z", + "lte": "2020-12-16T15:27:19.570Z" + }, + "kibana.alert.end": "2020-12-16T15:27:19.570Z", + "kibana.alert.status": "recovered", + "kibana.space_ids": ["space1"] + } + } +} diff --git a/x-pack/test/functional/es_archives/rule_registry/o11y_alerts/mappings.json b/x-pack/test/functional/es_archives/rule_registry/o11y_alerts/mappings.json new file mode 100644 index 0000000000000..0faf5daf3df76 --- /dev/null +++ b/x-pack/test/functional/es_archives/rule_registry/o11y_alerts/mappings.json @@ -0,0 +1,66 @@ +{ + "type": "index", + "value": { + "index": ".alerts-observability.apm.alerts-default-000001", + "mappings": { + "properties": { + "message": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "kibana.alert.rule.consumer": { + "type": "keyword", + "ignore_above": 256 + }, + "kibana.alert.time_range": { + "type": "date_range", + "format": "epoch_millis||strict_date_optional_time" + } + } + } + } +} + + +{ + "type": "index", + "value": { + "index": ".alerts-observability.logs.alerts-default-000001", + "aliases": { + ".alerts-observability.logs.alerts-default": {} + }, + "mappings": { + "properties": { + "message": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "kibana.alert.rule.consumer": { + "type": "keyword", + "ignore_above": 256 + }, + "kibana.alert.status": { + "type": "keyword", + "ignore_above": 256 + }, + "kibana.alert.time_range": { + "type": "date_range", + "format": "epoch_millis||strict_date_optional_time" + }, + "kibana.alert.end": { + "type": "date" + } + } + } + } +} diff --git a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts index 7f8711fba0ab1..e912a52d36a18 100644 --- a/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts +++ b/x-pack/test/observability_functional/apps/observability/pages/rules_page.ts @@ -8,13 +8,16 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../../ftr_provider_context'; -export default ({ getService }: FtrProviderContext) => { +export default ({ getService, getPageObjects }: FtrProviderContext) => { const esArchiver = getService('esArchiver'); const testSubjects = getService('testSubjects'); const supertest = getService('supertest'); const find = getService('find'); const retry = getService('retry'); const RULE_ENDPOINT = '/api/alerting/rule'; + const INTERNAL_RULE_ENDPOINT = '/internal/alerting/rules'; + + const PageObjects = getPageObjects(['header']); async function createRule(rule: any): Promise { const ruleResponse = await supertest.post(RULE_ENDPOINT).set('kbn-xsrf', 'foo').send(rule); @@ -22,11 +25,21 @@ export default ({ getService }: FtrProviderContext) => { return ruleResponse.body.id; } + async function getRuleByName(name: string) { + const { + body: { data: rules }, + } = await supertest + .get(`${INTERNAL_RULE_ENDPOINT}/_find?search=${name}&search_fields=name`) + .expect(200); + return rules.find((rule: any) => rule.name === name); + } + async function deleteRuleById(ruleId: string) { - const ruleResponse = await supertest - .delete(`${RULE_ENDPOINT}/${ruleId}`) - .set('kbn-xsrf', 'foo'); - expect(ruleResponse.status).to.eql(204); + await supertest + .patch(`${INTERNAL_RULE_ENDPOINT}/_bulk_delete`) + .set('kbn-xsrf', 'foo') + .send({ ids: [ruleId] }) + .expect(200); return true; } @@ -42,11 +55,47 @@ export default ({ getService }: FtrProviderContext) => { return rows; }; + const selectAndFillInEsQueryRule = async (ruleName: string) => { + await testSubjects.setValue('ruleNameInput', ruleName); + await testSubjects.click(`.es-query-SelectOption`); + await testSubjects.click('queryFormType_esQuery'); + await testSubjects.click('selectIndexExpression'); + const indexComboBox = await find.byCssSelector('#indexSelectSearchBox'); + await indexComboBox.click(); + await indexComboBox.type('*'); + const filterSelectItems = await find.allByCssSelector(`.euiFilterSelectItem`); + await filterSelectItems[1].click(); + await testSubjects.click('thresholdAlertTimeFieldSelect'); + await retry.try(async () => { + const fieldOptions = await find.allByCssSelector('#thresholdTimeField option'); + expect(fieldOptions[1]).not.to.be(undefined); + await fieldOptions[1].click(); + }); + await testSubjects.click('closePopover'); + }; + describe('Observability Rules page', function () { this.tags('includeFirefox'); const observability = getService('observability'); + const navigateAndOpenCreateRuleFlyout = async () => { + await observability.alerts.common.navigateToRulesPage(); + await retry.waitFor( + 'Create Rule button is visible', + async () => await testSubjects.exists('createRuleButton') + ); + await retry.waitFor( + 'Create Rule button is enabled', + async () => await testSubjects.isEnabled('createRuleButton') + ); + await observability.alerts.rulesPage.clickCreateRuleButton(); + await retry.waitFor( + 'Create Rule flyout is visible', + async () => await testSubjects.exists('addRuleFlyoutTitle') + ); + }; + before(async () => { await esArchiver.load('x-pack/test/functional/es_archives/observability/alerts'); await esArchiver.load('x-pack/test/functional/es_archives/infra/metrics_and_logs'); @@ -67,20 +116,85 @@ export default ({ getService }: FtrProviderContext) => { describe('Create rule button', () => { it('Show Create Rule flyout when Create Rule button is clicked', async () => { - await observability.alerts.common.navigateToRulesPage(); - await retry.waitFor( - 'Create Rule button is visible', - async () => await testSubjects.exists('createRuleButton') + await navigateAndOpenCreateRuleFlyout(); + }); + }); + + describe('Create rules flyout', async () => { + const ruleName = 'esQueryRule'; + + afterEach(async () => { + const rule = await getRuleByName(ruleName); + if (rule) { + await deleteRuleById(rule.id); + } + await observability.users.restoreDefaultTestUserRole(); + }); + + it('Allows ES query rules to be created by users with only infrastructure feature enabled', async () => { + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + infrastructure: ['all'], + }) ); - await retry.waitFor( - 'Create Rule button is enabled', - async () => await testSubjects.isEnabled('createRuleButton') + await navigateAndOpenCreateRuleFlyout(); + await selectAndFillInEsQueryRule(ruleName); + + await testSubjects.click('saveRuleButton'); + + await PageObjects.header.waitUntilLoadingHasFinished(); + + const tableRows = await find.allByCssSelector('.euiTableRow'); + const rows = await getRulesList(tableRows); + expect(rows.length).to.be(1); + expect(rows[0].name).to.contain(ruleName); + }); + + it('allows ES query rules to be created by users with only logs feature enabled', async () => { + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + logs: ['all'], + }) ); - await observability.alerts.rulesPage.clickCreateRuleButton(); - await retry.waitFor( - 'Create Rule flyout is visible', - async () => await testSubjects.exists('addRuleFlyoutTitle') + await navigateAndOpenCreateRuleFlyout(); + await selectAndFillInEsQueryRule(ruleName); + + await testSubjects.click('saveRuleButton'); + + await PageObjects.header.waitUntilLoadingHasFinished(); + + const tableRows = await find.allByCssSelector('.euiTableRow'); + const rows = await getRulesList(tableRows); + expect(rows.length).to.be(1); + expect(rows[0].name).to.contain(ruleName); + }); + + it('Should allow the user to select consumers when creating ES query rules', async () => { + await observability.users.setTestUserRole( + observability.users.defineBasicObservabilityRole({ + logs: ['all'], + infrastructure: ['all'], + }) + ); + + await navigateAndOpenCreateRuleFlyout(); + await selectAndFillInEsQueryRule(ruleName); + + await retry.waitFor('consumer select modal is visible', async () => { + return await testSubjects.exists('ruleFormConsumerSelect'); + }); + + const consumerSelect = await testSubjects.find('ruleFormConsumerSelect'); + await consumerSelect.click(); + const consumerOptionsList = await testSubjects.find( + 'comboBoxOptionsList ruleFormConsumerSelect-optionsList' + ); + const consumerOptions = await consumerOptionsList.findAllByClassName( + 'euiComboBoxOption__content' ); + expect(consumerOptions.length).eql(2); + expect(await consumerOptions[0].getVisibleText()).eql('Metrics'); + expect(await consumerOptions[1].getVisibleText()).eql('Logs'); }); }); diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts index 1d328f05545d6..a5f039eaed5f2 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/check_registered_task_types.ts @@ -97,6 +97,7 @@ export default function ({ getService }: FtrProviderContext) { 'alerting:monitoring_alert_thread_pool_write_rejections', 'alerting:monitoring_ccr_read_exceptions', 'alerting:monitoring_shard_size', + 'alerting:observability.rules.custom_threshold', 'alerting:siem.eqlRule', 'alerting:siem.indicatorRule', 'alerting:siem.mlRule', diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_summary.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_summary.ts index b22fc830cb73d..5da72c7f6a76a 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_summary.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alert_summary.ts @@ -24,11 +24,11 @@ export default ({ getService }: FtrProviderContext) => { describe('Alerts - GET - _alert_summary', () => { before(async () => { - await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/alerts'); + await esArchiver.load('x-pack/test/functional/es_archives/rule_registry/o11y_alerts'); }); after(async () => { - await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/alerts'); + await esArchiver.unload('x-pack/test/functional/es_archives/rule_registry/o11y_alerts'); }); it('Alert summary for all LOGS alerts with features', async () => { diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts index 2ac420a8beb6b..1146fa925908c 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_alerts_index.ts @@ -20,7 +20,7 @@ export default ({ getService }: FtrProviderContext) => { const TEST_URL = '/internal/rac/alerts'; const ALERTS_INDEX_URL = `${TEST_URL}/index`; const SPACE1 = 'space1'; - const APM_ALERT_INDEX = '.alerts-observability.apm.alerts'; + const APM_ALERT_INDEX = '.alerts-observability.apm.alerts-default'; const SECURITY_SOLUTION_ALERT_INDEX = '.alerts-security.alerts'; const getAPMIndexName = async (user: User, space: string, expectedStatusCode: number = 200) => { @@ -53,12 +53,12 @@ export default ({ getService }: FtrProviderContext) => { describe('Users:', () => { it(`${obsOnlySpacesAll.username} should be able to access the APM alert in ${SPACE1}`, async () => { const indexNames = await getAPMIndexName(obsOnlySpacesAll, SPACE1); - expect(indexNames.includes(`${APM_ALERT_INDEX}-*`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below + expect(indexNames.includes(APM_ALERT_INDEX)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below }); it(`${superUser.username} should be able to access the APM alert in ${SPACE1}`, async () => { const indexNames = await getAPMIndexName(superUser, SPACE1); - expect(indexNames.includes(`${APM_ALERT_INDEX}-*`)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below + expect(indexNames.includes(APM_ALERT_INDEX)).to.eql(true); // assert this here so we can use constants in the dynamically-defined test cases below }); it(`${secOnlyRead.username} should NOT be able to access the APM alert in ${SPACE1}`, async () => { diff --git a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_browser_fields_by_feature_id.ts b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_browser_fields_by_feature_id.ts index feb84ab625fdb..94cbdbce77491 100644 --- a/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_browser_fields_by_feature_id.ts +++ b/x-pack/test/rule_registry/security_and_spaces/tests/basic/get_browser_fields_by_feature_id.ts @@ -46,7 +46,7 @@ export default ({ getService }: FtrProviderContext) => { 'uptime', ]); expect(Object.keys(resp.browserFields)).toEqual( - expect.arrayContaining(['base', 'event', 'kibana', 'message']) + expect.arrayContaining(['base', 'event', 'kibana']) ); }); @@ -66,7 +66,6 @@ export default ({ getService }: FtrProviderContext) => { 'error', 'event', 'kibana', - 'message', 'monitor', 'observer', 'tls', diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts index f75faa2b2f686..0389d0eace4e7 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_fired.ts @@ -12,7 +12,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -57,7 +57,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await dataViewApi.delete({ id: DATA_VIEW_ID, @@ -75,7 +75,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -142,7 +142,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts index fbcb7a1404293..0bd7fdf7bbb6f 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/avg_pct_no_data.ts @@ -11,7 +11,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -52,7 +52,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await dataViewApi.delete({ id: DATA_VIEW_ID, @@ -68,7 +68,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -135,7 +135,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts index 32c8111f33083..5ee54e1c9ad17 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/custom_eq_avg_bytes_fired.ts @@ -18,7 +18,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -62,7 +62,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await dataViewApi.delete({ id: DATA_VIEW_ID, @@ -80,7 +80,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -149,7 +149,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts index 1fa95b8cab8a9..56412a8380d2c 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/documents_count_fired.ts @@ -12,7 +12,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -56,7 +56,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await dataViewApi.delete({ id: DATA_VIEW_ID, @@ -74,7 +74,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -139,7 +139,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts index 8bd8312a5184a..f1b3c949421d4 100644 --- a/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts +++ b/x-pack/test_serverless/api_integration/test_suites/observability/custom_threshold_rule/group_by_fired.ts @@ -20,7 +20,7 @@ import { } from '@kbn/observability-plugin/common/custom_threshold_rule/types'; import { FIRED_ACTIONS_ID } from '@kbn/observability-plugin/server/lib/rules/custom_threshold/custom_threshold_executor'; import expect from '@kbn/expect'; -import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/observability-plugin/common/constants'; +import { OBSERVABILITY_THRESHOLD_RULE_TYPE_ID } from '@kbn/rule-data-utils'; import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { @@ -66,7 +66,7 @@ export default function ({ getService }: FtrProviderContext) { }); await esClient.deleteByQuery({ index: '.kibana-event-log-*', - query: { term: { 'kibana.alert.rule.consumer': 'alerts' } }, + query: { term: { 'kibana.alert.rule.consumer': 'logs' } }, }); await dataViewApi.delete({ id: DATA_VIEW_ID, @@ -84,7 +84,7 @@ export default function ({ getService }: FtrProviderContext) { const createdRule = await alertingApi.createRule({ tags: ['observability'], - consumer: 'alerts', + consumer: 'logs', name: 'Threshold rule', ruleTypeId: OBSERVABILITY_THRESHOLD_RULE_TYPE_ID, params: { @@ -158,7 +158,7 @@ export default function ({ getService }: FtrProviderContext) { 'kibana.alert.rule.category', 'Custom threshold (BETA)' ); - expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'alerts'); + expect(resp.hits.hits[0]._source).property('kibana.alert.rule.consumer', 'logs'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.name', 'Threshold rule'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.producer', 'observability'); expect(resp.hits.hits[0]._source).property('kibana.alert.rule.revision', 0); diff --git a/x-pack/test_serverless/tsconfig.json b/x-pack/test_serverless/tsconfig.json index 7a075a2903860..bf23a18899402 100644 --- a/x-pack/test_serverless/tsconfig.json +++ b/x-pack/test_serverless/tsconfig.json @@ -50,6 +50,7 @@ "@kbn/data-plugin", "@kbn/dev-utils", "@kbn/bfetch-plugin", + "@kbn/rule-data-utils", "@kbn/rison", "@kbn/std", "@kbn/serverless-common-settings",