diff --git a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.mock.ts b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.mock.ts new file mode 100644 index 0000000000000..d80f1d7289627 --- /dev/null +++ b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.mock.ts @@ -0,0 +1,21 @@ +/* + * 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 type { PublicMethodsOf } from '@kbn/utility-types'; +import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; + +const creatAlertingAuthorizationClientFactoryMock = () => { + const mocked: jest.Mocked> = { + create: jest.fn(), + initialize: jest.fn(), + }; + return mocked; +}; + +export const alertingAuthorizationClientFactoryMock = { + createFactory: creatAlertingAuthorizationClientFactoryMock, +}; diff --git a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.test.ts b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.test.ts new file mode 100644 index 0000000000000..dd7c483c4554e --- /dev/null +++ b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.test.ts @@ -0,0 +1,130 @@ +/* + * 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 { Request } from '@hapi/hapi'; +import { alertTypeRegistryMock } from './alert_type_registry.mock'; +import { KibanaRequest } from '../../../../src/core/server'; +import { savedObjectsClientMock } from '../../../../src/core/server/mocks'; +import { securityMock } from '../../security/server/mocks'; +import { ALERTS_FEATURE_ID } from '../common'; +import { + AlertingAuthorizationClientFactory, + AlertingAuthorizationClientFactoryOpts, +} from './alerting_authorization_client_factory'; +import { featuresPluginMock } from '../../features/server/mocks'; + +jest.mock('./authorization/alerting_authorization'); +jest.mock('./authorization/audit_logger'); + +const savedObjectsClient = savedObjectsClientMock.create(); +const features = featuresPluginMock.createStart(); + +const securityPluginSetup = securityMock.createSetup(); +const securityPluginStart = securityMock.createStart(); + +const alertingAuthorizationClientFactoryParams: jest.Mocked = { + alertTypeRegistry: alertTypeRegistryMock.create(), + getSpace: jest.fn(), + features, +}; + +const fakeRequest = ({ + app: {}, + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + getSavedObjectsClient: () => savedObjectsClient, +} as unknown) as Request; + +beforeEach(() => { + jest.resetAllMocks(); +}); + +test('creates an alerting authorization client with proper constructor arguments when security is enabled', async () => { + const factory = new AlertingAuthorizationClientFactory(); + factory.initialize({ + securityPluginSetup, + securityPluginStart, + ...alertingAuthorizationClientFactoryParams, + }); + const request = KibanaRequest.from(fakeRequest); + const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger'); + + factory.create(request); + + const { AlertingAuthorization } = jest.requireMock('./authorization/alerting_authorization'); + expect(AlertingAuthorization).toHaveBeenCalledWith({ + request, + authorization: securityPluginStart.authz, + alertTypeRegistry: alertingAuthorizationClientFactoryParams.alertTypeRegistry, + features: alertingAuthorizationClientFactoryParams.features, + auditLogger: expect.any(AlertingAuthorizationAuditLogger), + getSpace: expect.any(Function), + exemptConsumerIds: [], + }); + + expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled(); + expect(securityPluginSetup.audit.getLogger).toHaveBeenCalledWith(ALERTS_FEATURE_ID); +}); + +test('creates an alerting authorization client with proper constructor arguments when exemptConsumerIds are specified', async () => { + const factory = new AlertingAuthorizationClientFactory(); + factory.initialize({ + securityPluginSetup, + securityPluginStart, + ...alertingAuthorizationClientFactoryParams, + }); + const request = KibanaRequest.from(fakeRequest); + const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger'); + + factory.create(request, ['exemptConsumerA', 'exemptConsumerB']); + + const { AlertingAuthorization } = jest.requireMock('./authorization/alerting_authorization'); + expect(AlertingAuthorization).toHaveBeenCalledWith({ + request, + authorization: securityPluginStart.authz, + alertTypeRegistry: alertingAuthorizationClientFactoryParams.alertTypeRegistry, + features: alertingAuthorizationClientFactoryParams.features, + auditLogger: expect.any(AlertingAuthorizationAuditLogger), + getSpace: expect.any(Function), + exemptConsumerIds: ['exemptConsumerA', 'exemptConsumerB'], + }); + + expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled(); + expect(securityPluginSetup.audit.getLogger).toHaveBeenCalledWith(ALERTS_FEATURE_ID); +}); + +test('creates an alerting authorization client with proper constructor arguments', async () => { + const factory = new AlertingAuthorizationClientFactory(); + factory.initialize(alertingAuthorizationClientFactoryParams); + const request = KibanaRequest.from(fakeRequest); + const { AlertingAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger'); + + factory.create(request); + + const { AlertingAuthorization } = jest.requireMock('./authorization/alerting_authorization'); + expect(AlertingAuthorization).toHaveBeenCalledWith({ + request, + alertTypeRegistry: alertingAuthorizationClientFactoryParams.alertTypeRegistry, + features: alertingAuthorizationClientFactoryParams.features, + auditLogger: expect.any(AlertingAuthorizationAuditLogger), + getSpace: expect.any(Function), + exemptConsumerIds: [], + }); + + expect(AlertingAuthorizationAuditLogger).toHaveBeenCalled(); + expect(securityPluginSetup.audit.getLogger).not.toHaveBeenCalled(); +}); diff --git a/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts new file mode 100644 index 0000000000000..ea882b07f1e98 --- /dev/null +++ b/x-pack/plugins/alerting/server/alerting_authorization_client_factory.ts @@ -0,0 +1,59 @@ +/* + * 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 { KibanaRequest } from 'src/core/server'; +import { ALERTS_FEATURE_ID } from '../common'; +import { AlertTypeRegistry } from './types'; +import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; +import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; +import { AlertingAuthorization } from './authorization/alerting_authorization'; +import { AlertingAuthorizationAuditLogger } from './authorization/audit_logger'; +import { Space } from '../../spaces/server'; + +export interface AlertingAuthorizationClientFactoryOpts { + alertTypeRegistry: AlertTypeRegistry; + securityPluginSetup?: SecurityPluginSetup; + securityPluginStart?: SecurityPluginStart; + getSpace: (request: KibanaRequest) => Promise; + features: FeaturesPluginStart; +} + +export class AlertingAuthorizationClientFactory { + private isInitialized = false; + private alertTypeRegistry!: AlertTypeRegistry; + private securityPluginStart?: SecurityPluginStart; + private securityPluginSetup?: SecurityPluginSetup; + private features!: FeaturesPluginStart; + private getSpace!: (request: KibanaRequest) => Promise; + + public initialize(options: AlertingAuthorizationClientFactoryOpts) { + if (this.isInitialized) { + throw new Error('AlertingAuthorizationClientFactory already initialized'); + } + this.isInitialized = true; + this.getSpace = options.getSpace; + this.alertTypeRegistry = options.alertTypeRegistry; + this.securityPluginSetup = options.securityPluginSetup; + this.securityPluginStart = options.securityPluginStart; + this.features = options.features; + } + + public create(request: KibanaRequest, exemptConsumerIds: string[] = []): AlertingAuthorization { + const { securityPluginSetup, securityPluginStart, features } = this; + return new AlertingAuthorization({ + authorization: securityPluginStart?.authz, + request, + getSpace: this.getSpace, + alertTypeRegistry: this.alertTypeRegistry, + features: features!, + auditLogger: new AlertingAuthorizationAuditLogger( + securityPluginSetup?.audit.getLogger(ALERTS_FEATURE_ID) + ), + exemptConsumerIds, + }); + } +} diff --git a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts index 21eff9d796235..b8df0c3d8de09 100644 --- a/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts +++ b/x-pack/plugins/alerting/server/alerts_client/alerts_client.ts @@ -46,7 +46,14 @@ import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/se import { TaskManagerStartContract } from '../../../task_manager/server'; import { taskInstanceToAlertTaskInstance } from '../task_runner/alert_task_instance'; import { RegistryAlertType, UntypedNormalizedAlertType } from '../alert_type_registry'; -import { AlertsAuthorization, WriteOperations, ReadOperations } from '../authorization'; +import { + AlertingAuthorization, + WriteOperations, + ReadOperations, + AlertingAuthorizationEntity, + AlertingAuthorizationFilterType, + AlertingAuthorizationFilterOpts, +} from '../authorization'; import { IEventLogClient } from '../../../../plugins/event_log/server'; import { parseIsoOrRelativeDate } from '../lib/iso_or_relative_date'; import { alertInstanceSummaryFromEventLog } from '../lib/alert_instance_summary_from_event_log'; @@ -57,7 +64,7 @@ import { retryIfConflicts } from '../lib/retry_if_conflicts'; import { partiallyUpdateAlert } from '../saved_objects'; import { markApiKeyForInvalidation } from '../invalidate_pending_api_keys/mark_api_key_for_invalidation'; import { alertAuditEvent, AlertAuditAction } from './audit_events'; -import { nodeBuilder } from '../../../../../src/plugins/data/common'; +import { KueryNode, nodeBuilder } from '../../../../../src/plugins/data/common'; import { mapSortField } from './lib'; import { getAlertExecutionStatusPending } from '../lib/alert_execution_status'; @@ -76,7 +83,7 @@ export interface ConstructorOptions { logger: Logger; taskManager: TaskManagerStartContract; unsecuredSavedObjectsClient: SavedObjectsClientContract; - authorization: AlertsAuthorization; + authorization: AlertingAuthorization; actionsAuthorization: ActionsAuthorization; alertTypeRegistry: AlertTypeRegistry; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; @@ -176,6 +183,10 @@ export interface GetAlertInstanceSummaryParams { dateStart?: string; } +const alertingAuthorizationFilterOpts: AlertingAuthorizationFilterOpts = { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { ruleTypeId: 'alert.attributes.alertTypeId', consumer: 'alert.attributes.consumer' }, +}; export class AlertsClient { private readonly logger: Logger; private readonly getUserName: () => Promise; @@ -183,7 +194,7 @@ export class AlertsClient { private readonly namespace?: string; private readonly taskManager: TaskManagerStartContract; private readonly unsecuredSavedObjectsClient: SavedObjectsClientContract; - private readonly authorization: AlertsAuthorization; + private readonly authorization: AlertingAuthorization; private readonly alertTypeRegistry: AlertTypeRegistry; private readonly createAPIKey: (name: string) => Promise; private readonly getActionsClient: () => Promise; @@ -234,11 +245,12 @@ export class AlertsClient { const id = options?.id || SavedObjectsUtils.generateId(); try { - await this.authorization.ensureAuthorized( - data.alertTypeId, - data.consumer, - WriteOperations.Create - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: data.alertTypeId, + consumer: data.consumer, + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); } catch (error) { this.auditLogger?.log( alertAuditEvent({ @@ -355,11 +367,12 @@ export class AlertsClient { }): Promise> { const result = await this.unsecuredSavedObjectsClient.get('alert', id); try { - await this.authorization.ensureAuthorized( - result.attributes.alertTypeId, - result.attributes.consumer, - ReadOperations.Get - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: result.attributes.alertTypeId, + consumer: result.attributes.consumer, + operation: ReadOperations.Get, + entity: AlertingAuthorizationEntity.Rule, + }); } catch (error) { this.auditLogger?.log( alertAuditEvent({ @@ -381,11 +394,12 @@ export class AlertsClient { public async getAlertState({ id }: { id: string }): Promise { const alert = await this.get({ id }); - await this.authorization.ensureAuthorized( - alert.alertTypeId, - alert.consumer, - ReadOperations.GetAlertState - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: alert.alertTypeId, + consumer: alert.consumer, + operation: ReadOperations.GetRuleState, + entity: AlertingAuthorizationEntity.Rule, + }); if (alert.scheduledTaskId) { const { state } = taskInstanceToAlertTaskInstance( await this.taskManager.get(alert.scheduledTaskId), @@ -401,11 +415,12 @@ export class AlertsClient { }: GetAlertInstanceSummaryParams): Promise { this.logger.debug(`getAlertInstanceSummary(): getting alert ${id}`); const alert = await this.get({ id }); - await this.authorization.ensureAuthorized( - alert.alertTypeId, - alert.consumer, - ReadOperations.GetAlertInstanceSummary - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: alert.alertTypeId, + consumer: alert.consumer, + operation: ReadOperations.GetAlertSummary, + entity: AlertingAuthorizationEntity.Rule, + }); // default duration of instance summary is 60 * alert interval const dateNow = new Date(); @@ -446,7 +461,10 @@ export class AlertsClient { }: { options?: FindOptions } = {}): Promise> { let authorizationTuple; try { - authorizationTuple = await this.authorization.getFindAuthorizationFilter(); + authorizationTuple = await this.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); } catch (error) { this.auditLogger?.log( alertAuditEvent({ @@ -458,7 +476,7 @@ export class AlertsClient { } const { filter: authorizationFilter, - ensureAlertTypeIsAuthorized, + ensureRuleTypeIsAuthorized, logSuccessfulAuthorization, } = authorizationTuple; @@ -472,7 +490,10 @@ export class AlertsClient { sortField: mapSortField(options.sortField), filter: (authorizationFilter && options.filter - ? nodeBuilder.and([esKuery.fromKueryExpression(options.filter), authorizationFilter]) + ? nodeBuilder.and([ + esKuery.fromKueryExpression(options.filter), + authorizationFilter as KueryNode, + ]) : authorizationFilter) ?? options.filter, fields: fields ? this.includeFieldsRequiredForAuthentication(fields) : fields, type: 'alert', @@ -480,7 +501,11 @@ export class AlertsClient { const authorizedData = data.map(({ id, attributes, references }) => { try { - ensureAlertTypeIsAuthorized(attributes.alertTypeId, attributes.consumer); + ensureRuleTypeIsAuthorized( + attributes.alertTypeId, + attributes.consumer, + AlertingAuthorizationEntity.Rule + ); } catch (error) { this.auditLogger?.log( alertAuditEvent({ @@ -526,7 +551,10 @@ export class AlertsClient { const { filter: authorizationFilter, logSuccessfulAuthorization, - } = await this.authorization.getFindAuthorizationFilter(); + } = await this.authorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + alertingAuthorizationFilterOpts + ); const filter = options.filter ? `${options.filter} and alert.attributes.executionStatus.status:(${status})` : `alert.attributes.executionStatus.status:(${status})`; @@ -534,7 +562,10 @@ export class AlertsClient { ...options, filter: (authorizationFilter && filter - ? nodeBuilder.and([esKuery.fromKueryExpression(filter), authorizationFilter]) + ? nodeBuilder.and([ + esKuery.fromKueryExpression(filter), + authorizationFilter as KueryNode, + ]) : authorizationFilter) ?? filter, page: 1, perPage: 0, @@ -581,11 +612,12 @@ export class AlertsClient { } try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.Delete - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Delete, + entity: AlertingAuthorizationEntity.Rule, + }); } catch (error) { this.auditLogger?.log( alertAuditEvent({ @@ -654,11 +686,12 @@ export class AlertsClient { } try { - await this.authorization.ensureAuthorized( - alertSavedObject.attributes.alertTypeId, - alertSavedObject.attributes.consumer, - WriteOperations.Update - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: alertSavedObject.attributes.alertTypeId, + consumer: alertSavedObject.attributes.consumer, + operation: WriteOperations.Update, + entity: AlertingAuthorizationEntity.Rule, + }); } catch (error) { this.auditLogger?.log( alertAuditEvent({ @@ -826,11 +859,12 @@ export class AlertsClient { } try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.UpdateApiKey - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.UpdateApiKey, + entity: AlertingAuthorizationEntity.Rule, + }); if (attributes.actions.length) { await this.actionsAuthorization.ensureAuthorized('execute'); } @@ -930,11 +964,12 @@ export class AlertsClient { } try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.Enable - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Enable, + entity: AlertingAuthorizationEntity.Rule, + }); if (attributes.actions.length) { await this.actionsAuthorization.ensureAuthorized('execute'); @@ -1047,11 +1082,12 @@ export class AlertsClient { } try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.Disable - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.Disable, + entity: AlertingAuthorizationEntity.Rule, + }); } catch (error) { this.auditLogger?.log( alertAuditEvent({ @@ -1119,11 +1155,12 @@ export class AlertsClient { ); try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.MuteAll - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.MuteAll, + entity: AlertingAuthorizationEntity.Rule, + }); if (attributes.actions.length) { await this.actionsAuthorization.ensureAuthorized('execute'); @@ -1180,11 +1217,12 @@ export class AlertsClient { ); try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.UnmuteAll - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.UnmuteAll, + entity: AlertingAuthorizationEntity.Rule, + }); if (attributes.actions.length) { await this.actionsAuthorization.ensureAuthorized('execute'); @@ -1241,11 +1279,12 @@ export class AlertsClient { ); try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.MuteInstance - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.MuteAlert, + entity: AlertingAuthorizationEntity.Rule, + }); if (attributes.actions.length) { await this.actionsAuthorization.ensureAuthorized('execute'); @@ -1308,11 +1347,12 @@ export class AlertsClient { ); try { - await this.authorization.ensureAuthorized( - attributes.alertTypeId, - attributes.consumer, - WriteOperations.UnmuteInstance - ); + await this.authorization.ensureAuthorized({ + ruleTypeId: attributes.alertTypeId, + consumer: attributes.consumer, + operation: WriteOperations.UnmuteAlert, + entity: AlertingAuthorizationEntity.Rule, + }); if (attributes.actions.length) { await this.actionsAuthorization.ensureAuthorized('execute'); } @@ -1353,10 +1393,11 @@ export class AlertsClient { } public async listAlertTypes() { - return await this.authorization.filterByAlertTypeAuthorization(this.alertTypeRegistry.list(), [ - ReadOperations.Get, - WriteOperations.Create, - ]); + return await this.authorization.filterByRuleTypeAuthorization( + this.alertTypeRegistry.list(), + [ReadOperations.Get, WriteOperations.Create], + AlertingAuthorizationEntity.Rule + ); } private async scheduleAlert(id: string, alertTypeId: string, schedule: IntervalSchedule) { diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts index 81240f1e88531..bf966d38f6bc6 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/aggregate.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { getBeforeSetup, setGlobalDate } from './lib'; import { AlertExecutionStatusValues } from '../../types'; @@ -24,7 +24,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const kibanaVersion = 'v7.10.0'; @@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -67,7 +67,7 @@ describe('aggregate()', () => { ]); beforeEach(() => { authorization.getFindAuthorizationFilter.mockResolvedValue({ - ensureAlertTypeIsAuthorized() {}, + ensureRuleTypeIsAuthorized() {}, logSuccessfulAuthorization() {}, }); unsecuredSavedObjectsClient.find @@ -102,7 +102,7 @@ describe('aggregate()', () => { saved_objects: [], }); alertTypeRegistry.list.mockReturnValue(listedTypes); - authorization.filterByAlertTypeAuthorization.mockResolvedValue( + authorization.filterByRuleTypeAuthorization.mockResolvedValue( new Set([ { id: 'myType', diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts index 21974cff5eb2f..a2d5a5e0386c4 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/create.test.ts @@ -10,10 +10,10 @@ import { AlertsClient, ConstructorOptions, CreateOptions } from '../alerts_clien import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization, ActionsClient } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -31,7 +31,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -40,7 +40,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -194,7 +194,12 @@ describe('create()', () => { await tryToExecuteOperation({ data }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'create'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'create', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to create this type of alert', async () => { @@ -211,7 +216,12 @@ describe('create()', () => { `[Error: Unauthorized to create a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'create'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'create', + ruleTypeId: 'myType', + }); }); }); @@ -338,7 +348,12 @@ describe('create()', () => { ], }); const result = await alertsClient.create({ data }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('123', 'bar', 'create'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'bar', + operation: 'create', + ruleTypeId: '123', + }); expect(result).toMatchInlineSnapshot(` Object { "actions": Array [ diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/delete.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/delete.test.ts index 82aea8e5b3ba2..0f9d91d829854 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/delete.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/delete.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -231,7 +231,12 @@ describe('delete()', () => { test('ensures user is authorised to delete this type of alert under the consumer', async () => { await alertsClient.delete({ id: '1' }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'delete'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'delete', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to delete this type of alert', async () => { @@ -243,7 +248,12 @@ describe('delete()', () => { `[Error: Unauthorized to delete a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'delete'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'delete', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/disable.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/disable.test.ts index 712a1c539d8d9..7eb107c2f4dec 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/disable.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/disable.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { InvalidatePendingApiKey } from '../../types'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; @@ -23,7 +23,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -99,7 +99,12 @@ describe('disable()', () => { test('ensures user is authorised to disable this type of alert under the consumer', async () => { await alertsClient.disable({ id: '1' }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'disable'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'disable', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to disable this type of alert', async () => { @@ -111,7 +116,12 @@ describe('disable()', () => { `[Error: Unauthorized to disable a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'disable'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'disable', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/enable.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/enable.test.ts index 8c0a09c74457e..8329e52d7444a 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/enable.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/enable.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; @@ -24,7 +24,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -33,7 +33,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -137,7 +137,12 @@ describe('enable()', () => { test('ensures user is authorised to enable this type of alert under the consumer', async () => { await alertsClient.enable({ id: '1' }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'enable'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'enable', + ruleTypeId: 'myType', + }); expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute'); }); @@ -150,7 +155,12 @@ describe('enable()', () => { `[Error: Unauthorized to enable a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'enable'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'enable', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts index bfeecd4540d15..8fa8ae7ae38b0 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/find.test.ts @@ -9,12 +9,12 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { nodeTypes } from '../../../../../../src/plugins/data/common'; import { esKuery } from '../../../../../../src/plugins/data/server'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -26,7 +26,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -35,7 +35,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -75,7 +75,7 @@ describe('find()', () => { ]); beforeEach(() => { authorization.getFindAuthorizationFilter.mockResolvedValue({ - ensureAlertTypeIsAuthorized() {}, + ensureRuleTypeIsAuthorized() {}, logSuccessfulAuthorization() {}, }); unsecuredSavedObjectsClient.find.mockResolvedValueOnce({ @@ -117,7 +117,7 @@ describe('find()', () => { ], }); alertTypeRegistry.list.mockReturnValue(listedTypes); - authorization.filterByAlertTypeAuthorization.mockResolvedValue( + authorization.filterByRuleTypeAuthorization.mockResolvedValue( new Set([ { id: 'myType', @@ -196,7 +196,7 @@ describe('find()', () => { ); authorization.getFindAuthorizationFilter.mockResolvedValue({ filter, - ensureAlertTypeIsAuthorized() {}, + ensureRuleTypeIsAuthorized() {}, logSuccessfulAuthorization() {}, }); @@ -219,10 +219,10 @@ describe('find()', () => { }); test('ensures authorization even when the fields required to authorize are omitted from the find', async () => { - const ensureAlertTypeIsAuthorized = jest.fn(); + const ensureRuleTypeIsAuthorized = jest.fn(); const logSuccessfulAuthorization = jest.fn(); authorization.getFindAuthorizationFilter.mockResolvedValue({ - ensureAlertTypeIsAuthorized, + ensureRuleTypeIsAuthorized, logSuccessfulAuthorization, }); @@ -271,7 +271,7 @@ describe('find()', () => { fields: ['tags', 'alertTypeId', 'consumer'], type: 'alert', }); - expect(ensureAlertTypeIsAuthorized).toHaveBeenCalledWith('myType', 'myApp'); + expect(ensureRuleTypeIsAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'rule'); expect(logSuccessfulAuthorization).toHaveBeenCalled(); }); }); @@ -313,7 +313,7 @@ describe('find()', () => { test('logs audit event when not authorised to search alert type', async () => { const alertsClient = new AlertsClient({ ...alertsClientParams, auditLogger }); authorization.getFindAuthorizationFilter.mockResolvedValue({ - ensureAlertTypeIsAuthorized: jest.fn(() => { + ensureRuleTypeIsAuthorized: jest.fn(() => { throw new Error('Unauthorized'); }), logSuccessfulAuthorization: jest.fn(), diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts index d25ed1da51577..a958ea4061ae5 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/get.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -182,7 +182,12 @@ describe('get()', () => { const alertsClient = new AlertsClient(alertsClientParams); await alertsClient.get({ id: '1' }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'get'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'get', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to get this type of alert', async () => { @@ -195,7 +200,12 @@ describe('get()', () => { `[Error: Unauthorized to get a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'get'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'get', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_instance_summary.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_instance_summary.test.ts index d5a4b2d8d9446..2ef9982ba8f85 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_instance_summary.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_instance_summary.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { eventLogClientMock } from '../../../../event_log/server/mocks'; import { QueryEventsBySavedObjectResult } from '../../../../event_log/server'; @@ -27,7 +27,7 @@ const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const eventLogClient = eventLogClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const kibanaVersion = 'v7.10.0'; @@ -35,7 +35,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_state.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_state.test.ts index 0cd7dcf14c7c3..07f58924d727d 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_state.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/get_alert_state.test.ts @@ -9,11 +9,11 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { TaskStatus } from '../../../../task_manager/server'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { getBeforeSetup } from './lib'; @@ -22,7 +22,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const kibanaVersion = 'v7.10.0'; @@ -30,7 +30,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -210,31 +210,33 @@ describe('getAlertState()', () => { const alertsClient = new AlertsClient(alertsClientParams); await alertsClient.getAlertState({ id: '1' }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'getAlertState' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'getRuleState', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to getAlertState this type of alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); // `get` check authorization.ensureAuthorized.mockResolvedValueOnce(); - // `getAlertState` check + // `getRuleState` check authorization.ensureAuthorized.mockRejectedValueOnce( - new Error(`Unauthorized to getAlertState a "myType" alert for "myApp"`) + new Error(`Unauthorized to getRuleState a "myType" alert for "myApp"`) ); await expect(alertsClient.getAlertState({ id: '1' })).rejects.toMatchInlineSnapshot( - `[Error: Unauthorized to getAlertState a "myType" alert for "myApp"]` + `[Error: Unauthorized to getRuleState a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'getAlertState' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'getRuleState', + ruleTypeId: 'myType', + }); }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts index b8d597ab15471..9fe33996b9edf 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/list_alert_types.test.ts @@ -9,13 +9,13 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; import { - AlertsAuthorization, + AlertingAuthorization, RegistryAlertTypeWithAuth, -} from '../../authorization/alerts_authorization'; +} from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { getBeforeSetup } from './lib'; import { RecoveredActionGroup } from '../../../common'; @@ -26,7 +26,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const kibanaVersion = 'v7.10.0'; @@ -34,7 +34,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -89,7 +89,7 @@ describe('listAlertTypes', () => { test('should return a list of AlertTypes that exist in the registry', async () => { alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); - authorization.filterByAlertTypeAuthorization.mockResolvedValue( + authorization.filterByRuleTypeAuthorization.mockResolvedValue( new Set([ { ...myAppAlertType, authorizedConsumers }, { ...alertingAlertType, authorizedConsumers }, @@ -147,7 +147,7 @@ describe('listAlertTypes', () => { enabledInLicense: true, }, ]); - authorization.filterByAlertTypeAuthorization.mockResolvedValue(authorizedTypes); + authorization.filterByRuleTypeAuthorization.mockResolvedValue(authorizedTypes); expect(await alertsClient.listAlertTypes()).toEqual(authorizedTypes); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/mute_all.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/mute_all.test.ts index 227f57af6a53e..6734ec9b99600 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/mute_all.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/mute_all.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -126,7 +126,12 @@ describe('muteAll()', () => { const alertsClient = new AlertsClient(alertsClientParams); await alertsClient.muteAll({ id: '1' }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'muteAll'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'muteAll', + ruleTypeId: 'myType', + }); expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute'); }); @@ -140,7 +145,12 @@ describe('muteAll()', () => { `[Error: Unauthorized to muteAll a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'muteAll'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'muteAll', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/mute_instance.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/mute_instance.test.ts index a97cc115a3baf..bc0b7288e952f 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/mute_instance.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/mute_instance.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -159,30 +159,32 @@ describe('muteInstance()', () => { await alertsClient.muteInstance({ alertId: '1', alertInstanceId: '2' }); expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute'); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'muteInstance' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'muteAlert', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to muteInstance this type of alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); authorization.ensureAuthorized.mockRejectedValue( - new Error(`Unauthorized to muteInstance a "myType" alert for "myApp"`) + new Error(`Unauthorized to muteAlert a "myType" alert for "myApp"`) ); await expect( alertsClient.muteInstance({ alertId: '1', alertInstanceId: '2' }) ).rejects.toMatchInlineSnapshot( - `[Error: Unauthorized to muteInstance a "myType" alert for "myApp"]` + `[Error: Unauthorized to muteAlert a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'muteInstance' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'muteAlert', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/unmute_all.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/unmute_all.test.ts index c3c1b609e3da0..c061bc7840fb6 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/unmute_all.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/unmute_all.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -126,7 +126,12 @@ describe('unmuteAll()', () => { const alertsClient = new AlertsClient(alertsClientParams); await alertsClient.unmuteAll({ id: '1' }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'unmuteAll'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'unmuteAll', + ruleTypeId: 'myType', + }); expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute'); }); @@ -140,7 +145,12 @@ describe('unmuteAll()', () => { `[Error: Unauthorized to unmuteAll a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'unmuteAll'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'unmuteAll', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/unmute_instance.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/unmute_instance.test.ts index 03736040e7085..4da83b6441a8d 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/unmute_instance.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/unmute_instance.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -22,7 +22,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -31,7 +31,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -157,30 +157,32 @@ describe('unmuteInstance()', () => { await alertsClient.unmuteInstance({ alertId: '1', alertInstanceId: '2' }); expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute'); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'unmuteInstance' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'unmuteAlert', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to unmuteInstance this type of alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); authorization.ensureAuthorized.mockRejectedValue( - new Error(`Unauthorized to unmuteInstance a "myType" alert for "myApp"`) + new Error(`Unauthorized to unmuteAlert a "myType" alert for "myApp"`) ); await expect( alertsClient.unmuteInstance({ alertId: '1', alertInstanceId: '2' }) ).rejects.toMatchInlineSnapshot( - `[Error: Unauthorized to unmuteInstance a "myType" alert for "myApp"]` + `[Error: Unauthorized to unmuteAlert a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'unmuteInstance' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'unmuteAlert', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts index c5e30e29efb44..c743312ef2c4b 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/update.test.ts @@ -11,12 +11,12 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { IntervalSchedule, InvalidatePendingApiKey } from '../../types'; import { RecoveredActionGroup } from '../../../common'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { resolvable } from '../../test_utils'; import { ActionsAuthorization, ActionsClient } from '../../../../actions/server'; import { TaskStatus } from '../../../../task_manager/server'; @@ -28,7 +28,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -37,7 +37,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -1415,7 +1415,12 @@ describe('update()', () => { }, }); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'update'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'update', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to update this type of alert', async () => { @@ -1442,7 +1447,12 @@ describe('update()', () => { `[Error: Unauthorized to update a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith('myType', 'myApp', 'update'); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'update', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client/tests/update_api_key.test.ts b/x-pack/plugins/alerting/server/alerts_client/tests/update_api_key.test.ts index 18bae8d34a8da..4215f14b4a560 100644 --- a/x-pack/plugins/alerting/server/alerts_client/tests/update_api_key.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client/tests/update_api_key.test.ts @@ -9,10 +9,10 @@ import { AlertsClient, ConstructorOptions } from '../alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { taskManagerMock } from '../../../../task_manager/server/mocks'; import { alertTypeRegistryMock } from '../../alert_type_registry.mock'; -import { alertsAuthorizationMock } from '../../authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from '../../authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../../../encrypted_saved_objects/server/mocks'; import { actionsAuthorizationMock } from '../../../../actions/server/mocks'; -import { AlertsAuthorization } from '../../authorization/alerts_authorization'; +import { AlertingAuthorization } from '../../authorization/alerting_authorization'; import { ActionsAuthorization } from '../../../../actions/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { auditServiceMock } from '../../../../security/server/audit/index.mock'; @@ -23,7 +23,7 @@ const taskManager = taskManagerMock.createStart(); const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const auditLogger = auditServiceMock.create().asScoped(httpServerMock.createKibanaRequest()); @@ -32,7 +32,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', @@ -268,11 +268,12 @@ describe('updateApiKey()', () => { await alertsClient.updateApiKey({ id: '1' }); expect(actionsAuthorization.ensureAuthorized).toHaveBeenCalledWith('execute'); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'updateApiKey' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'updateApiKey', + ruleTypeId: 'myType', + }); }); test('throws when user is not authorised to updateApiKey this type of alert', async () => { @@ -284,11 +285,12 @@ describe('updateApiKey()', () => { `[Error: Unauthorized to updateApiKey a "myType" alert for "myApp"]` ); - expect(authorization.ensureAuthorized).toHaveBeenCalledWith( - 'myType', - 'myApp', - 'updateApiKey' - ); + expect(authorization.ensureAuthorized).toHaveBeenCalledWith({ + entity: 'rule', + consumer: 'myApp', + operation: 'updateApiKey', + ruleTypeId: 'myType', + }); }); }); diff --git a/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts b/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts index f16b16cf74dd2..98ad427d0c37b 100644 --- a/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_conflict_retries.test.ts @@ -11,10 +11,10 @@ import { AlertsClient, ConstructorOptions } from './alerts_client'; import { savedObjectsClientMock, loggingSystemMock } from '../../../../src/core/server/mocks'; import { taskManagerMock } from '../../task_manager/server/mocks'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; -import { alertsAuthorizationMock } from './authorization/alerts_authorization.mock'; +import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock'; import { encryptedSavedObjectsMock } from '../../encrypted_saved_objects/server/mocks'; import { actionsClientMock, actionsAuthorizationMock } from '../../actions/server/mocks'; -import { AlertsAuthorization } from './authorization/alerts_authorization'; +import { AlertingAuthorization } from './authorization/alerting_authorization'; import { ActionsAuthorization } from '../../actions/server'; import { SavedObjectsErrorHelpers } from '../../../../src/core/server'; import { RetryForConflictsAttempts } from './lib/retry_if_conflicts'; @@ -32,7 +32,7 @@ const alertTypeRegistry = alertTypeRegistryMock.create(); const unsecuredSavedObjectsClient = savedObjectsClientMock.create(); const encryptedSavedObjects = encryptedSavedObjectsMock.createClient(); -const authorization = alertsAuthorizationMock.create(); +const authorization = alertingAuthorizationMock.create(); const actionsAuthorization = actionsAuthorizationMock.create(); const kibanaVersion = 'v7.10.0'; @@ -41,7 +41,7 @@ const alertsClientParams: jest.Mocked = { taskManager, alertTypeRegistry, unsecuredSavedObjectsClient, - authorization: (authorization as unknown) as AlertsAuthorization, + authorization: (authorization as unknown) as AlertingAuthorization, actionsAuthorization: (actionsAuthorization as unknown) as ActionsAuthorization, spaceId: 'default', namespace: 'default', diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts index 2bcd792e0a1b1..1b39af9972814 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.test.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.test.ts @@ -20,34 +20,40 @@ import { AuthenticatedUser } from '../../../plugins/security/common/model'; import { securityMock } from '../../security/server/mocks'; import { PluginStartContract as ActionsStartContract } from '../../actions/server'; import { actionsMock, actionsAuthorizationMock } from '../../actions/server/mocks'; -import { featuresPluginMock } from '../../features/server/mocks'; import { LegacyAuditLogger } from '../../security/server'; import { ALERTS_FEATURE_ID } from '../common'; import { eventLogMock } from '../../event_log/server/mocks'; +import { alertingAuthorizationMock } from './authorization/alerting_authorization.mock'; +import { alertingAuthorizationClientFactoryMock } from './alerting_authorization_client_factory.mock'; +import { AlertingAuthorization } from './authorization'; +import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; jest.mock('./alerts_client'); -jest.mock('./authorization/alerts_authorization'); +jest.mock('./authorization/alerting_authorization'); jest.mock('./authorization/audit_logger'); const savedObjectsClient = savedObjectsClientMock.create(); const savedObjectsService = savedObjectsServiceMock.createInternalStartContract(); -const features = featuresPluginMock.createStart(); const securityPluginSetup = securityMock.createSetup(); const securityPluginStart = securityMock.createStart(); + +const alertsAuthorization = alertingAuthorizationMock.create(); +const alertingAuthorizationClientFactory = alertingAuthorizationClientFactoryMock.createFactory(); + const alertsClientFactoryParams: jest.Mocked = { logger: loggingSystemMock.create().get(), taskManager: taskManagerMock.createStart(), alertTypeRegistry: alertTypeRegistryMock.create(), getSpaceId: jest.fn(), - getSpace: jest.fn(), spaceIdToNamespace: jest.fn(), encryptedSavedObjectsClient: encryptedSavedObjectsMock.createClient(), actions: actionsMock.createStart(), - features, eventLog: eventLogMock.createStart(), kibanaVersion: '7.10.0', + authorization: (alertingAuthorizationClientFactory as unknown) as AlertingAuthorizationClientFactory, }; + const fakeRequest = ({ app: {}, headers: {}, @@ -82,8 +88,10 @@ test('creates an alerts client with proper constructor arguments when security i factory.initialize({ securityPluginSetup, securityPluginStart, ...alertsClientFactoryParams }); const request = KibanaRequest.from(fakeRequest); - const { AlertsAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger'); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); + alertingAuthorizationClientFactory.create.mockReturnValue( + (alertsAuthorization as unknown) as AlertingAuthorization + ); const logger = { log: jest.fn(), @@ -97,18 +105,9 @@ test('creates an alerts client with proper constructor arguments when security i includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }); - const { AlertsAuthorization } = jest.requireMock('./authorization/alerts_authorization'); - expect(AlertsAuthorization).toHaveBeenCalledWith({ - request, - authorization: securityPluginStart.authz, - alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry, - features: alertsClientFactoryParams.features, - auditLogger: expect.any(AlertsAuthorizationAuditLogger), - getSpace: expect.any(Function), - }); - - expect(AlertsAuthorizationAuditLogger).toHaveBeenCalledWith(logger); - expect(securityPluginSetup.audit.getLogger).toHaveBeenCalledWith(ALERTS_FEATURE_ID); + expect(alertingAuthorizationClientFactory.create).toHaveBeenCalledWith(request, [ + ALERTS_FEATURE_ID, + ]); expect(alertsClientFactoryParams.actions.getActionsAuthorizationWithRequest).toHaveBeenCalledWith( request @@ -116,7 +115,7 @@ test('creates an alerts client with proper constructor arguments when security i expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({ unsecuredSavedObjectsClient: savedObjectsClient, - authorization: expect.any(AlertsAuthorization), + authorization: alertsAuthorization, actionsAuthorization, logger: alertsClientFactoryParams.logger, taskManager: alertsClientFactoryParams.taskManager, @@ -138,6 +137,9 @@ test('creates an alerts client with proper constructor arguments', async () => { const request = KibanaRequest.from(fakeRequest); savedObjectsService.getScopedClient.mockReturnValue(savedObjectsClient); + alertingAuthorizationClientFactory.create.mockReturnValue( + (alertsAuthorization as unknown) as AlertingAuthorization + ); factory.create(request, savedObjectsService); @@ -146,20 +148,13 @@ test('creates an alerts client with proper constructor arguments', async () => { includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }); - const { AlertsAuthorization } = jest.requireMock('./authorization/alerts_authorization'); - const { AlertsAuthorizationAuditLogger } = jest.requireMock('./authorization/audit_logger'); - expect(AlertsAuthorization).toHaveBeenCalledWith({ - request, - authorization: undefined, - alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry, - features: alertsClientFactoryParams.features, - auditLogger: expect.any(AlertsAuthorizationAuditLogger), - getSpace: expect.any(Function), - }); + expect(alertingAuthorizationClientFactory.create).toHaveBeenCalledWith(request, [ + ALERTS_FEATURE_ID, + ]); expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({ unsecuredSavedObjectsClient: savedObjectsClient, - authorization: expect.any(AlertsAuthorization), + authorization: alertsAuthorization, actionsAuthorization, logger: alertsClientFactoryParams.logger, taskManager: alertsClientFactoryParams.taskManager, diff --git a/x-pack/plugins/alerting/server/alerts_client_factory.ts b/x-pack/plugins/alerting/server/alerts_client_factory.ts index 05e50346f56cf..3bb014bf9fd07 100644 --- a/x-pack/plugins/alerting/server/alerts_client_factory.ts +++ b/x-pack/plugins/alerting/server/alerts_client_factory.ts @@ -13,17 +13,13 @@ import { } from 'src/core/server'; import { PluginStartContract as ActionsPluginStartContract } from '../../actions/server'; import { AlertsClient } from './alerts_client'; -import { ALERTS_FEATURE_ID } from '../common'; import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; import { SecurityPluginSetup, SecurityPluginStart } from '../../security/server'; import { EncryptedSavedObjectsClient } from '../../encrypted_saved_objects/server'; import { TaskManagerStartContract } from '../../task_manager/server'; -import { PluginStartContract as FeaturesPluginStart } from '../../features/server'; -import { AlertsAuthorization } from './authorization/alerts_authorization'; -import { AlertsAuthorizationAuditLogger } from './authorization/audit_logger'; -import { Space } from '../../spaces/server'; import { IEventLogClientService } from '../../../plugins/event_log/server'; - +import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; +import { ALERTS_FEATURE_ID } from '../common'; export interface AlertsClientFactoryOpts { logger: Logger; taskManager: TaskManagerStartContract; @@ -31,13 +27,12 @@ export interface AlertsClientFactoryOpts { securityPluginSetup?: SecurityPluginSetup; securityPluginStart?: SecurityPluginStart; getSpaceId: (request: KibanaRequest) => string | undefined; - getSpace: (request: KibanaRequest) => Promise; spaceIdToNamespace: SpaceIdToNamespaceFunction; encryptedSavedObjectsClient: EncryptedSavedObjectsClient; actions: ActionsPluginStartContract; - features: FeaturesPluginStart; eventLog: IEventLogClientService; kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; + authorization: AlertingAuthorizationClientFactory; } export class AlertsClientFactory { @@ -48,13 +43,12 @@ export class AlertsClientFactory { private securityPluginSetup?: SecurityPluginSetup; private securityPluginStart?: SecurityPluginStart; private getSpaceId!: (request: KibanaRequest) => string | undefined; - private getSpace!: (request: KibanaRequest) => Promise; private spaceIdToNamespace!: SpaceIdToNamespaceFunction; private encryptedSavedObjectsClient!: EncryptedSavedObjectsClient; private actions!: ActionsPluginStartContract; - private features!: FeaturesPluginStart; private eventLog!: IEventLogClientService; private kibanaVersion!: PluginInitializerContext['env']['packageInfo']['version']; + private authorization!: AlertingAuthorizationClientFactory; public initialize(options: AlertsClientFactoryOpts) { if (this.isInitialized) { @@ -63,7 +57,6 @@ export class AlertsClientFactory { this.isInitialized = true; this.logger = options.logger; this.getSpaceId = options.getSpaceId; - this.getSpace = options.getSpace; this.taskManager = options.taskManager; this.alertTypeRegistry = options.alertTypeRegistry; this.securityPluginSetup = options.securityPluginSetup; @@ -71,24 +64,18 @@ export class AlertsClientFactory { this.spaceIdToNamespace = options.spaceIdToNamespace; this.encryptedSavedObjectsClient = options.encryptedSavedObjectsClient; this.actions = options.actions; - this.features = options.features; this.eventLog = options.eventLog; this.kibanaVersion = options.kibanaVersion; + this.authorization = options.authorization; } public create(request: KibanaRequest, savedObjects: SavedObjectsServiceStart): AlertsClient { - const { securityPluginSetup, securityPluginStart, actions, eventLog, features } = this; + const { securityPluginSetup, securityPluginStart, actions, eventLog } = this; const spaceId = this.getSpaceId(request); - const authorization = new AlertsAuthorization({ - authorization: securityPluginStart?.authz, - request, - getSpace: this.getSpace, - alertTypeRegistry: this.alertTypeRegistry, - features: features!, - auditLogger: new AlertsAuthorizationAuditLogger( - securityPluginSetup?.audit.getLogger(ALERTS_FEATURE_ID) - ), - }); + + if (!this.authorization) { + throw new Error('AlertingAuthorizationClientFactory is not defined'); + } return new AlertsClient({ spaceId, @@ -100,7 +87,7 @@ export class AlertsClientFactory { excludedWrappers: ['security'], includedHiddenTypes: ['alert', 'api_key_pending_invalidation'], }), - authorization, + authorization: this.authorization.create(request, [ALERTS_FEATURE_ID]), actionsAuthorization: actions.getActionsAuthorizationWithRequest(request), namespace: this.spaceIdToNamespace(spaceId), encryptedSavedObjectsClient: this.encryptedSavedObjectsClient, diff --git a/x-pack/plugins/alerting/server/authorization/__snapshots__/alerts_authorization.test.ts.snap b/x-pack/plugins/alerting/server/authorization/__snapshots__/alerts_authorization.test.ts.snap deleted file mode 100644 index f9a28dc3eb119..0000000000000 --- a/x-pack/plugins/alerting/server/authorization/__snapshots__/alerts_authorization.test.ts.snap +++ /dev/null @@ -1,316 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AlertsAuthorization getFindAuthorizationFilter creates a filter based on the privileged types 1`] = ` -Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "myAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "alerts", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myOtherApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myAppWithSubFeature", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "or", - "type": "function", - }, - ], - "function": "and", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "myOtherAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "alerts", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myOtherApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myAppWithSubFeature", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "or", - "type": "function", - }, - ], - "function": "and", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "mySecondAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "alerts", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myOtherApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myAppWithSubFeature", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "or", - "type": "function", - }, - ], - "function": "and", - "type": "function", - }, - ], - "function": "or", - "type": "function", -} -`; diff --git a/x-pack/plugins/alerting/server/authorization/__snapshots__/alerts_authorization_kuery.test.ts.snap b/x-pack/plugins/alerting/server/authorization/__snapshots__/alerts_authorization_kuery.test.ts.snap deleted file mode 100644 index de01a7b27ef05..0000000000000 --- a/x-pack/plugins/alerting/server/authorization/__snapshots__/alerts_authorization_kuery.test.ts.snap +++ /dev/null @@ -1,448 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`asFiltersByAlertTypeAndConsumer constructs filter for multiple alert types across authorized consumer 1`] = ` -Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "myAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "alerts", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myOtherApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myAppWithSubFeature", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "or", - "type": "function", - }, - ], - "function": "and", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "myOtherAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "alerts", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myOtherApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myAppWithSubFeature", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "or", - "type": "function", - }, - ], - "function": "and", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "mySecondAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "alerts", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myOtherApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myAppWithSubFeature", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "or", - "type": "function", - }, - ], - "function": "and", - "type": "function", - }, - ], - "function": "or", - "type": "function", -} -`; - -exports[`asFiltersByAlertTypeAndConsumer constructs filter for single alert type with multiple authorized consumer 1`] = ` -Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "myAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "alerts", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myOtherApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "or", - "type": "function", - }, - ], - "function": "and", - "type": "function", -} -`; - -exports[`asFiltersByAlertTypeAndConsumer constructs filter for single alert type with single authorized consumer 1`] = ` -Object { - "arguments": Array [ - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.alertTypeId", - }, - Object { - "type": "literal", - "value": "myAppAlertType", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - Object { - "arguments": Array [ - Object { - "type": "literal", - "value": "alert.attributes.consumer", - }, - Object { - "type": "literal", - "value": "myApp", - }, - Object { - "type": "literal", - "value": false, - }, - ], - "function": "is", - "type": "function", - }, - ], - "function": "and", - "type": "function", -} -`; diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization.mock.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.mock.ts new file mode 100644 index 0000000000000..4e4cd4419a5a2 --- /dev/null +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.mock.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { PublicMethodsOf } from '@kbn/utility-types'; +import { AlertingAuthorization } from './alerting_authorization'; + +type Schema = PublicMethodsOf; +export type AlertingAuthorizationMock = jest.Mocked; + +const createAlertingAuthorizationMock = () => { + const mocked: AlertingAuthorizationMock = { + ensureAuthorized: jest.fn(), + filterByRuleTypeAuthorization: jest.fn(), + getFindAuthorizationFilter: jest.fn(), + }; + return mocked; +}; + +export const alertingAuthorizationMock: { + create: () => AlertingAuthorizationMock; +} = { + create: createAlertingAuthorizationMock, +}; diff --git a/x-pack/plugins/alerting/server/authorization/alerts_authorization.test.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts similarity index 53% rename from x-pack/plugins/alerting/server/authorization/alerts_authorization.test.ts rename to x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts index bc4404b3e0a4b..1b5e712a3ee69 100644 --- a/x-pack/plugins/alerting/server/authorization/alerts_authorization.test.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.test.ts @@ -13,24 +13,37 @@ import { KibanaFeature, } from '../../../features/server'; import { featuresPluginMock } from '../../../features/server/mocks'; -import { AlertsAuthorization, WriteOperations, ReadOperations } from './alerts_authorization'; -import { alertsAuthorizationAuditLoggerMock } from './audit_logger.mock'; -import { AlertsAuthorizationAuditLogger, AuthorizationResult } from './audit_logger'; +import { + AlertingAuthorization, + WriteOperations, + ReadOperations, + AlertingAuthorizationEntity, +} from './alerting_authorization'; +import { alertingAuthorizationAuditLoggerMock } from './audit_logger.mock'; +import { AlertingAuthorizationAuditLogger, AuthorizationResult } from './audit_logger'; import uuid from 'uuid'; import { RecoveredActionGroup } from '../../common'; import { RegistryAlertType } from '../alert_type_registry'; +import { esKuery } from '../../../../../src/plugins/data/server'; +import { AlertingAuthorizationFilterType } from './alerting_authorization_kuery'; const alertTypeRegistry = alertTypeRegistryMock.create(); const features: jest.Mocked = featuresPluginMock.createStart(); const request = {} as KibanaRequest; -const auditLogger = alertsAuthorizationAuditLoggerMock.create(); -const realAuditLogger = new AlertsAuthorizationAuditLogger(); +const auditLogger = alertingAuthorizationAuditLoggerMock.create(); +const realAuditLogger = new AlertingAuthorizationAuditLogger(); const getSpace = jest.fn(); -const mockAuthorizationAction = (type: string, app: string, operation: string) => - `${type}/${app}/${operation}`; +const exemptConsumerIds: string[] = []; + +const mockAuthorizationAction = ( + type: string, + app: string, + alertingType: string, + operation: string +) => `${type}/${app}/${alertingType}/${operation}`; function mockSecurity() { const security = securityMock.createSetup(); const authorization = security.authz; @@ -161,13 +174,13 @@ const myFeatureWithoutAlerting = mockFeature('myOtherApp'); beforeEach(() => { jest.resetAllMocks(); - auditLogger.alertsAuthorizationFailure.mockImplementation((username, ...args) => + auditLogger.logAuthorizationFailure.mockImplementation((username, ...args) => realAuditLogger.getAuthorizationMessage(AuthorizationResult.Unauthorized, ...args) ); - auditLogger.alertsAuthorizationSuccess.mockImplementation((username, ...args) => + auditLogger.logAuthorizationSuccess.mockImplementation((username, ...args) => realAuditLogger.getAuthorizationMessage(AuthorizationResult.Authorized, ...args) ); - auditLogger.alertsUnscopedAuthorizationFailure.mockImplementation( + auditLogger.logUnscopedAuthorizationFailure.mockImplementation( (username, operation) => `Unauthorized ${username}/${operation}` ); alertTypeRegistry.get.mockImplementation((id) => ({ @@ -189,7 +202,7 @@ beforeEach(() => { getSpace.mockResolvedValue(undefined); }); -describe('AlertsAuthorization', () => { +describe('AlertingAuthorization', () => { describe('constructor', () => { test(`fetches the user's current space`, async () => { const space = { @@ -199,12 +212,13 @@ describe('AlertsAuthorization', () => { }; getSpace.mockResolvedValue(space); - new AlertsAuthorization({ + new AlertingAuthorization({ request, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); expect(getSpace).toHaveBeenCalledWith(request); @@ -213,15 +227,21 @@ describe('AlertsAuthorization', () => { describe('ensureAuthorized', () => { test('is a no-op when there is no authorization api', async () => { - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); - await alertAuthorization.ensureAuthorized('myType', 'myApp', WriteOperations.Create); + await alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'myApp', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); expect(alertTypeRegistry.get).toHaveBeenCalledTimes(0); }); @@ -229,33 +249,40 @@ describe('AlertsAuthorization', () => { test('is a no-op when the security license is disabled', async () => { const { authorization } = mockSecurity(); authorization.mode.useRbacForRequest.mockReturnValue(false); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, alertTypeRegistry, authorization, features, auditLogger, getSpace, + exemptConsumerIds, }); - await alertAuthorization.ensureAuthorized('myType', 'myApp', WriteOperations.Create); + await alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'myApp', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); expect(alertTypeRegistry.get).toHaveBeenCalledTimes(0); }); - test('ensures the user has privileges to execute the specified type, operation and consumer', async () => { + test('ensures the user has privileges to execute the specified rule type, operation and alerting type without consumer when producer and consumer are the same', async () => { const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType > = jest.fn(); authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); checkPrivileges.mockResolvedValueOnce({ @@ -264,41 +291,54 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [] }, }); - await alertAuthorization.ensureAuthorized('myType', 'myApp', WriteOperations.Create); + await alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'myApp', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); expect(alertTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledWith('myType', 'myApp', 'create'); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); + expect(authorization.actions.alerting.get).toHaveBeenCalledWith( + 'myType', + 'myApp', + 'rule', + 'create' + ); expect(checkPrivileges).toHaveBeenCalledWith({ - kibana: [mockAuthorizationAction('myType', 'myApp', 'create')], + kibana: [mockAuthorizationAction('myType', 'myApp', 'rule', 'create')], }); - expect(auditLogger.alertsAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myApp", - "create", - ] - `); + expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); + expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "some-user", + "myType", + 0, + "myApp", + "create", + "rule", + ] + `); }); - test('ensures the user has privileges to execute the specified type and operation without consumer when consumer is alerts', async () => { + test('ensures the user has privileges to execute the specified rule type, operation and alerting type without consumer when consumer is exempt', async () => { const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType > = jest.fn(); authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds: ['exemptConsumer'], }); checkPrivileges.mockResolvedValueOnce({ @@ -307,29 +347,47 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [] }, }); - await alertAuthorization.ensureAuthorized('myType', 'alerts', WriteOperations.Create); + await alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'exemptConsumer', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); expect(alertTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledWith('myType', 'myApp', 'create'); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); + expect(authorization.actions.alerting.get).toHaveBeenCalledWith( + 'myType', + 'exemptConsumer', + 'rule', + 'create' + ); + expect(authorization.actions.alerting.get).toHaveBeenCalledWith( + 'myType', + 'myApp', + 'rule', + 'create' + ); expect(checkPrivileges).toHaveBeenCalledWith({ - kibana: [mockAuthorizationAction('myType', 'myApp', 'create')], + kibana: [mockAuthorizationAction('myType', 'myApp', 'rule', 'create')], }); - expect(auditLogger.alertsAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "alerts", - "create", - ] - `); + expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); + expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "some-user", + "myType", + 0, + "exemptConsumer", + "create", + "rule", + ] + `); }); - test('ensures the user has privileges to execute the specified type, operation and producer when producer is different from consumer', async () => { + test('ensures the user has privileges to execute the specified rule type, operation, alerting type and producer when producer is different from consumer', async () => { const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType @@ -341,58 +399,73 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [] }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); - await alertAuthorization.ensureAuthorized('myType', 'myOtherApp', WriteOperations.Create); + await alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'myOtherApp', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }); expect(alertTypeRegistry.get).toHaveBeenCalledWith('myType'); - expect(authorization.actions.alerting.get).toHaveBeenCalledWith('myType', 'myApp', 'create'); + expect(authorization.actions.alerting.get).toHaveBeenCalledTimes(2); + expect(authorization.actions.alerting.get).toHaveBeenCalledWith( + 'myType', + 'myApp', + 'rule', + 'create' + ); expect(authorization.actions.alerting.get).toHaveBeenCalledWith( 'myType', 'myOtherApp', + 'rule', 'create' ); expect(checkPrivileges).toHaveBeenCalledWith({ kibana: [ - mockAuthorizationAction('myType', 'myOtherApp', 'create'), - mockAuthorizationAction('myType', 'myApp', 'create'), + mockAuthorizationAction('myType', 'myOtherApp', 'rule', 'create'), + mockAuthorizationAction('myType', 'myApp', 'rule', 'create'), ], }); - expect(auditLogger.alertsAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsAuthorizationFailure).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myOtherApp", - "create", - ] - `); + expect(auditLogger.logAuthorizationSuccess).toHaveBeenCalledTimes(1); + expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "some-user", + "myType", + 0, + "myOtherApp", + "create", + "rule", + ] + `); }); - test('throws if user lacks the required privieleges for the consumer', async () => { + test('throws if user lacks the required privileges for the consumer', async () => { const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType > = jest.fn(); authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); checkPrivileges.mockResolvedValueOnce({ @@ -401,11 +474,11 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction('myType', 'myOtherApp', 'rule', 'create'), authorized: false, }, { - privilege: mockAuthorizationAction('myType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myType', 'myApp', 'rule', 'create'), authorized: true, }, ], @@ -413,22 +486,28 @@ describe('AlertsAuthorization', () => { }); await expect( - alertAuthorization.ensureAuthorized('myType', 'myOtherApp', WriteOperations.Create) + alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'myOtherApp', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Rule, + }) ).rejects.toThrowErrorMatchingInlineSnapshot( - `"Unauthorized to create a \\"myType\\" alert for \\"myOtherApp\\""` + `"Unauthorized to create a \\"myType\\" rule for \\"myOtherApp\\""` ); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myOtherApp", - "create", - ] - `); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); + expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "some-user", + "myType", + 0, + "myOtherApp", + "create", + "rule", + ] + `); }); test('throws if user lacks the required privieleges for the producer', async () => { @@ -437,13 +516,14 @@ describe('AlertsAuthorization', () => { ReturnType > = jest.fn(); authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); checkPrivileges.mockResolvedValueOnce({ @@ -452,11 +532,11 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction('myType', 'myOtherApp', 'alert', 'create'), authorized: true, }, { - privilege: mockAuthorizationAction('myType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myType', 'myApp', 'alert', 'create'), authorized: false, }, ], @@ -464,22 +544,28 @@ describe('AlertsAuthorization', () => { }); await expect( - alertAuthorization.ensureAuthorized('myType', 'myOtherApp', WriteOperations.Create) + alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'myOtherApp', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Alert, + }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unauthorized to create a \\"myType\\" alert by \\"myApp\\""` ); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 1, - "myApp", - "create", - ] - `); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); + expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "some-user", + "myType", + 1, + "myApp", + "create", + "alert", + ] + `); }); test('throws if user lacks the required privieleges for both consumer and producer', async () => { @@ -488,13 +574,14 @@ describe('AlertsAuthorization', () => { ReturnType > = jest.fn(); authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); checkPrivileges.mockResolvedValueOnce({ @@ -503,11 +590,11 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction('myType', 'myOtherApp', 'alert', 'create'), authorized: false, }, { - privilege: mockAuthorizationAction('myType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myType', 'myApp', 'alert', 'create'), authorized: false, }, ], @@ -515,22 +602,28 @@ describe('AlertsAuthorization', () => { }); await expect( - alertAuthorization.ensureAuthorized('myType', 'myOtherApp', WriteOperations.Create) + alertAuthorization.ensureAuthorized({ + ruleTypeId: 'myType', + consumer: 'myOtherApp', + operation: WriteOperations.Create, + entity: AlertingAuthorizationEntity.Alert, + }) ).rejects.toThrowErrorMatchingInlineSnapshot( `"Unauthorized to create a \\"myType\\" alert for \\"myOtherApp\\""` ); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myType", - 0, - "myOtherApp", - "create", - ] - `); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); + expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "some-user", + "myType", + 0, + "myOtherApp", + "create", + "alert", + ] + `); }); }); @@ -571,39 +664,56 @@ describe('AlertsAuthorization', () => { const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType, mySecondAppAlertType]); test('omits filter when there is no authorization api', async () => { - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); const { filter, - ensureAlertTypeIsAuthorized, - } = await alertAuthorization.getFindAuthorizationFilter(); + ensureRuleTypeIsAuthorized, + } = await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'ruleId', + consumer: 'consumer', + }, + }); - expect(() => ensureAlertTypeIsAuthorized('someMadeUpType', 'myApp')).not.toThrow(); + expect(() => ensureRuleTypeIsAuthorized('someMadeUpType', 'myApp', 'rule')).not.toThrow(); expect(filter).toEqual(undefined); }); test('ensureAlertTypeIsAuthorized is no-op when there is no authorization api', async () => { - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); - const { ensureAlertTypeIsAuthorized } = await alertAuthorization.getFindAuthorizationFilter(); + const { ensureRuleTypeIsAuthorized } = await alertAuthorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'ruleId', + consumer: 'consumer', + }, + } + ); - ensureAlertTypeIsAuthorized('someMadeUpType', 'myApp'); + ensureRuleTypeIsAuthorized('someMadeUpType', 'myApp', 'rule'); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationFailure).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); }); test('creates a filter based on the privileged types', async () => { @@ -618,29 +728,34 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [] }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); - // TODO: once issue https://github.com/elastic/kibana/issues/89473 is - // resolved, we can start using this code again, instead of toMatchSnapshot(): - // - // expect((await alertAuthorization.getFindAuthorizationFilter()).filter).toEqual( - // esKuery.fromKueryExpression( - // `((alert.attributes.alertTypeId:myAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (alert.attributes.alertTypeId:myOtherAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (alert.attributes.alertTypeId:mySecondAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)))` - // ) - // ); - - // This code is the replacement code for above - expect((await alertAuthorization.getFindAuthorizationFilter()).filter).toMatchSnapshot(); + expect( + ( + await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule.id', + consumer: 'consumer-field', + }, + }) + ).filter + ).toEqual( + esKuery.fromKueryExpression( + `((path.to.rule.id:myAppAlertType and consumer-field:(myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule.id:myOtherAppAlertType and consumer-field:(myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule.id:mySecondAppAlertType and consumer-field:(myApp or myOtherApp or myAppWithSubFeature)))` + ) + ); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); }); test('creates an `ensureAlertTypeIsAuthorized` function which throws if type is unauthorized', async () => { @@ -655,53 +770,69 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'find'), + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myOtherApp', 'find'), + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'alert', + 'find' + ), authorized: false, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'find'), + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'alert', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'find'), + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'alert', 'find'), authorized: false, }, ], }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); - const { ensureAlertTypeIsAuthorized } = await alertAuthorization.getFindAuthorizationFilter(); + const { ensureRuleTypeIsAuthorized } = await alertAuthorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Alert, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'ruleId', + consumer: 'consumer', + }, + } + ); expect(() => { - ensureAlertTypeIsAuthorized('myAppAlertType', 'myOtherApp'); + ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'alert'); }).toThrowErrorMatchingInlineSnapshot( `"Unauthorized to find a \\"myAppAlertType\\" alert for \\"myOtherApp\\""` ); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationFailure).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", - "myAppAlertType", - 0, - "myOtherApp", - "find", - ] - `); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationFailure).toHaveBeenCalledTimes(1); + expect(auditLogger.logAuthorizationFailure.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "some-user", + "myAppAlertType", + 0, + "myOtherApp", + "find", + "alert", + ] + `); }); test('creates an `ensureAlertTypeIsAuthorized` function which is no-op if type is authorized', async () => { @@ -716,42 +847,57 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'find'), + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'rule', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myOtherApp', 'find'), + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'rule', + 'find' + ), authorized: false, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'find'), + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'rule', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'find'), + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'rule', 'find'), authorized: true, }, ], }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); - const { ensureAlertTypeIsAuthorized } = await alertAuthorization.getFindAuthorizationFilter(); + const { ensureRuleTypeIsAuthorized } = await alertAuthorization.getFindAuthorizationFilter( + AlertingAuthorizationEntity.Rule, + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'ruleId', + consumer: 'consumer', + }, + } + ); expect(() => { - ensureAlertTypeIsAuthorized('myAppAlertType', 'myOtherApp'); + ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'rule'); }).not.toThrow(); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationFailure).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); }); test('creates an `logSuccessfulAuthorization` function which logs every authorized type', async () => { @@ -766,76 +912,94 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'find'), + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'rule', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myOtherApp', 'find'), + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'rule', + 'find' + ), authorized: false, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'find'), + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'rule', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'find'), + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'rule', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('mySecondAppAlertType', 'myApp', 'find'), + privilege: mockAuthorizationAction('mySecondAppAlertType', 'myApp', 'rule', 'find'), authorized: true, }, { - privilege: mockAuthorizationAction('mySecondAppAlertType', 'myOtherApp', 'find'), + privilege: mockAuthorizationAction( + 'mySecondAppAlertType', + 'myOtherApp', + 'rule', + 'find' + ), authorized: true, }, ], }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); const { - ensureAlertTypeIsAuthorized, + ensureRuleTypeIsAuthorized, logSuccessfulAuthorization, - } = await alertAuthorization.getFindAuthorizationFilter(); + } = await alertAuthorization.getFindAuthorizationFilter(AlertingAuthorizationEntity.Rule, { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'ruleId', + consumer: 'consumer', + }, + }); expect(() => { - ensureAlertTypeIsAuthorized('myAppAlertType', 'myOtherApp'); - ensureAlertTypeIsAuthorized('mySecondAppAlertType', 'myOtherApp'); - ensureAlertTypeIsAuthorized('myAppAlertType', 'myOtherApp'); + ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'rule'); + ensureRuleTypeIsAuthorized('mySecondAppAlertType', 'myOtherApp', 'rule'); + ensureRuleTypeIsAuthorized('myAppAlertType', 'myOtherApp', 'rule'); }).not.toThrow(); - expect(auditLogger.alertsAuthorizationSuccess).not.toHaveBeenCalled(); - expect(auditLogger.alertsAuthorizationFailure).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationSuccess).not.toHaveBeenCalled(); + expect(auditLogger.logAuthorizationFailure).not.toHaveBeenCalled(); logSuccessfulAuthorization(); - expect(auditLogger.alertsBulkAuthorizationSuccess).toHaveBeenCalledTimes(1); - expect(auditLogger.alertsBulkAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - "some-user", + expect(auditLogger.logBulkAuthorizationSuccess).toHaveBeenCalledTimes(1); + expect(auditLogger.logBulkAuthorizationSuccess.mock.calls[0]).toMatchInlineSnapshot(` Array [ + "some-user", Array [ - "myAppAlertType", - "myOtherApp", + Array [ + "myAppAlertType", + "myOtherApp", + ], + Array [ + "mySecondAppAlertType", + "myOtherApp", + ], ], - Array [ - "mySecondAppAlertType", - "myOtherApp", - ], - ], - 0, - "find", - ] - `); + 0, + "find", + "rule", + ] + `); }); }); @@ -865,19 +1029,21 @@ describe('AlertsAuthorization', () => { const setOfAlertTypes = new Set([myAppAlertType, myOtherAppAlertType]); test('augments a list of types with all features when there is no authorization api', async () => { - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); await expect( - alertAuthorization.filterByAlertTypeAuthorization( + alertAuthorization.filterByRuleTypeAuthorization( new Set([myAppAlertType, myOtherAppAlertType]), - [WriteOperations.Create] + [WriteOperations.Create], + AlertingAuthorizationEntity.Rule ) ).resolves.toMatchInlineSnapshot(` Set { @@ -885,10 +1051,6 @@ describe('AlertsAuthorization', () => { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { - "all": true, - "read": true, - }, "myApp": Object { "all": true, "read": true, @@ -917,10 +1079,6 @@ describe('AlertsAuthorization', () => { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { - "all": true, - "read": true, - }, "myApp": Object { "all": true, "read": true, @@ -949,51 +1107,22 @@ describe('AlertsAuthorization', () => { `); }); - test('augments a list of types with consumers under which the operation is authorized', async () => { - const { authorization } = mockSecurity(); - const checkPrivileges: jest.MockedFunction< - ReturnType - > = jest.fn(); - authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); - checkPrivileges.mockResolvedValueOnce({ - username: 'some-user', - hasAllRequested: false, - privileges: { - kibana: [ - { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'create'), - authorized: true, - }, - { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myOtherApp', 'create'), - authorized: false, - }, - { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'create'), - authorized: true, - }, - { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'create'), - authorized: true, - }, - ], - }, - }); - - const alertAuthorization = new AlertsAuthorization({ + test('augments a list of types with all features and exempt consumer ids when there is no authorization api', async () => { + const alertAuthorization = new AlertingAuthorization({ request, - authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds: ['exemptConsumerA', 'exemptConsumerB'], }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); await expect( - alertAuthorization.filterByAlertTypeAuthorization( + alertAuthorization.filterByRuleTypeAuthorization( new Set([myAppAlertType, myOtherAppAlertType]), - [WriteOperations.Create] + [WriteOperations.Create], + AlertingAuthorizationEntity.Rule ) ).resolves.toMatchInlineSnapshot(` Set { @@ -1001,17 +1130,33 @@ describe('AlertsAuthorization', () => { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { + "exemptConsumerA": Object { + "all": true, + "read": true, + }, + "exemptConsumerB": Object { + "all": true, + "read": true, + }, "myApp": Object { "all": true, "read": true, }, + "myAppWithSubFeature": Object { + "all": true, + "read": true, + }, + "myOtherApp": Object { + "all": true, + "read": true, + }, }, "defaultActionGroupId": "default", "enabledInLicense": true, - "id": "myOtherAppAlertType", + "id": "myAppAlertType", "minimumLicenseRequired": "basic", - "name": "myOtherAppAlertType", - "producer": "myOtherApp", + "name": "myAppAlertType", + "producer": "myApp", "recoveryActionGroup": Object { "id": "recovered", "name": "Recovered", @@ -1021,7 +1166,11 @@ describe('AlertsAuthorization', () => { "actionGroups": Array [], "actionVariables": undefined, "authorizedConsumers": Object { - "alerts": Object { + "exemptConsumerA": Object { + "all": true, + "read": true, + }, + "exemptConsumerB": Object { "all": true, "read": true, }, @@ -1029,6 +1178,10 @@ describe('AlertsAuthorization', () => { "all": true, "read": true, }, + "myAppWithSubFeature": Object { + "all": true, + "read": true, + }, "myOtherApp": Object { "all": true, "read": true, @@ -1036,10 +1189,10 @@ describe('AlertsAuthorization', () => { }, "defaultActionGroupId": "default", "enabledInLicense": true, - "id": "myAppAlertType", + "id": "myOtherAppAlertType", "minimumLicenseRequired": "basic", - "name": "myAppAlertType", - "producer": "myApp", + "name": "myOtherAppAlertType", + "producer": "myOtherApp", "recoveryActionGroup": Object { "id": "recovered", "name": "Recovered", @@ -1049,7 +1202,7 @@ describe('AlertsAuthorization', () => { `); }); - test('authorizes user under the Alerts consumer when they are authorized by the producer', async () => { + test('augments a list of types with consumers under which the operation is authorized', async () => { const { authorization } = mockSecurity(); const checkPrivileges: jest.MockedFunction< ReturnType @@ -1061,59 +1214,272 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'rule', 'create'), authorized: true, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'rule', + 'create' + ), authorized: false, }, + { + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'rule', 'create'), + authorized: true, + }, ], }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); await expect( - alertAuthorization.filterByAlertTypeAuthorization(new Set([myAppAlertType]), [ - WriteOperations.Create, - ]) + alertAuthorization.filterByRuleTypeAuthorization( + new Set([myAppAlertType, myOtherAppAlertType]), + [WriteOperations.Create], + AlertingAuthorizationEntity.Rule + ) ).resolves.toMatchInlineSnapshot(` - Set { - Object { - "actionGroups": Array [], - "actionVariables": undefined, - "authorizedConsumers": Object { - "alerts": Object { - "all": true, - "read": true, + Set { + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "myApp": Object { + "all": true, + "read": true, + }, }, - "myApp": Object { - "all": true, - "read": true, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myOtherAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myOtherAppAlertType", + "producer": "myOtherApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", }, }, - "defaultActionGroupId": "default", - "enabledInLicense": true, - "id": "myAppAlertType", - "minimumLicenseRequired": "basic", - "name": "myAppAlertType", - "producer": "myApp", - "recoveryActionGroup": Object { - "id": "recovered", - "name": "Recovered", + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "myApp": Object { + "all": true, + "read": true, + }, + "myOtherApp": Object { + "all": true, + "read": true, + }, + }, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myAppAlertType", + "producer": "myApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", + }, }, - }, - } - `); + } + `); + }); + + test('augments a list of types with consumers and exempt consumer ids under which the operation is authorized', async () => { + const { authorization } = mockSecurity(); + const checkPrivileges: jest.MockedFunction< + ReturnType + > = jest.fn(); + authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); + checkPrivileges.mockResolvedValueOnce({ + username: 'some-user', + hasAllRequested: false, + privileges: { + kibana: [ + { + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'rule', + 'create' + ), + authorized: false, + }, + { + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'rule', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'rule', 'create'), + authorized: true, + }, + ], + }, + }); + + const alertAuthorization = new AlertingAuthorization({ + request, + authorization, + alertTypeRegistry, + features, + auditLogger, + getSpace, + exemptConsumerIds: ['exemptConsumerA'], + }); + alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); + + await expect( + alertAuthorization.filterByRuleTypeAuthorization( + new Set([myAppAlertType, myOtherAppAlertType]), + [WriteOperations.Create], + AlertingAuthorizationEntity.Rule + ) + ).resolves.toMatchInlineSnapshot(` + Set { + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "myApp": Object { + "all": true, + "read": true, + }, + }, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myOtherAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myOtherAppAlertType", + "producer": "myOtherApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", + }, + }, + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "exemptConsumerA": Object { + "all": true, + "read": true, + }, + "myApp": Object { + "all": true, + "read": true, + }, + "myOtherApp": Object { + "all": true, + "read": true, + }, + }, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myAppAlertType", + "producer": "myApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", + }, + }, + } + `); + }); + + test('authorizes user under exempt consumers when they are authorized by the producer', async () => { + const { authorization } = mockSecurity(); + const checkPrivileges: jest.MockedFunction< + ReturnType + > = jest.fn(); + authorization.checkPrivilegesDynamicallyWithRequest.mockReturnValue(checkPrivileges); + checkPrivileges.mockResolvedValueOnce({ + username: 'some-user', + hasAllRequested: false, + privileges: { + kibana: [ + { + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'alert', 'create'), + authorized: true, + }, + { + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'alert', 'create'), + authorized: false, + }, + ], + }, + }); + + const alertAuthorization = new AlertingAuthorization({ + request, + authorization, + alertTypeRegistry, + features, + auditLogger, + getSpace, + exemptConsumerIds: ['exemptConsumerA'], + }); + alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); + + await expect( + alertAuthorization.filterByRuleTypeAuthorization( + new Set([myAppAlertType]), + [WriteOperations.Create], + AlertingAuthorizationEntity.Alert + ) + ).resolves.toMatchInlineSnapshot(` + Set { + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "exemptConsumerA": Object { + "all": true, + "read": true, + }, + "myApp": Object { + "all": true, + "read": true, + }, + }, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myAppAlertType", + "producer": "myApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", + }, + }, + } + `); }); test('augments a list of types with consumers under which multiple operations are authorized', async () => { @@ -1128,116 +1494,120 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'create'), authorized: true, }, { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'alert', + 'create' + ), authorized: false, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'alert', 'create'), authorized: false, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'alert', 'create'), authorized: false, }, { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'get'), + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'get'), authorized: true, }, { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myOtherApp', 'get'), + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'alert', + 'get' + ), authorized: true, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'get'), + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'alert', 'get'), authorized: true, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'get'), + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'alert', 'get'), authorized: true, }, ], }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); await expect( - alertAuthorization.filterByAlertTypeAuthorization( + alertAuthorization.filterByRuleTypeAuthorization( new Set([myAppAlertType, myOtherAppAlertType]), - [WriteOperations.Create, ReadOperations.Get] + [WriteOperations.Create, ReadOperations.Get], + AlertingAuthorizationEntity.Alert ) ).resolves.toMatchInlineSnapshot(` - Set { - Object { - "actionGroups": Array [], - "actionVariables": undefined, - "authorizedConsumers": Object { - "alerts": Object { - "all": false, - "read": true, + Set { + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "myApp": Object { + "all": true, + "read": true, + }, + "myOtherApp": Object { + "all": false, + "read": true, + }, }, - "myApp": Object { - "all": true, - "read": true, - }, - "myOtherApp": Object { - "all": false, - "read": true, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myOtherAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myOtherAppAlertType", + "producer": "myOtherApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", }, }, - "defaultActionGroupId": "default", - "enabledInLicense": true, - "id": "myOtherAppAlertType", - "minimumLicenseRequired": "basic", - "name": "myOtherAppAlertType", - "producer": "myOtherApp", - "recoveryActionGroup": Object { - "id": "recovered", - "name": "Recovered", - }, - }, - Object { - "actionGroups": Array [], - "actionVariables": undefined, - "authorizedConsumers": Object { - "alerts": Object { - "all": false, - "read": true, + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "myApp": Object { + "all": false, + "read": true, + }, + "myOtherApp": Object { + "all": false, + "read": true, + }, }, - "myApp": Object { - "all": false, - "read": true, - }, - "myOtherApp": Object { - "all": false, - "read": true, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myAppAlertType", + "producer": "myApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", }, }, - "defaultActionGroupId": "default", - "enabledInLicense": true, - "id": "myAppAlertType", - "minimumLicenseRequired": "basic", - "name": "myAppAlertType", - "producer": "myApp", - "recoveryActionGroup": Object { - "id": "recovered", - "name": "Recovered", - }, - }, - } - `); + } + `); }); test('omits types which have no consumers under which the operation is authorized', async () => { @@ -1252,72 +1622,75 @@ describe('AlertsAuthorization', () => { privileges: { kibana: [ { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myOtherAppAlertType', 'myApp', 'alert', 'create'), authorized: true, }, { - privilege: mockAuthorizationAction('myOtherAppAlertType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction( + 'myOtherAppAlertType', + 'myOtherApp', + 'alert', + 'create' + ), authorized: true, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'create'), + privilege: mockAuthorizationAction('myAppAlertType', 'myApp', 'alert', 'create'), authorized: false, }, { - privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'create'), + privilege: mockAuthorizationAction('myAppAlertType', 'myOtherApp', 'alert', 'create'), authorized: false, }, ], }, }); - const alertAuthorization = new AlertsAuthorization({ + const alertAuthorization = new AlertingAuthorization({ request, authorization, alertTypeRegistry, features, auditLogger, getSpace, + exemptConsumerIds, }); alertTypeRegistry.list.mockReturnValue(setOfAlertTypes); await expect( - alertAuthorization.filterByAlertTypeAuthorization( + alertAuthorization.filterByRuleTypeAuthorization( new Set([myAppAlertType, myOtherAppAlertType]), - [WriteOperations.Create] + [WriteOperations.Create], + AlertingAuthorizationEntity.Alert ) ).resolves.toMatchInlineSnapshot(` - Set { - Object { - "actionGroups": Array [], - "actionVariables": undefined, - "authorizedConsumers": Object { - "alerts": Object { - "all": true, - "read": true, - }, - "myApp": Object { - "all": true, - "read": true, + Set { + Object { + "actionGroups": Array [], + "actionVariables": undefined, + "authorizedConsumers": Object { + "myApp": Object { + "all": true, + "read": true, + }, + "myOtherApp": Object { + "all": true, + "read": true, + }, }, - "myOtherApp": Object { - "all": true, - "read": true, + "defaultActionGroupId": "default", + "enabledInLicense": true, + "id": "myOtherAppAlertType", + "minimumLicenseRequired": "basic", + "name": "myOtherAppAlertType", + "producer": "myOtherApp", + "recoveryActionGroup": Object { + "id": "recovered", + "name": "Recovered", }, }, - "defaultActionGroupId": "default", - "enabledInLicense": true, - "id": "myOtherAppAlertType", - "minimumLicenseRequired": "basic", - "name": "myOtherAppAlertType", - "producer": "myOtherApp", - "recoveryActionGroup": Object { - "id": "recovered", - "name": "Recovered", - }, - }, - } - `); + } + `); }); }); }); diff --git a/x-pack/plugins/alerting/server/authorization/alerts_authorization.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts similarity index 56% rename from x-pack/plugins/alerting/server/authorization/alerts_authorization.ts rename to x-pack/plugins/alerting/server/authorization/alerting_authorization.ts index b2ead9bf1b4e6..7506accd8b88e 100644 --- a/x-pack/plugins/alerting/server/authorization/alerts_authorization.ts +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization.ts @@ -8,20 +8,28 @@ import Boom from '@hapi/boom'; import { map, mapValues, fromPairs, has } from 'lodash'; import { KibanaRequest } from 'src/core/server'; -import { ALERTS_FEATURE_ID } from '../../common'; import { AlertTypeRegistry } from '../types'; import { SecurityPluginSetup } from '../../../security/server'; import { RegistryAlertType } from '../alert_type_registry'; import { PluginStartContract as FeaturesPluginStart } from '../../../features/server'; -import { AlertsAuthorizationAuditLogger, ScopeType } from './audit_logger'; +import { AlertingAuthorizationAuditLogger, ScopeType } from './audit_logger'; import { Space } from '../../../spaces/server'; -import { asFiltersByAlertTypeAndConsumer } from './alerts_authorization_kuery'; +import { + asFiltersByRuleTypeAndConsumer, + AlertingAuthorizationFilterOpts, +} from './alerting_authorization_kuery'; import { KueryNode } from '../../../../../src/plugins/data/server'; +import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; + +export enum AlertingAuthorizationEntity { + Rule = 'rule', + Alert = 'alert', +} export enum ReadOperations { Get = 'get', - GetAlertState = 'getAlertState', - GetAlertInstanceSummary = 'getAlertInstanceSummary', + GetRuleState = 'getRuleState', + GetAlertSummary = 'getAlertSummary', Find = 'find', } @@ -34,8 +42,15 @@ export enum WriteOperations { Disable = 'disable', MuteAll = 'muteAll', UnmuteAll = 'unmuteAll', - MuteInstance = 'muteInstance', - UnmuteInstance = 'unmuteInstance', + MuteAlert = 'muteAlert', + UnmuteAlert = 'unmuteAlert', +} + +export interface EnsureAuthorizedOpts { + ruleTypeId: string; + consumer: string; + operation: ReadOperations | WriteOperations; + entity: AlertingAuthorizationEntity; } interface HasPrivileges { @@ -48,23 +63,24 @@ export interface RegistryAlertTypeWithAuth extends RegistryAlertType { } type IsAuthorizedAtProducerLevel = boolean; - export interface ConstructorOptions { alertTypeRegistry: AlertTypeRegistry; request: KibanaRequest; features: FeaturesPluginStart; getSpace: (request: KibanaRequest) => Promise; - auditLogger: AlertsAuthorizationAuditLogger; + auditLogger: AlertingAuthorizationAuditLogger; + exemptConsumerIds: string[]; authorization?: SecurityPluginSetup['authz']; } -export class AlertsAuthorization { +export class AlertingAuthorization { private readonly alertTypeRegistry: AlertTypeRegistry; private readonly request: KibanaRequest; private readonly authorization?: SecurityPluginSetup['authz']; - private readonly auditLogger: AlertsAuthorizationAuditLogger; + private readonly auditLogger: AlertingAuthorizationAuditLogger; private readonly featuresIds: Promise>; private readonly allPossibleConsumers: Promise; + private readonly exemptConsumerIds: string[]; constructor({ alertTypeRegistry, @@ -73,12 +89,18 @@ export class AlertsAuthorization { features, auditLogger, getSpace, + exemptConsumerIds, }: ConstructorOptions) { this.request = request; this.authorization = authorization; this.alertTypeRegistry = alertTypeRegistry; this.auditLogger = auditLogger; + // List of consumer ids that are exempt from privilege check. This should be used sparingly. + // An example of this is the Rules Management `consumer` as we don't want to have to + // manually authorize each rule type in the management UI. + this.exemptConsumerIds = exemptConsumerIds; + this.featuresIds = getSpace(request) .then((maybeSpace) => new Set(maybeSpace?.disabledFeatures ?? [])) .then( @@ -104,7 +126,7 @@ export class AlertsAuthorization { this.allPossibleConsumers = this.featuresIds.then((featuresIds) => featuresIds.size - ? asAuthorizedConsumers([ALERTS_FEATURE_ID, ...featuresIds], { + ? asAuthorizedConsumers([...this.exemptConsumerIds, ...featuresIds], { read: true, all: true, }) @@ -116,29 +138,29 @@ export class AlertsAuthorization { return this.authorization?.mode?.useRbacForRequest(this.request) ?? false; } - public async ensureAuthorized( - alertTypeId: string, - consumer: string, - operation: ReadOperations | WriteOperations - ) { + public async ensureAuthorized({ ruleTypeId, consumer, operation, entity }: EnsureAuthorizedOpts) { const { authorization } = this; const isAvailableConsumer = has(await this.allPossibleConsumers, consumer); if (authorization && this.shouldCheckAuthorization()) { - const alertType = this.alertTypeRegistry.get(alertTypeId); + const ruleType = this.alertTypeRegistry.get(ruleTypeId); const requiredPrivilegesByScope = { - consumer: authorization.actions.alerting.get(alertTypeId, consumer, operation), - producer: authorization.actions.alerting.get(alertTypeId, alertType.producer, operation), + consumer: authorization.actions.alerting.get(ruleTypeId, consumer, entity, operation), + producer: authorization.actions.alerting.get( + ruleTypeId, + ruleType.producer, + entity, + operation + ), }; - // We special case the Alerts Management `consumer` as we don't want to have to - // manually authorize each alert type in the management UI - const shouldAuthorizeConsumer = consumer !== ALERTS_FEATURE_ID; + // Skip authorizing consumer if it is in the list of exempt consumer ids + const shouldAuthorizeConsumer = !this.exemptConsumerIds.includes(consumer); const checkPrivileges = authorization.checkPrivilegesDynamicallyWithRequest(this.request); const { hasAllRequested, username, privileges } = await checkPrivileges({ kibana: - shouldAuthorizeConsumer && consumer !== alertType.producer + shouldAuthorizeConsumer && consumer !== ruleType.producer ? [ // check for access at consumer level requiredPrivilegesByScope.consumer, @@ -146,8 +168,8 @@ export class AlertsAuthorization { requiredPrivilegesByScope.producer, ] : [ - // skip consumer privilege checks under `alerts` as all alert types can - // be created under `alerts` if you have producer level privileges + // skip consumer privilege checks for exempt consumer ids as all rule types can + // be created for exempt consumers if user has producer level privileges requiredPrivilegesByScope.producer, ], }); @@ -161,23 +183,25 @@ export class AlertsAuthorization { * This check will ensure we don't accidentally let these through */ throw Boom.forbidden( - this.auditLogger.alertsAuthorizationFailure( + this.auditLogger.logAuthorizationFailure( username, - alertTypeId, + ruleTypeId, ScopeType.Consumer, consumer, - operation + operation, + entity ) ); } if (hasAllRequested) { - this.auditLogger.alertsAuthorizationSuccess( + this.auditLogger.logAuthorizationSuccess( username, - alertTypeId, + ruleTypeId, ScopeType.Consumer, consumer, - operation + operation, + entity ); } else { const authorizedPrivileges = map( @@ -192,84 +216,89 @@ export class AlertsAuthorization { const [unauthorizedScopeType, unauthorizedScope] = shouldAuthorizeConsumer && unauthorizedScopes.consumer ? [ScopeType.Consumer, consumer] - : [ScopeType.Producer, alertType.producer]; + : [ScopeType.Producer, ruleType.producer]; throw Boom.forbidden( - this.auditLogger.alertsAuthorizationFailure( + this.auditLogger.logAuthorizationFailure( username, - alertTypeId, + ruleTypeId, unauthorizedScopeType, unauthorizedScope, - operation + operation, + entity ) ); } } else if (!isAvailableConsumer) { throw Boom.forbidden( - this.auditLogger.alertsAuthorizationFailure( + this.auditLogger.logAuthorizationFailure( '', - alertTypeId, + ruleTypeId, ScopeType.Consumer, consumer, - operation + operation, + entity ) ); } } - public async getFindAuthorizationFilter(): Promise<{ - filter?: KueryNode; - ensureAlertTypeIsAuthorized: (alertTypeId: string, consumer: string) => void; + public async getFindAuthorizationFilter( + authorizationEntity: AlertingAuthorizationEntity, + filterOpts: AlertingAuthorizationFilterOpts + ): Promise<{ + filter?: KueryNode | JsonObject; + ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, auth: string) => void; logSuccessfulAuthorization: () => void; }> { if (this.authorization && this.shouldCheckAuthorization()) { - const { - username, - authorizedAlertTypes, - } = await this.augmentAlertTypesWithAuthorization(this.alertTypeRegistry.list(), [ - ReadOperations.Find, - ]); + const { username, authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization( + this.alertTypeRegistry.list(), + [ReadOperations.Find], + authorizationEntity + ); - if (!authorizedAlertTypes.size) { + if (!authorizedRuleTypes.size) { throw Boom.forbidden( - this.auditLogger.alertsUnscopedAuthorizationFailure(username!, 'find') + this.auditLogger.logUnscopedAuthorizationFailure(username!, 'find', authorizationEntity) ); } - const authorizedAlertTypeIdsToConsumers = new Set( - [...authorizedAlertTypes].reduce((alertTypeIdConsumerPairs, alertType) => { - for (const consumer of Object.keys(alertType.authorizedConsumers)) { - alertTypeIdConsumerPairs.push(`${alertType.id}/${consumer}`); + const authorizedRuleTypeIdsToConsumers = new Set( + [...authorizedRuleTypes].reduce((ruleTypeIdConsumerPairs, ruleType) => { + for (const consumer of Object.keys(ruleType.authorizedConsumers)) { + ruleTypeIdConsumerPairs.push(`${ruleType.id}/${consumer}/${authorizationEntity}`); } - return alertTypeIdConsumerPairs; + return ruleTypeIdConsumerPairs; }, []) ); const authorizedEntries: Map> = new Map(); return { - filter: asFiltersByAlertTypeAndConsumer(authorizedAlertTypes), - ensureAlertTypeIsAuthorized: (alertTypeId: string, consumer: string) => { - if (!authorizedAlertTypeIdsToConsumers.has(`${alertTypeId}/${consumer}`)) { + filter: asFiltersByRuleTypeAndConsumer(authorizedRuleTypes, filterOpts), + ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => { + if (!authorizedRuleTypeIdsToConsumers.has(`${ruleTypeId}/${consumer}/${authType}`)) { throw Boom.forbidden( - this.auditLogger.alertsAuthorizationFailure( + this.auditLogger.logAuthorizationFailure( username!, - alertTypeId, + ruleTypeId, ScopeType.Consumer, consumer, - 'find' + 'find', + authorizationEntity ) ); } else { - if (authorizedEntries.has(alertTypeId)) { - authorizedEntries.get(alertTypeId)!.add(consumer); + if (authorizedEntries.has(ruleTypeId)) { + authorizedEntries.get(ruleTypeId)!.add(consumer); } else { - authorizedEntries.set(alertTypeId, new Set([consumer])); + authorizedEntries.set(ruleTypeId, new Set([consumer])); } } }, logSuccessfulAuthorization: () => { if (authorizedEntries.size) { - this.auditLogger.alertsBulkAuthorizationSuccess( + this.auditLogger.logBulkAuthorizationSuccess( username!, [...authorizedEntries.entries()].reduce>( (authorizedPairs, [alertTypeId, consumers]) => { @@ -281,36 +310,40 @@ export class AlertsAuthorization { [] ), ScopeType.Consumer, - 'find' + 'find', + authorizationEntity ); } }, }; } return { - ensureAlertTypeIsAuthorized: (alertTypeId: string, consumer: string) => {}, + ensureRuleTypeIsAuthorized: (ruleTypeId: string, consumer: string, authType: string) => {}, logSuccessfulAuthorization: () => {}, }; } - public async filterByAlertTypeAuthorization( - alertTypes: Set, - operations: Array + public async filterByRuleTypeAuthorization( + ruleTypes: Set, + operations: Array, + authorizationEntity: AlertingAuthorizationEntity ): Promise> { - const { authorizedAlertTypes } = await this.augmentAlertTypesWithAuthorization( - alertTypes, - operations + const { authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization( + ruleTypes, + operations, + authorizationEntity ); - return authorizedAlertTypes; + return authorizedRuleTypes; } - private async augmentAlertTypesWithAuthorization( - alertTypes: Set, - operations: Array + private async augmentRuleTypesWithAuthorization( + ruleTypes: Set, + operations: Array, + authorizationEntity: AlertingAuthorizationEntity ): Promise<{ username?: string; hasAllRequested: boolean; - authorizedAlertTypes: Set; + authorizedRuleTypes: Set; }> { const featuresIds = await this.featuresIds; if (this.authorization && this.shouldCheckAuthorization()) { @@ -318,74 +351,76 @@ export class AlertsAuthorization { this.request ); - // add an empty `authorizedConsumers` array on each alertType - const alertTypesWithAuthorization = this.augmentWithAuthorizedConsumers(alertTypes, {}); + // add an empty `authorizedConsumers` array on each ruleType + const ruleTypesWithAuthorization = this.augmentWithAuthorizedConsumers(ruleTypes, {}); - // map from privilege to alertType which we can refer back to when analyzing the result + // map from privilege to ruleType which we can refer back to when analyzing the result // of checkPrivileges - const privilegeToAlertType = new Map< + const privilegeToRuleType = new Map< string, [RegistryAlertTypeWithAuth, string, HasPrivileges, IsAuthorizedAtProducerLevel] >(); // as we can't ask ES for the user's individual privileges we need to ask for each feature - // and alertType in the system whether this user has this privilege - for (const alertType of alertTypesWithAuthorization) { + // and ruleType in the system whether this user has this privilege + for (const ruleType of ruleTypesWithAuthorization) { for (const feature of featuresIds) { for (const operation of operations) { - privilegeToAlertType.set( - this.authorization!.actions.alerting.get(alertType.id, feature, operation), - [ - alertType, + privilegeToRuleType.set( + this.authorization!.actions.alerting.get( + ruleType.id, feature, - hasPrivilegeByOperation(operation), - alertType.producer === feature, - ] + authorizationEntity, + operation + ), + [ruleType, feature, hasPrivilegeByOperation(operation), ruleType.producer === feature] ); } } } const { username, hasAllRequested, privileges } = await checkPrivileges({ - kibana: [...privilegeToAlertType.keys()], + kibana: [...privilegeToRuleType.keys()], }); return { username, hasAllRequested, - authorizedAlertTypes: hasAllRequested + authorizedRuleTypes: hasAllRequested ? // has access to all features - this.augmentWithAuthorizedConsumers(alertTypes, await this.allPossibleConsumers) + this.augmentWithAuthorizedConsumers(ruleTypes, await this.allPossibleConsumers) : // only has some of the required privileges - privileges.kibana.reduce((authorizedAlertTypes, { authorized, privilege }) => { - if (authorized && privilegeToAlertType.has(privilege)) { + privileges.kibana.reduce((authorizedRuleTypes, { authorized, privilege }) => { + if (authorized && privilegeToRuleType.has(privilege)) { const [ - alertType, + ruleType, feature, hasPrivileges, isAuthorizedAtProducerLevel, - ] = privilegeToAlertType.get(privilege)!; - alertType.authorizedConsumers[feature] = mergeHasPrivileges( + ] = privilegeToRuleType.get(privilege)!; + ruleType.authorizedConsumers[feature] = mergeHasPrivileges( hasPrivileges, - alertType.authorizedConsumers[feature] + ruleType.authorizedConsumers[feature] ); - if (isAuthorizedAtProducerLevel) { - // granting privileges under the producer automatically authorized the Alerts Management UI as well - alertType.authorizedConsumers[ALERTS_FEATURE_ID] = mergeHasPrivileges( - hasPrivileges, - alertType.authorizedConsumers[ALERTS_FEATURE_ID] - ); + if (isAuthorizedAtProducerLevel && this.exemptConsumerIds.length > 0) { + // granting privileges under the producer automatically authorized exempt consumer IDs as well + this.exemptConsumerIds.forEach((exemptId: string) => { + ruleType.authorizedConsumers[exemptId] = mergeHasPrivileges( + hasPrivileges, + ruleType.authorizedConsumers[exemptId] + ); + }); } - authorizedAlertTypes.add(alertType); + authorizedRuleTypes.add(ruleType); } - return authorizedAlertTypes; + return authorizedRuleTypes; }, new Set()), }; } else { return { hasAllRequested: true, - authorizedAlertTypes: this.augmentWithAuthorizedConsumers( - new Set([...alertTypes].filter((alertType) => featuresIds.has(alertType.producer))), + authorizedRuleTypes: this.augmentWithAuthorizedConsumers( + new Set([...ruleTypes].filter((ruleType) => featuresIds.has(ruleType.producer))), await this.allPossibleConsumers ), }; @@ -393,12 +428,12 @@ export class AlertsAuthorization { } private augmentWithAuthorizedConsumers( - alertTypes: Set, + ruleTypes: Set, authorizedConsumers: AuthorizedConsumers ): Set { return new Set( - Array.from(alertTypes).map((alertType) => ({ - ...alertType, + Array.from(ruleTypes).map((ruleType) => ({ + ...ruleType, authorizedConsumers: { ...authorizedConsumers }, })) ); 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 new file mode 100644 index 0000000000000..8a558b6427383 --- /dev/null +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.test.ts @@ -0,0 +1,508 @@ +/* + * 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 { RecoveredActionGroup } from '../../common'; +import { + AlertingAuthorizationFilterType, + asFiltersByRuleTypeAndConsumer, + ensureFieldIsSafeForQuery, +} from './alerting_authorization_kuery'; +import { esKuery } from '../../../../../src/plugins/data/server'; + +describe('asKqlFiltersByRuleTypeAndConsumer', () => { + test('constructs KQL filter for single rule type with single authorized consumer', async () => { + expect( + asFiltersByRuleTypeAndConsumer( + new Set([ + { + actionGroups: [], + defaultActionGroupId: 'default', + recoveryActionGroup: RecoveredActionGroup, + id: 'myAppAlertType', + name: 'myAppAlertType', + producer: 'myApp', + minimumLicenseRequired: 'basic', + authorizedConsumers: { + myApp: { read: true, all: true }, + }, + enabledInLicense: true, + }, + ]), + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule.id', + consumer: 'consumer-field', + }, + } + ) + ).toEqual( + esKuery.fromKueryExpression(`((path.to.rule.id:myAppAlertType and consumer-field:(myApp)))`) + ); + }); + + test('constructs KQL filter for single rule type with multiple authorized consumers', async () => { + expect( + asFiltersByRuleTypeAndConsumer( + new Set([ + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'myAppAlertType', + name: 'myAppAlertType', + producer: 'myApp', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + }, + enabledInLicense: true, + }, + ]), + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule.id', + consumer: 'consumer-field', + }, + } + ) + ).toEqual( + esKuery.fromKueryExpression( + `((path.to.rule.id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp)))` + ) + ); + }); + + test('constructs KQL filter for multiple rule types across authorized consumer', async () => { + expect( + asFiltersByRuleTypeAndConsumer( + new Set([ + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'myAppAlertType', + name: 'myAppAlertType', + producer: 'myApp', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + myAppWithSubFeature: { read: true, all: true }, + }, + enabledInLicense: true, + }, + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'myOtherAppAlertType', + name: 'myOtherAppAlertType', + producer: 'alerts', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + myAppWithSubFeature: { read: true, all: true }, + }, + enabledInLicense: true, + }, + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'mySecondAppAlertType', + name: 'mySecondAppAlertType', + producer: 'myApp', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + myAppWithSubFeature: { read: true, all: true }, + }, + enabledInLicense: true, + }, + ]), + { + type: AlertingAuthorizationFilterType.KQL, + fieldNames: { + ruleTypeId: 'path.to.rule.id', + consumer: 'consumer-field', + }, + } + ) + ).toEqual( + esKuery.fromKueryExpression( + `((path.to.rule.id:myAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule.id:myOtherAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (path.to.rule.id:mySecondAppAlertType and consumer-field:(alerts or myApp or myOtherApp or myAppWithSubFeature)))` + ) + ); + }); +}); + +describe('asEsDslFiltersByRuleTypeAndConsumer', () => { + test('constructs ES DSL filter for single rule type with single authorized consumer', async () => { + expect( + asFiltersByRuleTypeAndConsumer( + new Set([ + { + actionGroups: [], + defaultActionGroupId: 'default', + recoveryActionGroup: RecoveredActionGroup, + id: 'myAppAlertType', + name: 'myAppAlertType', + producer: 'myApp', + minimumLicenseRequired: 'basic', + authorizedConsumers: { + myApp: { read: true, all: true }, + }, + enabledInLicense: true, + }, + ]), + { + type: AlertingAuthorizationFilterType.ESDSL, + fieldNames: { + ruleTypeId: 'path.to.rule.id', + consumer: 'consumer-field', + }, + } + ) + ).toEqual({ + bool: { + filter: [ + { + bool: { + should: [ + { + match: { + 'path.to.rule.id': 'myAppAlertType', + }, + }, + ], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + match: { + 'consumer-field': 'myApp', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }); + }); + + test('constructs ES DSL filter for single rule type with multiple authorized consumers', async () => { + expect( + asFiltersByRuleTypeAndConsumer( + new Set([ + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'myAppAlertType', + name: 'myAppAlertType', + producer: 'myApp', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + }, + enabledInLicense: true, + }, + ]), + { + type: AlertingAuthorizationFilterType.ESDSL, + fieldNames: { + ruleTypeId: 'path.to.rule.id', + consumer: 'consumer-field', + }, + } + ) + ).toEqual({ + bool: { + filter: [ + { + bool: { + should: [{ match: { 'path.to.rule.id': 'myAppAlertType' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [{ match: { 'consumer-field': 'alerts' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myApp' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myOtherApp' } }], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }); + }); + + test('constructs ES DSL filter for multiple rule types across authorized consumer', async () => { + expect( + asFiltersByRuleTypeAndConsumer( + new Set([ + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'myAppAlertType', + name: 'myAppAlertType', + producer: 'myApp', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + myAppWithSubFeature: { read: true, all: true }, + }, + enabledInLicense: true, + }, + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'myOtherAppAlertType', + name: 'myOtherAppAlertType', + producer: 'alerts', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + myAppWithSubFeature: { read: true, all: true }, + }, + enabledInLicense: true, + }, + { + actionGroups: [], + defaultActionGroupId: 'default', + minimumLicenseRequired: 'basic', + recoveryActionGroup: RecoveredActionGroup, + id: 'mySecondAppAlertType', + name: 'mySecondAppAlertType', + producer: 'myApp', + authorizedConsumers: { + alerts: { read: true, all: true }, + myApp: { read: true, all: true }, + myOtherApp: { read: true, all: true }, + myAppWithSubFeature: { read: true, all: true }, + }, + enabledInLicense: true, + }, + ]), + { + type: AlertingAuthorizationFilterType.ESDSL, + fieldNames: { + ruleTypeId: 'path.to.rule.id', + consumer: 'consumer-field', + }, + } + ) + ).toEqual({ + bool: { + should: [ + { + bool: { + filter: [ + { + bool: { + should: [{ match: { 'path.to.rule.id': 'myAppAlertType' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [{ match: { 'consumer-field': 'alerts' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myApp' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myOtherApp' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myAppWithSubFeature' } }], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + { + bool: { + filter: [ + { + bool: { + should: [{ match: { 'path.to.rule.id': 'myOtherAppAlertType' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [{ match: { 'consumer-field': 'alerts' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myApp' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myOtherApp' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myAppWithSubFeature' } }], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + { + bool: { + filter: [ + { + bool: { + should: [{ match: { 'path.to.rule.id': 'mySecondAppAlertType' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [ + { + bool: { + should: [{ match: { 'consumer-field': 'alerts' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myApp' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myOtherApp' } }], + minimum_should_match: 1, + }, + }, + { + bool: { + should: [{ match: { 'consumer-field': 'myAppWithSubFeature' } }], + minimum_should_match: 1, + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + ], + minimum_should_match: 1, + }, + }); + }); +}); + +describe('ensureFieldIsSafeForQuery', () => { + test('throws if field contains character that isnt safe in a KQL query', () => { + expect(() => ensureFieldIsSafeForQuery('id', 'alert-*')).toThrowError( + `expected id not to include invalid character: *` + ); + + expect(() => ensureFieldIsSafeForQuery('id', '<=""')).toThrowError( + `expected id not to include invalid character: <=` + ); + + expect(() => ensureFieldIsSafeForQuery('id', '>=""')).toThrowError( + `expected id not to include invalid character: >=` + ); + + expect(() => ensureFieldIsSafeForQuery('id', '1 or alertid:123')).toThrowError( + `expected id not to include whitespace and invalid character: :` + ); + + expect(() => ensureFieldIsSafeForQuery('id', ') or alertid:123')).toThrowError( + `expected id not to include whitespace and invalid characters: ), :` + ); + + expect(() => ensureFieldIsSafeForQuery('id', 'some space')).toThrowError( + `expected id not to include whitespace` + ); + }); + + test('doesnt throws if field is safe as part of a KQL query', () => { + expect(() => ensureFieldIsSafeForQuery('id', '123-0456-678')).not.toThrow(); + }); +}); diff --git a/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts new file mode 100644 index 0000000000000..eb6f1605f2ba5 --- /dev/null +++ b/x-pack/plugins/alerting/server/authorization/alerting_authorization_kuery.ts @@ -0,0 +1,80 @@ +/* + * 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 { remove } from 'lodash'; +import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; +import { nodeBuilder, EsQueryConfig } from '../../../../../src/plugins/data/common'; +import { toElasticsearchQuery } from '../../../../../src/plugins/data/common/es_query'; +import { KueryNode } from '../../../../../src/plugins/data/server'; +import { RegistryAlertTypeWithAuth } from './alerting_authorization'; + +export enum AlertingAuthorizationFilterType { + KQL = 'kql', + ESDSL = 'dsl', +} + +export interface AlertingAuthorizationFilterOpts { + type: AlertingAuthorizationFilterType; + fieldNames: AlertingAuthorizationFilterFieldNames; +} + +interface AlertingAuthorizationFilterFieldNames { + ruleTypeId: string; + consumer: string; +} + +const esQueryConfig: EsQueryConfig = { + allowLeadingWildcards: true, + dateFormatTZ: 'Zulu', + ignoreFilterIfFieldNotInIndex: false, + queryStringOptions: { analyze_wildcard: true }, +}; + +export function asFiltersByRuleTypeAndConsumer( + ruleTypes: Set, + opts: AlertingAuthorizationFilterOpts +): KueryNode | JsonObject { + const kueryNode = nodeBuilder.or( + Array.from(ruleTypes).reduce((filters, { id, authorizedConsumers }) => { + ensureFieldIsSafeForQuery('ruleTypeId', id); + filters.push( + nodeBuilder.and([ + nodeBuilder.is(opts.fieldNames.ruleTypeId, id), + nodeBuilder.or( + Object.keys(authorizedConsumers).map((consumer) => { + ensureFieldIsSafeForQuery('consumer', consumer); + return nodeBuilder.is(opts.fieldNames.consumer, consumer); + }) + ), + ]) + ); + return filters; + }, []) + ); + + if (opts.type === AlertingAuthorizationFilterType.ESDSL) { + return toElasticsearchQuery(kueryNode, undefined, esQueryConfig); + } + + return kueryNode; +} + +export function ensureFieldIsSafeForQuery(field: string, value: string): boolean { + const invalid = value.match(/([>=<\*:()]+|\s+)/g); + if (invalid) { + const whitespace = remove(invalid, (chars) => chars.trim().length === 0); + const errors = []; + if (whitespace.length) { + errors.push(`whitespace`); + } + if (invalid.length) { + errors.push(`invalid character${invalid.length > 1 ? `s` : ``}: ${invalid?.join(`, `)}`); + } + throw new Error(`expected ${field} not to include ${errors.join(' and ')}`); + } + return true; +} diff --git a/x-pack/plugins/alerting/server/authorization/alerts_authorization.mock.ts b/x-pack/plugins/alerting/server/authorization/alerts_authorization.mock.ts deleted file mode 100644 index e996258f07717..0000000000000 --- a/x-pack/plugins/alerting/server/authorization/alerts_authorization.mock.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 type { PublicMethodsOf } from '@kbn/utility-types'; -import { AlertsAuthorization } from './alerts_authorization'; - -type Schema = PublicMethodsOf; -export type AlertsAuthorizationMock = jest.Mocked; - -const createAlertsAuthorizationMock = () => { - const mocked: AlertsAuthorizationMock = { - ensureAuthorized: jest.fn(), - filterByAlertTypeAuthorization: jest.fn(), - getFindAuthorizationFilter: jest.fn(), - }; - return mocked; -}; - -export const alertsAuthorizationMock: { - create: () => AlertsAuthorizationMock; -} = { - create: createAlertsAuthorizationMock, -}; diff --git a/x-pack/plugins/alerting/server/authorization/alerts_authorization_kuery.test.ts b/x-pack/plugins/alerting/server/authorization/alerts_authorization_kuery.test.ts deleted file mode 100644 index a09c10f659837..0000000000000 --- a/x-pack/plugins/alerting/server/authorization/alerts_authorization_kuery.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -/* - * 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 { RecoveredActionGroup } from '../../common'; -import { - asFiltersByAlertTypeAndConsumer, - ensureFieldIsSafeForQuery, -} from './alerts_authorization_kuery'; - -describe('asFiltersByAlertTypeAndConsumer', () => { - test('constructs filter for single alert type with single authorized consumer', async () => { - expect( - asFiltersByAlertTypeAndConsumer( - new Set([ - { - actionGroups: [], - defaultActionGroupId: 'default', - recoveryActionGroup: RecoveredActionGroup, - id: 'myAppAlertType', - name: 'myAppAlertType', - producer: 'myApp', - minimumLicenseRequired: 'basic', - authorizedConsumers: { - myApp: { read: true, all: true }, - }, - enabledInLicense: true, - }, - ]) - ) - ).toMatchSnapshot(); - // TODO: once issue https://github.com/elastic/kibana/issues/89473 is - // resolved, we can start using this code again instead of toMatchSnapshot() - // ).toEqual( - // esKuery.fromKueryExpression( - // `((alert.attributes.alertTypeId:myAppAlertType and alert.attributes.consumer:(myApp)))` - // ) - // ); - }); - - test('constructs filter for single alert type with multiple authorized consumer', async () => { - expect( - asFiltersByAlertTypeAndConsumer( - new Set([ - { - actionGroups: [], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - recoveryActionGroup: RecoveredActionGroup, - id: 'myAppAlertType', - name: 'myAppAlertType', - producer: 'myApp', - authorizedConsumers: { - alerts: { read: true, all: true }, - myApp: { read: true, all: true }, - myOtherApp: { read: true, all: true }, - }, - enabledInLicense: true, - }, - ]) - ) - ).toMatchSnapshot(); - // TODO: once issue https://github.com/elastic/kibana/issues/89473 is - // resolved, we can start using this code again, instead of toMatchSnapshot(): - // ).toEqual( - // esKuery.fromKueryExpression( - // `((alert.attributes.alertTypeId:myAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp)))` - // ) - // ); - }); - - test('constructs filter for multiple alert types across authorized consumer', async () => { - expect( - asFiltersByAlertTypeAndConsumer( - new Set([ - { - actionGroups: [], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - recoveryActionGroup: RecoveredActionGroup, - id: 'myAppAlertType', - name: 'myAppAlertType', - producer: 'myApp', - authorizedConsumers: { - alerts: { read: true, all: true }, - myApp: { read: true, all: true }, - myOtherApp: { read: true, all: true }, - myAppWithSubFeature: { read: true, all: true }, - }, - enabledInLicense: true, - }, - { - actionGroups: [], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - recoveryActionGroup: RecoveredActionGroup, - id: 'myOtherAppAlertType', - name: 'myOtherAppAlertType', - producer: 'alerts', - authorizedConsumers: { - alerts: { read: true, all: true }, - myApp: { read: true, all: true }, - myOtherApp: { read: true, all: true }, - myAppWithSubFeature: { read: true, all: true }, - }, - enabledInLicense: true, - }, - { - actionGroups: [], - defaultActionGroupId: 'default', - minimumLicenseRequired: 'basic', - recoveryActionGroup: RecoveredActionGroup, - id: 'mySecondAppAlertType', - name: 'mySecondAppAlertType', - producer: 'myApp', - authorizedConsumers: { - alerts: { read: true, all: true }, - myApp: { read: true, all: true }, - myOtherApp: { read: true, all: true }, - myAppWithSubFeature: { read: true, all: true }, - }, - enabledInLicense: true, - }, - ]) - ) - ).toMatchSnapshot(); - // TODO: once issue https://github.com/elastic/kibana/issues/89473 is - // resolved, we can start using this code again, instead of toMatchSnapshot(): - // ).toEqual( - // esKuery.fromKueryExpression( - // `((alert.attributes.alertTypeId:myAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (alert.attributes.alertTypeId:myOtherAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)) or (alert.attributes.alertTypeId:mySecondAppAlertType and alert.attributes.consumer:(alerts or myApp or myOtherApp or myAppWithSubFeature)))` - // ) - // ); - }); -}); - -describe('ensureFieldIsSafeForQuery', () => { - test('throws if field contains character that isnt safe in a KQL query', () => { - expect(() => ensureFieldIsSafeForQuery('id', 'alert-*')).toThrowError( - `expected id not to include invalid character: *` - ); - - expect(() => ensureFieldIsSafeForQuery('id', '<=""')).toThrowError( - `expected id not to include invalid character: <=` - ); - - expect(() => ensureFieldIsSafeForQuery('id', '>=""')).toThrowError( - `expected id not to include invalid character: >=` - ); - - expect(() => ensureFieldIsSafeForQuery('id', '1 or alertid:123')).toThrowError( - `expected id not to include whitespace and invalid character: :` - ); - - expect(() => ensureFieldIsSafeForQuery('id', ') or alertid:123')).toThrowError( - `expected id not to include whitespace and invalid characters: ), :` - ); - - expect(() => ensureFieldIsSafeForQuery('id', 'some space')).toThrowError( - `expected id not to include whitespace` - ); - }); - - test('doesnt throws if field is safe as part of a KQL query', () => { - expect(() => ensureFieldIsSafeForQuery('id', '123-0456-678')).not.toThrow(); - }); -}); diff --git a/x-pack/plugins/alerting/server/authorization/alerts_authorization_kuery.ts b/x-pack/plugins/alerting/server/authorization/alerts_authorization_kuery.ts deleted file mode 100644 index b7a9efd1d964f..0000000000000 --- a/x-pack/plugins/alerting/server/authorization/alerts_authorization_kuery.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 { remove } from 'lodash'; -import { nodeBuilder } from '../../../../../src/plugins/data/common'; -import { KueryNode } from '../../../../../src/plugins/data/server'; -import { RegistryAlertTypeWithAuth } from './alerts_authorization'; - -export function asFiltersByAlertTypeAndConsumer( - alertTypes: Set -): KueryNode { - return nodeBuilder.or( - Array.from(alertTypes).reduce((filters, { id, authorizedConsumers }) => { - ensureFieldIsSafeForQuery('alertTypeId', id); - filters.push( - nodeBuilder.and([ - nodeBuilder.is(`alert.attributes.alertTypeId`, id), - nodeBuilder.or( - Object.keys(authorizedConsumers).map((consumer) => { - ensureFieldIsSafeForQuery('consumer', consumer); - return nodeBuilder.is(`alert.attributes.consumer`, consumer); - }) - ), - ]) - ); - return filters; - }, []) - ); -} - -export function ensureFieldIsSafeForQuery(field: string, value: string): boolean { - const invalid = value.match(/([>=<\*:()]+|\s+)/g); - if (invalid) { - const whitespace = remove(invalid, (chars) => chars.trim().length === 0); - const errors = []; - if (whitespace.length) { - errors.push(`whitespace`); - } - if (invalid.length) { - errors.push(`invalid character${invalid.length > 1 ? `s` : ``}: ${invalid?.join(`, `)}`); - } - throw new Error(`expected ${field} not to include ${errors.join(' and ')}`); - } - return true; -} diff --git a/x-pack/plugins/alerting/server/authorization/audit_logger.mock.ts b/x-pack/plugins/alerting/server/authorization/audit_logger.mock.ts index 01e1821512606..30fa6c9ef1e28 100644 --- a/x-pack/plugins/alerting/server/authorization/audit_logger.mock.ts +++ b/x-pack/plugins/alerting/server/authorization/audit_logger.mock.ts @@ -5,21 +5,21 @@ * 2.0. */ -import { AlertsAuthorizationAuditLogger } from './audit_logger'; +import { AlertingAuthorizationAuditLogger } from './audit_logger'; -const createAlertsAuthorizationAuditLoggerMock = () => { +const createAlertingAuthorizationAuditLoggerMock = () => { const mocked = ({ getAuthorizationMessage: jest.fn(), - alertsAuthorizationFailure: jest.fn(), - alertsUnscopedAuthorizationFailure: jest.fn(), - alertsAuthorizationSuccess: jest.fn(), - alertsBulkAuthorizationSuccess: jest.fn(), - } as unknown) as jest.Mocked; + logAuthorizationFailure: jest.fn(), + logUnscopedAuthorizationFailure: jest.fn(), + logAuthorizationSuccess: jest.fn(), + logBulkAuthorizationSuccess: jest.fn(), + } as unknown) as jest.Mocked; return mocked; }; -export const alertsAuthorizationAuditLoggerMock: { - create: () => jest.Mocked; +export const alertingAuthorizationAuditLoggerMock: { + create: () => jest.Mocked; } = { - create: createAlertsAuthorizationAuditLoggerMock, + create: createAlertingAuthorizationAuditLoggerMock, }; diff --git a/x-pack/plugins/alerting/server/authorization/audit_logger.test.ts b/x-pack/plugins/alerting/server/authorization/audit_logger.test.ts index 948c745eb2fb2..7b0ffdb193c83 100644 --- a/x-pack/plugins/alerting/server/authorization/audit_logger.test.ts +++ b/x-pack/plugins/alerting/server/authorization/audit_logger.test.ts @@ -5,7 +5,7 @@ * 2.0. */ -import { AlertsAuthorizationAuditLogger, ScopeType } from './audit_logger'; +import { AlertingAuthorizationAuditLogger, ScopeType } from './audit_logger'; const createMockAuditLogger = () => { return { @@ -15,46 +15,50 @@ const createMockAuditLogger = () => { describe(`#constructor`, () => { test('initializes a noop auditLogger if security logger is unavailable', () => { - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(undefined); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(undefined); const username = 'foo-user'; const alertTypeId = 'alert-type-id'; const scopeType = ScopeType.Consumer; const scope = 'myApp'; const operation = 'create'; + const entity = 'rule'; expect(() => { - alertsAuditLogger.alertsAuthorizationFailure( + alertsAuditLogger.logAuthorizationFailure( username, alertTypeId, scopeType, scope, - operation + operation, + entity ); - alertsAuditLogger.alertsAuthorizationSuccess( + alertsAuditLogger.logAuthorizationSuccess( username, alertTypeId, scopeType, scope, - operation + operation, + entity ); }).not.toThrow(); }); }); -describe(`#alertsUnscopedAuthorizationFailure`, () => { +describe(`#logUnscopedAuthorizationFailure`, () => { test('logs auth failure of operation', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsUnscopedAuthorizationFailure(username, operation); + alertsAuditLogger.logUnscopedAuthorizationFailure(username, operation, entity); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_unscoped_authorization_failure", - "foo-user Unauthorized to create any alert types", + "alerting_unscoped_authorization_failure", + "foo-user Unauthorized to create rules for any rule types", Object { "operation": "create", "username": "foo-user", @@ -65,27 +69,30 @@ describe(`#alertsUnscopedAuthorizationFailure`, () => { test('logs auth failure with producer scope', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const alertTypeId = 'alert-type-id'; const scopeType = ScopeType.Producer; const scope = 'myOtherApp'; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsAuthorizationFailure( + alertsAuditLogger.logAuthorizationFailure( username, alertTypeId, scopeType, scope, - operation + operation, + entity ); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_authorization_failure", - "foo-user Unauthorized to create a \\"alert-type-id\\" alert by \\"myOtherApp\\"", + "alerting_authorization_failure", + "foo-user Unauthorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"", Object { "alertTypeId": "alert-type-id", + "entity": "rule", "operation": "create", "scope": "myOtherApp", "scopeType": 1, @@ -96,30 +103,33 @@ describe(`#alertsUnscopedAuthorizationFailure`, () => { }); }); -describe(`#alertsAuthorizationFailure`, () => { +describe(`#logAuthorizationFailure`, () => { test('logs auth failure with consumer scope', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const alertTypeId = 'alert-type-id'; const scopeType = ScopeType.Consumer; const scope = 'myApp'; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsAuthorizationFailure( + alertsAuditLogger.logAuthorizationFailure( username, alertTypeId, scopeType, scope, - operation + operation, + entity ); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_authorization_failure", - "foo-user Unauthorized to create a \\"alert-type-id\\" alert for \\"myApp\\"", + "alerting_authorization_failure", + "foo-user Unauthorized to create a \\"alert-type-id\\" rule for \\"myApp\\"", Object { "alertTypeId": "alert-type-id", + "entity": "rule", "operation": "create", "scope": "myApp", "scopeType": 0, @@ -131,27 +141,30 @@ describe(`#alertsAuthorizationFailure`, () => { test('logs auth failure with producer scope', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const alertTypeId = 'alert-type-id'; const scopeType = ScopeType.Producer; const scope = 'myOtherApp'; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsAuthorizationFailure( + alertsAuditLogger.logAuthorizationFailure( username, alertTypeId, scopeType, scope, - operation + operation, + entity ); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_authorization_failure", - "foo-user Unauthorized to create a \\"alert-type-id\\" alert by \\"myOtherApp\\"", + "alerting_authorization_failure", + "foo-user Unauthorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"", Object { "alertTypeId": "alert-type-id", + "entity": "rule", "operation": "create", "scope": "myOtherApp", "scopeType": 1, @@ -162,10 +175,10 @@ describe(`#alertsAuthorizationFailure`, () => { }); }); -describe(`#alertsBulkAuthorizationSuccess`, () => { +describe(`#logBulkAuthorizationSuccess`, () => { test('logs auth success with consumer scope', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const scopeType = ScopeType.Consumer; const authorizedEntries: Array<[string, string]> = [ @@ -173,18 +186,20 @@ describe(`#alertsBulkAuthorizationSuccess`, () => { ['other-alert-type-id', 'myOtherApp'], ]; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsBulkAuthorizationSuccess( + alertsAuditLogger.logBulkAuthorizationSuccess( username, authorizedEntries, scopeType, - operation + operation, + entity ); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_authorization_success", - "foo-user Authorized to create: \\"alert-type-id\\" alert for \\"myApp\\", \\"other-alert-type-id\\" alert for \\"myOtherApp\\"", + "alerting_authorization_success", + "foo-user Authorized to create: \\"alert-type-id\\" rules for \\"myApp\\", \\"other-alert-type-id\\" rules for \\"myOtherApp\\"", Object { "authorizedEntries": Array [ Array [ @@ -196,6 +211,7 @@ describe(`#alertsBulkAuthorizationSuccess`, () => { "myOtherApp", ], ], + "entity": "rule", "operation": "create", "scopeType": 0, "username": "foo-user", @@ -206,7 +222,7 @@ describe(`#alertsBulkAuthorizationSuccess`, () => { test('logs auth success with producer scope', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const scopeType = ScopeType.Producer; const authorizedEntries: Array<[string, string]> = [ @@ -214,18 +230,20 @@ describe(`#alertsBulkAuthorizationSuccess`, () => { ['other-alert-type-id', 'myOtherApp'], ]; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsBulkAuthorizationSuccess( + alertsAuditLogger.logBulkAuthorizationSuccess( username, authorizedEntries, scopeType, - operation + operation, + entity ); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_authorization_success", - "foo-user Authorized to create: \\"alert-type-id\\" alert by \\"myApp\\", \\"other-alert-type-id\\" alert by \\"myOtherApp\\"", + "alerting_authorization_success", + "foo-user Authorized to create: \\"alert-type-id\\" rules by \\"myApp\\", \\"other-alert-type-id\\" rules by \\"myOtherApp\\"", Object { "authorizedEntries": Array [ Array [ @@ -237,6 +255,7 @@ describe(`#alertsBulkAuthorizationSuccess`, () => { "myOtherApp", ], ], + "entity": "rule", "operation": "create", "scopeType": 1, "username": "foo-user", @@ -249,27 +268,30 @@ describe(`#alertsBulkAuthorizationSuccess`, () => { describe(`#savedObjectsAuthorizationSuccess`, () => { test('logs auth success with consumer scope', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const alertTypeId = 'alert-type-id'; const scopeType = ScopeType.Consumer; const scope = 'myApp'; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsAuthorizationSuccess( + alertsAuditLogger.logAuthorizationSuccess( username, alertTypeId, scopeType, scope, - operation + operation, + entity ); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_authorization_success", - "foo-user Authorized to create a \\"alert-type-id\\" alert for \\"myApp\\"", + "alerting_authorization_success", + "foo-user Authorized to create a \\"alert-type-id\\" rule for \\"myApp\\"", Object { "alertTypeId": "alert-type-id", + "entity": "rule", "operation": "create", "scope": "myApp", "scopeType": 0, @@ -281,27 +303,30 @@ describe(`#savedObjectsAuthorizationSuccess`, () => { test('logs auth success with producer scope', () => { const auditLogger = createMockAuditLogger(); - const alertsAuditLogger = new AlertsAuthorizationAuditLogger(auditLogger); + const alertsAuditLogger = new AlertingAuthorizationAuditLogger(auditLogger); const username = 'foo-user'; const alertTypeId = 'alert-type-id'; const scopeType = ScopeType.Producer; const scope = 'myOtherApp'; const operation = 'create'; + const entity = 'rule'; - alertsAuditLogger.alertsAuthorizationSuccess( + alertsAuditLogger.logAuthorizationSuccess( username, alertTypeId, scopeType, scope, - operation + operation, + entity ); expect(auditLogger.log.mock.calls[0]).toMatchInlineSnapshot(` Array [ - "alerts_authorization_success", - "foo-user Authorized to create a \\"alert-type-id\\" alert by \\"myOtherApp\\"", + "alerting_authorization_success", + "foo-user Authorized to create a \\"alert-type-id\\" rule by \\"myOtherApp\\"", Object { "alertTypeId": "alert-type-id", + "entity": "rule", "operation": "create", "scope": "myOtherApp", "scopeType": 1, diff --git a/x-pack/plugins/alerting/server/authorization/audit_logger.ts b/x-pack/plugins/alerting/server/authorization/audit_logger.ts index 5137b3adf8b37..2a0c851733800 100644 --- a/x-pack/plugins/alerting/server/authorization/audit_logger.ts +++ b/x-pack/plugins/alerting/server/authorization/audit_logger.ts @@ -17,7 +17,7 @@ export enum AuthorizationResult { Authorized = 'Authorized', } -export class AlertsAuthorizationAuditLogger { +export class AlertingAuthorizationAuditLogger { private readonly auditLogger: LegacyAuditLogger; constructor(auditLogger: LegacyAuditLogger = { log() {} }) { @@ -29,89 +29,102 @@ export class AlertsAuthorizationAuditLogger { alertTypeId: string, scopeType: ScopeType, scope: string, - operation: string + operation: string, + entity: string ): string { - return `${authorizationResult} to ${operation} a "${alertTypeId}" alert ${ + return `${authorizationResult} to ${operation} a "${alertTypeId}" ${entity} ${ scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"` }`; } - public alertsAuthorizationFailure( + public logAuthorizationFailure( username: string, alertTypeId: string, scopeType: ScopeType, scope: string, - operation: string + operation: string, + entity: string ): string { const message = this.getAuthorizationMessage( AuthorizationResult.Unauthorized, alertTypeId, scopeType, scope, - operation + operation, + entity ); - this.auditLogger.log('alerts_authorization_failure', `${username} ${message}`, { + this.auditLogger.log('alerting_authorization_failure', `${username} ${message}`, { username, alertTypeId, scopeType, scope, operation, + entity, }); return message; } - public alertsUnscopedAuthorizationFailure(username: string, operation: string): string { - const message = `Unauthorized to ${operation} any alert types`; - this.auditLogger.log('alerts_unscoped_authorization_failure', `${username} ${message}`, { + public logUnscopedAuthorizationFailure( + username: string, + operation: string, + entity: string + ): string { + const message = `Unauthorized to ${operation} ${entity}s for any rule types`; + this.auditLogger.log('alerting_unscoped_authorization_failure', `${username} ${message}`, { username, operation, }); return message; } - public alertsAuthorizationSuccess( + public logAuthorizationSuccess( username: string, alertTypeId: string, scopeType: ScopeType, scope: string, - operation: string + operation: string, + entity: string ): string { const message = this.getAuthorizationMessage( AuthorizationResult.Authorized, alertTypeId, scopeType, scope, - operation + operation, + entity ); - this.auditLogger.log('alerts_authorization_success', `${username} ${message}`, { + this.auditLogger.log('alerting_authorization_success', `${username} ${message}`, { username, alertTypeId, scopeType, scope, operation, + entity, }); return message; } - public alertsBulkAuthorizationSuccess( + public logBulkAuthorizationSuccess( username: string, authorizedEntries: Array<[string, string]>, scopeType: ScopeType, - operation: string + operation: string, + entity: string ): string { const message = `${AuthorizationResult.Authorized} to ${operation}: ${authorizedEntries .map( ([alertTypeId, scope]) => - `"${alertTypeId}" alert ${ + `"${alertTypeId}" ${entity}s ${ scopeType === ScopeType.Consumer ? `for "${scope}"` : `by "${scope}"` }` ) .join(', ')}`; - this.auditLogger.log('alerts_authorization_success', `${username} ${message}`, { + this.auditLogger.log('alerting_authorization_success', `${username} ${message}`, { username, scopeType, authorizedEntries, operation, + entity, }); return message; } diff --git a/x-pack/plugins/alerting/server/authorization/index.ts b/x-pack/plugins/alerting/server/authorization/index.ts index 06bfe5e99f572..17f0f9f22bbe1 100644 --- a/x-pack/plugins/alerting/server/authorization/index.ts +++ b/x-pack/plugins/alerting/server/authorization/index.ts @@ -5,5 +5,5 @@ * 2.0. */ -export * from './alerts_authorization'; -export * from './alerts_authorization_kuery'; +export * from './alerting_authorization'; +export * from './alerting_authorization_kuery'; diff --git a/x-pack/plugins/alerting/server/mocks.ts b/x-pack/plugins/alerting/server/mocks.ts index df9a3c5ddf169..b87e6c8ca8f7c 100644 --- a/x-pack/plugins/alerting/server/mocks.ts +++ b/x-pack/plugins/alerting/server/mocks.ts @@ -26,6 +26,7 @@ const createSetupMock = () => { const createStartMock = () => { const mock: jest.Mocked = { listTypes: jest.fn(), + getAlertingAuthorizationWithRequest: jest.fn(), getAlertsClientWithRequest: jest.fn().mockResolvedValue(alertsClientMock.create()), getFrameworkHealth: jest.fn(), }; diff --git a/x-pack/plugins/alerting/server/plugin.test.ts b/x-pack/plugins/alerting/server/plugin.test.ts index 3e3ca4854b124..ec4b7095d67f7 100644 --- a/x-pack/plugins/alerting/server/plugin.test.ts +++ b/x-pack/plugins/alerting/server/plugin.test.ts @@ -201,6 +201,58 @@ describe('Alerting Plugin', () => { startContract.getAlertsClientWithRequest(fakeRequest); }); }); + + test(`exposes getAlertingAuthorizationWithRequest()`, async () => { + const context = coreMock.createPluginInitializerContext({ + healthCheck: { + interval: '5m', + }, + invalidateApiKeysTask: { + interval: '5m', + removalDelay: '1h', + }, + }); + const plugin = new AlertingPlugin(context); + + const encryptedSavedObjectsSetup = { + ...encryptedSavedObjectsMock.createSetup(), + canEncrypt: true, + }; + plugin.setup(coreMock.createSetup(), { + licensing: licensingMock.createSetup(), + encryptedSavedObjects: encryptedSavedObjectsSetup, + taskManager: taskManagerMock.createSetup(), + eventLog: eventLogServiceMock.create(), + actions: actionsMock.createSetup(), + statusService: statusServiceMock.createSetupContract(), + }); + + const startContract = plugin.start(coreMock.createStart(), { + actions: actionsMock.createStart(), + encryptedSavedObjects: encryptedSavedObjectsMock.createStart(), + features: mockFeatures(), + licensing: licensingMock.createStart(), + eventLog: eventLogMock.createStart(), + taskManager: taskManagerMock.createStart(), + }); + + const fakeRequest = ({ + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + getSavedObjectsClient: jest.fn(), + } as unknown) as KibanaRequest; + startContract.getAlertingAuthorizationWithRequest(fakeRequest); + }); }); }); diff --git a/x-pack/plugins/alerting/server/plugin.ts b/x-pack/plugins/alerting/server/plugin.ts index 3d3b478c6480c..990733c320dfe 100644 --- a/x-pack/plugins/alerting/server/plugin.ts +++ b/x-pack/plugins/alerting/server/plugin.ts @@ -36,7 +36,6 @@ import { SavedObjectsBulkGetObject, } from '../../../../src/core/server'; import type { AlertingRequestHandlerContext } from './types'; - import { defineRoutes } from './routes'; import { LICENSE_TYPE, LicensingPluginSetup, LicensingPluginStart } from '../../licensing/server'; import { @@ -68,6 +67,8 @@ import { } from './health'; import { AlertsConfig } from './config'; import { getHealth } from './health/get_health'; +import { AlertingAuthorizationClientFactory } from './alerting_authorization_client_factory'; +import { AlertingAuthorization } from './authorization'; export const EVENT_LOG_PROVIDER = 'alerting'; export const EVENT_LOG_ACTIONS = { @@ -104,6 +105,9 @@ export interface PluginSetupContract { export interface PluginStartContract { listTypes: AlertTypeRegistry['list']; getAlertsClientWithRequest(request: KibanaRequest): PublicMethodsOf; + getAlertingAuthorizationWithRequest( + request: KibanaRequest + ): PublicMethodsOf; getFrameworkHealth: () => Promise; } @@ -137,6 +141,7 @@ export class AlertingPlugin { private isESOCanEncrypt?: boolean; private security?: SecurityPluginSetup; private readonly alertsClientFactory: AlertsClientFactory; + private readonly alertingAuthorizationClientFactory: AlertingAuthorizationClientFactory; private readonly telemetryLogger: Logger; private readonly kibanaVersion: PluginInitializerContext['env']['packageInfo']['version']; private eventLogService?: IEventLogService; @@ -149,6 +154,7 @@ export class AlertingPlugin { this.logger = initializerContext.logger.get('plugins', 'alerting'); this.taskRunnerFactory = new TaskRunnerFactory(); this.alertsClientFactory = new AlertsClientFactory(); + this.alertingAuthorizationClientFactory = new AlertingAuthorizationClientFactory(); this.telemetryLogger = initializerContext.logger.get('usage'); this.kibanaIndexConfig = initializerContext.config.legacy.globalConfig$; this.kibanaVersion = initializerContext.env.packageInfo.version; @@ -288,6 +294,7 @@ export class AlertingPlugin { taskRunnerFactory, alertTypeRegistry, alertsClientFactory, + alertingAuthorizationClientFactory, security, licenseState, } = this; @@ -304,6 +311,16 @@ export class AlertingPlugin { : undefined; }; + alertingAuthorizationClientFactory.initialize({ + alertTypeRegistry: alertTypeRegistry!, + securityPluginSetup: security, + securityPluginStart: plugins.security, + async getSpace(request: KibanaRequest) { + return plugins.spaces?.spacesService.getActiveSpace(request); + }, + features: plugins.features, + }); + alertsClientFactory.initialize({ alertTypeRegistry: alertTypeRegistry!, logger, @@ -315,13 +332,10 @@ export class AlertingPlugin { getSpaceId(request: KibanaRequest) { return plugins.spaces?.spacesService.getSpaceId(request); }, - async getSpace(request: KibanaRequest) { - return plugins.spaces?.spacesService.getActiveSpace(request); - }, actions: plugins.actions, - features: plugins.features, eventLog: plugins.eventLog, kibanaVersion: this.kibanaVersion, + authorization: alertingAuthorizationClientFactory, }); const getAlertsClientWithRequest = (request: KibanaRequest) => { @@ -333,6 +347,10 @@ export class AlertingPlugin { return alertsClientFactory!.create(request, core.savedObjects); }; + const getAlertingAuthorizationWithRequest = (request: KibanaRequest) => { + return alertingAuthorizationClientFactory!.create(request); + }; + taskRunnerFactory.initialize({ logger, getServices: this.getServicesFactory(core.savedObjects, core.elasticsearch), @@ -362,6 +380,7 @@ export class AlertingPlugin { return { listTypes: alertTypeRegistry!.list.bind(this.alertTypeRegistry!), + getAlertingAuthorizationWithRequest, getAlertsClientWithRequest, getFrameworkHealth: async () => await getHealth(core.savedObjects.createInternalRepository(['alert'])), diff --git a/x-pack/plugins/security/server/authorization/actions/__snapshots__/alerting.test.ts.snap b/x-pack/plugins/security/server/authorization/actions/__snapshots__/alerting.test.ts.snap index afa907fe09837..107c708c4811f 100644 --- a/x-pack/plugins/security/server/authorization/actions/__snapshots__/alerting.test.ts.snap +++ b/x-pack/plugins/security/server/authorization/actions/__snapshots__/alerting.test.ts.snap @@ -1,16 +1,16 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`#get alertType of "" throws error 1`] = `"alertTypeId is required and must be a string"`; +exports[`#get alertingType of "" throws error 1`] = `"alertingEntity is required and must be a string"`; -exports[`#get alertType of {} throws error 1`] = `"alertTypeId is required and must be a string"`; +exports[`#get alertingType of {} throws error 1`] = `"alertingEntity is required and must be a string"`; -exports[`#get alertType of 1 throws error 1`] = `"alertTypeId is required and must be a string"`; +exports[`#get alertingType of 1 throws error 1`] = `"alertingEntity is required and must be a string"`; -exports[`#get alertType of null throws error 1`] = `"alertTypeId is required and must be a string"`; +exports[`#get alertingType of null throws error 1`] = `"alertingEntity is required and must be a string"`; -exports[`#get alertType of true throws error 1`] = `"alertTypeId is required and must be a string"`; +exports[`#get alertingType of true throws error 1`] = `"alertingEntity is required and must be a string"`; -exports[`#get alertType of undefined throws error 1`] = `"alertTypeId is required and must be a string"`; +exports[`#get alertingType of undefined throws error 1`] = `"alertingEntity is required and must be a string"`; exports[`#get consumer of "" throws error 1`] = `"consumer is required and must be a string"`; @@ -35,3 +35,15 @@ exports[`#get operation of null throws error 1`] = `"operation is required and m exports[`#get operation of true throws error 1`] = `"operation is required and must be a string"`; exports[`#get operation of undefined throws error 1`] = `"operation is required and must be a string"`; + +exports[`#get ruleType of "" throws error 1`] = `"ruleTypeId is required and must be a string"`; + +exports[`#get ruleType of {} throws error 1`] = `"ruleTypeId is required and must be a string"`; + +exports[`#get ruleType of 1 throws error 1`] = `"ruleTypeId is required and must be a string"`; + +exports[`#get ruleType of null throws error 1`] = `"ruleTypeId is required and must be a string"`; + +exports[`#get ruleType of true throws error 1`] = `"ruleTypeId is required and must be a string"`; + +exports[`#get ruleType of undefined throws error 1`] = `"ruleTypeId is required and must be a string"`; diff --git a/x-pack/plugins/security/server/authorization/actions/alerting.test.ts b/x-pack/plugins/security/server/authorization/actions/alerting.test.ts index 2383e4a7c583a..eff2a37eb9e25 100644 --- a/x-pack/plugins/security/server/authorization/actions/alerting.test.ts +++ b/x-pack/plugins/security/server/authorization/actions/alerting.test.ts @@ -10,11 +10,11 @@ import { AlertingActions } from './alerting'; const version = '1.0.0-zeta1'; describe('#get', () => { - [null, undefined, '', 1, true, {}].forEach((alertType: any) => { - test(`alertType of ${JSON.stringify(alertType)} throws error`, () => { + [null, undefined, '', 1, true, {}].forEach((ruleType: any) => { + test(`ruleType of ${JSON.stringify(ruleType)} throws error`, () => { const alertingActions = new AlertingActions(version); expect(() => - alertingActions.get(alertType, 'consumer', 'foo-action') + alertingActions.get(ruleType, 'consumer', 'alertingType', 'foo-action') ).toThrowErrorMatchingSnapshot(); }); }); @@ -23,7 +23,7 @@ describe('#get', () => { test(`operation of ${JSON.stringify(operation)} throws error`, () => { const alertingActions = new AlertingActions(version); expect(() => - alertingActions.get('foo-alertType', 'consumer', operation) + alertingActions.get('foo-ruleType', 'consumer', 'alertingType', operation) ).toThrowErrorMatchingSnapshot(); }); }); @@ -32,15 +32,24 @@ describe('#get', () => { test(`consumer of ${JSON.stringify(consumer)} throws error`, () => { const alertingActions = new AlertingActions(version); expect(() => - alertingActions.get('foo-alertType', consumer, 'operation') + alertingActions.get('foo-ruleType', consumer, 'alertingType', 'operation') ).toThrowErrorMatchingSnapshot(); }); }); - test('returns `alerting:${alertType}/${consumer}/${operation}`', () => { + [null, '', 1, true, undefined, {}].forEach((alertingType: any) => { + test(`alertingType of ${JSON.stringify(alertingType)} throws error`, () => { + const alertingActions = new AlertingActions(version); + expect(() => + alertingActions.get('foo-ruleType', 'consumer', alertingType, 'operation') + ).toThrowErrorMatchingSnapshot(); + }); + }); + + test('returns `alerting:${ruleType}/${consumer}/${alertingType}/${operation}`', () => { const alertingActions = new AlertingActions(version); - expect(alertingActions.get('foo-alertType', 'consumer', 'bar-operation')).toBe( - 'alerting:1.0.0-zeta1:foo-alertType/consumer/bar-operation' + expect(alertingActions.get('foo-ruleType', 'consumer', 'alertingType', 'bar-operation')).toBe( + 'alerting:1.0.0-zeta1:foo-ruleType/consumer/alertingType/bar-operation' ); }); }); diff --git a/x-pack/plugins/security/server/authorization/actions/alerting.ts b/x-pack/plugins/security/server/authorization/actions/alerting.ts index 0dd30e7f26b32..339e8801e896f 100644 --- a/x-pack/plugins/security/server/authorization/actions/alerting.ts +++ b/x-pack/plugins/security/server/authorization/actions/alerting.ts @@ -14,9 +14,14 @@ export class AlertingActions { this.prefix = `alerting:${versionNumber}:`; } - public get(alertTypeId: string, consumer: string, operation: string): string { - if (!alertTypeId || !isString(alertTypeId)) { - throw new Error('alertTypeId is required and must be a string'); + public get( + ruleTypeId: string, + consumer: string, + alertingEntity: string, + operation: string + ): string { + if (!ruleTypeId || !isString(ruleTypeId)) { + throw new Error('ruleTypeId is required and must be a string'); } if (!operation || !isString(operation)) { @@ -27,6 +32,10 @@ export class AlertingActions { throw new Error('consumer is required and must be a string'); } - return `${this.prefix}${alertTypeId}/${consumer}/${operation}`; + if (!alertingEntity || !isString(alertingEntity)) { + throw new Error('alertingEntity is required and must be a string'); + } + + return `${this.prefix}${ruleTypeId}/${consumer}/${alertingEntity}/${operation}`; } } diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts index 34bfb113ab0ea..e06e40b86e01b 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.test.ts @@ -76,10 +76,12 @@ describe(`feature_privilege_builder`, () => { expect(alertingFeaturePrivileges.getActions(privilege, feature)).toMatchInlineSnapshot(` Array [ - "alerting:1.0.0-zeta1:alert-type/my-feature/get", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary", - "alerting:1.0.0-zeta1:alert-type/my-feature/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/get", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleState", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/get", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/find", ] `); }); @@ -114,20 +116,23 @@ describe(`feature_privilege_builder`, () => { expect(alertingFeaturePrivileges.getActions(privilege, feature)).toMatchInlineSnapshot(` Array [ - "alerting:1.0.0-zeta1:alert-type/my-feature/get", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary", - "alerting:1.0.0-zeta1:alert-type/my-feature/find", - "alerting:1.0.0-zeta1:alert-type/my-feature/create", - "alerting:1.0.0-zeta1:alert-type/my-feature/delete", - "alerting:1.0.0-zeta1:alert-type/my-feature/update", - "alerting:1.0.0-zeta1:alert-type/my-feature/updateApiKey", - "alerting:1.0.0-zeta1:alert-type/my-feature/enable", - "alerting:1.0.0-zeta1:alert-type/my-feature/disable", - "alerting:1.0.0-zeta1:alert-type/my-feature/muteAll", - "alerting:1.0.0-zeta1:alert-type/my-feature/unmuteAll", - "alerting:1.0.0-zeta1:alert-type/my-feature/muteInstance", - "alerting:1.0.0-zeta1:alert-type/my-feature/unmuteInstance", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/get", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleState", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/create", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/update", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/updateApiKey", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/enable", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/disable", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAll", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAll", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAlert", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/get", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/update", ] `); }); @@ -162,24 +167,29 @@ describe(`feature_privilege_builder`, () => { expect(alertingFeaturePrivileges.getActions(privilege, feature)).toMatchInlineSnapshot(` Array [ - "alerting:1.0.0-zeta1:alert-type/my-feature/get", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:alert-type/my-feature/getAlertInstanceSummary", - "alerting:1.0.0-zeta1:alert-type/my-feature/find", - "alerting:1.0.0-zeta1:alert-type/my-feature/create", - "alerting:1.0.0-zeta1:alert-type/my-feature/delete", - "alerting:1.0.0-zeta1:alert-type/my-feature/update", - "alerting:1.0.0-zeta1:alert-type/my-feature/updateApiKey", - "alerting:1.0.0-zeta1:alert-type/my-feature/enable", - "alerting:1.0.0-zeta1:alert-type/my-feature/disable", - "alerting:1.0.0-zeta1:alert-type/my-feature/muteAll", - "alerting:1.0.0-zeta1:alert-type/my-feature/unmuteAll", - "alerting:1.0.0-zeta1:alert-type/my-feature/muteInstance", - "alerting:1.0.0-zeta1:alert-type/my-feature/unmuteInstance", - "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/get", - "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/getAlertState", - "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/getAlertInstanceSummary", - "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/get", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getRuleState", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/getAlertSummary", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/create", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/delete", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/update", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/updateApiKey", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/enable", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/disable", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAll", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAll", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/muteAlert", + "alerting:1.0.0-zeta1:alert-type/my-feature/rule/unmuteAlert", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/get", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/find", + "alerting:1.0.0-zeta1:alert-type/my-feature/alert/update", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/get", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getRuleState", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/getAlertSummary", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/rule/find", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/get", + "alerting:1.0.0-zeta1:readonly-alert-type/my-feature/alert/find", ] `); }); diff --git a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts index c813f0f935cf5..1d0a2b0e12943 100644 --- a/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts +++ b/x-pack/plugins/security/server/authorization/privileges/feature_privilege_builder/alerting.ts @@ -10,20 +10,35 @@ import { uniq } from 'lodash'; import type { FeatureKibanaPrivileges, KibanaFeature } from '../../../../../features/server'; import { BaseFeaturePrivilegeBuilder } from './feature_privilege_builder'; -const readOperations: string[] = ['get', 'getAlertState', 'getAlertInstanceSummary', 'find']; -const writeOperations: string[] = [ - 'create', - 'delete', - 'update', - 'updateApiKey', - 'enable', - 'disable', - 'muteAll', - 'unmuteAll', - 'muteInstance', - 'unmuteInstance', -]; -const allOperations: string[] = [...readOperations, ...writeOperations]; +enum AlertingType { + RULE = 'rule', + ALERT = 'alert', +} + +const readOperations: Record = { + rule: ['get', 'getRuleState', 'getAlertSummary', 'find'], + alert: ['get', 'find'], +}; + +const writeOperations: Record = { + rule: [ + 'create', + 'delete', + 'update', + 'updateApiKey', + 'enable', + 'disable', + 'muteAll', + 'unmuteAll', + 'muteAlert', + 'unmuteAlert', + ], + alert: ['update'], +}; +const allOperations: Record = { + rule: [...readOperations.rule, ...writeOperations.rule], + alert: [...readOperations.alert, ...writeOperations.alert], +}; export class FeaturePrivilegeAlertingBuilder extends BaseFeaturePrivilegeBuilder { public getActions( @@ -31,12 +46,16 @@ export class FeaturePrivilegeAlertingBuilder extends BaseFeaturePrivilegeBuilder feature: KibanaFeature ): string[] { const getAlertingPrivilege = ( - operations: string[], + operations: Record, privilegedTypes: readonly string[], consumer: string ) => - privilegedTypes.flatMap((type) => - operations.map((operation) => this.actions.alerting.get(type, consumer, operation)) + privilegedTypes.flatMap((privilegedType) => + Object.values(AlertingType).flatMap((alertingType) => + operations[alertingType].map((operation) => + this.actions.alerting.get(privilegedType, consumer, alertingType, operation) + ) + ) ); return uniq([ 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 540f8f4d1cad9..ea16351b49543 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 @@ -308,7 +308,7 @@ export function getConsumerUnauthorizedErrorMessage( alertType: string, consumer: string ) { - return `Unauthorized to ${operation} a "${alertType}" alert for "${consumer}"`; + return `Unauthorized to ${operation} a "${alertType}" rule for "${consumer}"`; } export function getProducerUnauthorizedErrorMessage( @@ -316,7 +316,7 @@ export function getProducerUnauthorizedErrorMessage( alertType: string, producer: string ) { - return `Unauthorized to ${operation} a "${alertType}" alert by "${producer}"`; + return `Unauthorized to ${operation} a "${alertType}" rule by "${producer}"`; } function getDefaultAlwaysFiringAlertData(reference: string, actionId: string) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index dda5970904f8d..3454ef5c94d9f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -47,7 +47,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: `Unauthorized to find any alert types`, + message: `Unauthorized to find rules for any rule types`, statusCode: 403, }); break; @@ -143,7 +143,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: `Unauthorized to find any alert types`, + message: `Unauthorized to find rules for any rule types`, statusCode: 403, }); break; @@ -239,7 +239,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: `Unauthorized to find any alert types`, + message: `Unauthorized to find rules for any rule types`, statusCode: 403, }); break; @@ -333,7 +333,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: `Unauthorized to find any alert types`, + message: `Unauthorized to find rules for any rule types`, statusCode: 403, }); break; @@ -410,7 +410,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: `Unauthorized to find any alert types`, + message: `Unauthorized to find rules for any rule types`, statusCode: 403, }); break; @@ -470,7 +470,7 @@ export default function createFindTests({ getService }: FtrProviderContext) { expect(response.statusCode).to.eql(403); expect(response.body).to.eql({ error: 'Forbidden', - message: `Unauthorized to find any alert types`, + message: `Unauthorized to find rules for any rule types`, statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts index 858e61154cef5..4948737e0778a 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/mute_instance.ts @@ -73,7 +73,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'muteInstance', + 'muteAlert', 'test.noop', 'alertsFixture' ), @@ -138,7 +138,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'muteInstance', + 'muteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' ), @@ -192,7 +192,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'muteInstance', + 'muteAlert', 'test.unrestricted-noop', 'alertsFixture' ), @@ -205,7 +205,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getProducerUnauthorizedErrorMessage( - 'muteInstance', + 'muteAlert', 'test.unrestricted-noop', 'alertsRestrictedFixture' ), @@ -258,7 +258,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'muteInstance', + 'muteAlert', 'test.restricted-noop', 'alerts' ), @@ -272,7 +272,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getProducerUnauthorizedErrorMessage( - 'muteInstance', + 'muteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' ), @@ -325,7 +325,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'muteInstance', + 'muteAlert', 'test.noop', 'alertsFixture' ), diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts index 53ea2b845af1f..539e7eb059107 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/rbac_legacy.ts @@ -112,7 +112,7 @@ export default function alertTests({ getService }: FtrProviderContext) { expect(failedUpdateKeyDueToAlertsPrivilegesResponse.body).to.eql({ error: 'Forbidden', message: - 'Unauthorized to updateApiKey a "test.always-firing" alert for "alertsFixture"', + 'Unauthorized to updateApiKey a "test.always-firing" rule for "alertsFixture"', statusCode: 403, }); break; diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts index c19da8ba2008c..9c045db888391 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/unmute_instance.ts @@ -78,7 +78,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'unmuteInstance', + 'unmuteAlert', 'test.noop', 'alertsFixture' ), @@ -148,7 +148,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'unmuteInstance', + 'unmuteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' ), @@ -207,7 +207,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'unmuteInstance', + 'unmuteAlert', 'test.unrestricted-noop', 'alertsFixture' ), @@ -220,7 +220,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getProducerUnauthorizedErrorMessage( - 'unmuteInstance', + 'unmuteAlert', 'test.unrestricted-noop', 'alertsRestrictedFixture' ), @@ -278,7 +278,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getConsumerUnauthorizedErrorMessage( - 'unmuteInstance', + 'unmuteAlert', 'test.restricted-noop', 'alerts' ), @@ -292,7 +292,7 @@ export default function createMuteAlertInstanceTests({ getService }: FtrProvider expect(response.body).to.eql({ error: 'Forbidden', message: getProducerUnauthorizedErrorMessage( - 'unmuteInstance', + 'unmuteAlert', 'test.restricted-noop', 'alertsRestrictedFixture' ),