mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Alerting] Refactor alerts authorization client (#99078)
* WIP - creating alerting authorization client factory and exposing authorization client on plugin start contract * Updating alerting feature privilege builder to handle different alerting types * Passing in alerting authorization type to AlertingActions class string builder * Passing in authorization type in each function call * Passing in exempt consumer ids. Adding authorization type to audit logger * Changing alertType to ruleType * Changing alertType to ruleType * Updating unit tests * Updating unit tests * Passing field names into authorization query builder. Adding kql/es dsl option * Converting to es query if requested * Fixing functional tests * Removing ability to specify feature privilege name in constructor * Fixing some types and tests * Consolidating alerting authorization kuery filter options * Cleanup and tests * Cleanup and tests * Throwing error when AlertingAuthorizationClientFactory is not defined * Renaming authorizationType to entity * Renaming AlertsAuthorization to AlertingAuthorization * Fixing unit tests * Updating privilege string terminology * Updating privilege string terminology Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
a105c7a8b1
commit
0f0cee2510
50 changed files with 2461 additions and 1953 deletions
|
@ -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<PublicMethodsOf<AlertingAuthorizationClientFactory>> = {
|
||||
create: jest.fn(),
|
||||
initialize: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const alertingAuthorizationClientFactoryMock = {
|
||||
createFactory: creatAlertingAuthorizationClientFactoryMock,
|
||||
};
|
|
@ -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<AlertingAuthorizationClientFactoryOpts> = {
|
||||
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();
|
||||
});
|
|
@ -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<Space | undefined>;
|
||||
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<Space | undefined>;
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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<string | null>;
|
||||
|
@ -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<CreateAPIKeyResult>;
|
||||
private readonly getActionsClient: () => Promise<ActionsClient>;
|
||||
|
@ -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<SanitizedAlert<Params>> {
|
||||
const result = await this.unsecuredSavedObjectsClient.get<RawAlert>('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<AlertTaskState | void> {
|
||||
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<AlertInstanceSummary> {
|
||||
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<FindResult<Params>> {
|
||||
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) {
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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 [
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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(),
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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<RegistryAlertTypeWithAuth>([
|
||||
{ ...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);
|
||||
});
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<ConstructorOptions> = {
|
|||
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',
|
||||
|
|
|
@ -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<AlertsClientFactoryOpts> = {
|
||||
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,
|
||||
|
|
|
@ -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<Space | undefined>;
|
||||
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<Space | undefined>;
|
||||
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,
|
||||
|
|
|
@ -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",
|
||||
}
|
||||
`;
|
|
@ -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",
|
||||
}
|
||||
`;
|
|
@ -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<AlertingAuthorization>;
|
||||
export type AlertingAuthorizationMock = jest.Mocked<Schema>;
|
||||
|
||||
const createAlertingAuthorizationMock = () => {
|
||||
const mocked: AlertingAuthorizationMock = {
|
||||
ensureAuthorized: jest.fn(),
|
||||
filterByRuleTypeAuthorization: jest.fn(),
|
||||
getFindAuthorizationFilter: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const alertingAuthorizationMock: {
|
||||
create: () => AlertingAuthorizationMock;
|
||||
} = {
|
||||
create: createAlertingAuthorizationMock,
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -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<Space | undefined>;
|
||||
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<Set<string>>;
|
||||
private readonly allPossibleConsumers: Promise<AuthorizedConsumers>;
|
||||
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<string>(
|
||||
[...authorizedAlertTypes].reduce<string[]>((alertTypeIdConsumerPairs, alertType) => {
|
||||
for (const consumer of Object.keys(alertType.authorizedConsumers)) {
|
||||
alertTypeIdConsumerPairs.push(`${alertType.id}/${consumer}`);
|
||||
const authorizedRuleTypeIdsToConsumers = new Set<string>(
|
||||
[...authorizedRuleTypes].reduce<string[]>((ruleTypeIdConsumerPairs, ruleType) => {
|
||||
for (const consumer of Object.keys(ruleType.authorizedConsumers)) {
|
||||
ruleTypeIdConsumerPairs.push(`${ruleType.id}/${consumer}/${authorizationEntity}`);
|
||||
}
|
||||
return alertTypeIdConsumerPairs;
|
||||
return ruleTypeIdConsumerPairs;
|
||||
}, [])
|
||||
);
|
||||
|
||||
const authorizedEntries: Map<string, Set<string>> = 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<Array<[string, string]>>(
|
||||
(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<RegistryAlertType>,
|
||||
operations: Array<ReadOperations | WriteOperations>
|
||||
public async filterByRuleTypeAuthorization(
|
||||
ruleTypes: Set<RegistryAlertType>,
|
||||
operations: Array<ReadOperations | WriteOperations>,
|
||||
authorizationEntity: AlertingAuthorizationEntity
|
||||
): Promise<Set<RegistryAlertTypeWithAuth>> {
|
||||
const { authorizedAlertTypes } = await this.augmentAlertTypesWithAuthorization(
|
||||
alertTypes,
|
||||
operations
|
||||
const { authorizedRuleTypes } = await this.augmentRuleTypesWithAuthorization(
|
||||
ruleTypes,
|
||||
operations,
|
||||
authorizationEntity
|
||||
);
|
||||
return authorizedAlertTypes;
|
||||
return authorizedRuleTypes;
|
||||
}
|
||||
|
||||
private async augmentAlertTypesWithAuthorization(
|
||||
alertTypes: Set<RegistryAlertType>,
|
||||
operations: Array<ReadOperations | WriteOperations>
|
||||
private async augmentRuleTypesWithAuthorization(
|
||||
ruleTypes: Set<RegistryAlertType>,
|
||||
operations: Array<ReadOperations | WriteOperations>,
|
||||
authorizationEntity: AlertingAuthorizationEntity
|
||||
): Promise<{
|
||||
username?: string;
|
||||
hasAllRequested: boolean;
|
||||
authorizedAlertTypes: Set<RegistryAlertTypeWithAuth>;
|
||||
authorizedRuleTypes: Set<RegistryAlertTypeWithAuth>;
|
||||
}> {
|
||||
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<RegistryAlertTypeWithAuth>()),
|
||||
};
|
||||
} 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<RegistryAlertType>,
|
||||
ruleTypes: Set<RegistryAlertType>,
|
||||
authorizedConsumers: AuthorizedConsumers
|
||||
): Set<RegistryAlertTypeWithAuth> {
|
||||
return new Set(
|
||||
Array.from(alertTypes).map((alertType) => ({
|
||||
...alertType,
|
||||
Array.from(ruleTypes).map((ruleType) => ({
|
||||
...ruleType,
|
||||
authorizedConsumers: { ...authorizedConsumers },
|
||||
}))
|
||||
);
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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<RegistryAlertTypeWithAuth>,
|
||||
opts: AlertingAuthorizationFilterOpts
|
||||
): KueryNode | JsonObject {
|
||||
const kueryNode = nodeBuilder.or(
|
||||
Array.from(ruleTypes).reduce<KueryNode[]>((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;
|
||||
}
|
|
@ -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<AlertsAuthorization>;
|
||||
export type AlertsAuthorizationMock = jest.Mocked<Schema>;
|
||||
|
||||
const createAlertsAuthorizationMock = () => {
|
||||
const mocked: AlertsAuthorizationMock = {
|
||||
ensureAuthorized: jest.fn(),
|
||||
filterByAlertTypeAuthorization: jest.fn(),
|
||||
getFindAuthorizationFilter: jest.fn(),
|
||||
};
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const alertsAuthorizationMock: {
|
||||
create: () => AlertsAuthorizationMock;
|
||||
} = {
|
||||
create: createAlertsAuthorizationMock,
|
||||
};
|
|
@ -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();
|
||||
});
|
||||
});
|
|
@ -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<RegistryAlertTypeWithAuth>
|
||||
): KueryNode {
|
||||
return nodeBuilder.or(
|
||||
Array.from(alertTypes).reduce<KueryNode[]>((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;
|
||||
}
|
|
@ -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<AlertsAuthorizationAuditLogger>;
|
||||
logAuthorizationFailure: jest.fn(),
|
||||
logUnscopedAuthorizationFailure: jest.fn(),
|
||||
logAuthorizationSuccess: jest.fn(),
|
||||
logBulkAuthorizationSuccess: jest.fn(),
|
||||
} as unknown) as jest.Mocked<AlertingAuthorizationAuditLogger>;
|
||||
return mocked;
|
||||
};
|
||||
|
||||
export const alertsAuthorizationAuditLoggerMock: {
|
||||
create: () => jest.Mocked<AlertsAuthorizationAuditLogger>;
|
||||
export const alertingAuthorizationAuditLoggerMock: {
|
||||
create: () => jest.Mocked<AlertingAuthorizationAuditLogger>;
|
||||
} = {
|
||||
create: createAlertsAuthorizationAuditLoggerMock,
|
||||
create: createAlertingAuthorizationAuditLoggerMock,
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -5,5 +5,5 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export * from './alerts_authorization';
|
||||
export * from './alerts_authorization_kuery';
|
||||
export * from './alerting_authorization';
|
||||
export * from './alerting_authorization_kuery';
|
||||
|
|
|
@ -26,6 +26,7 @@ const createSetupMock = () => {
|
|||
const createStartMock = () => {
|
||||
const mock: jest.Mocked<PluginStartContract> = {
|
||||
listTypes: jest.fn(),
|
||||
getAlertingAuthorizationWithRequest: jest.fn(),
|
||||
getAlertsClientWithRequest: jest.fn().mockResolvedValue(alertsClientMock.create()),
|
||||
getFrameworkHealth: jest.fn(),
|
||||
};
|
||||
|
|
|
@ -201,6 +201,58 @@ describe('Alerting Plugin', () => {
|
|||
startContract.getAlertsClientWithRequest(fakeRequest);
|
||||
});
|
||||
});
|
||||
|
||||
test(`exposes getAlertingAuthorizationWithRequest()`, async () => {
|
||||
const context = coreMock.createPluginInitializerContext<AlertsConfig>({
|
||||
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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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<AlertsClient>;
|
||||
getAlertingAuthorizationWithRequest(
|
||||
request: KibanaRequest
|
||||
): PublicMethodsOf<AlertingAuthorization>;
|
||||
getFrameworkHealth: () => Promise<AlertsHealth>;
|
||||
}
|
||||
|
||||
|
@ -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'])),
|
||||
|
|
|
@ -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"`;
|
||||
|
|
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -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<AlertingType, string[]> = {
|
||||
rule: ['get', 'getRuleState', 'getAlertSummary', 'find'],
|
||||
alert: ['get', 'find'],
|
||||
};
|
||||
|
||||
const writeOperations: Record<AlertingType, string[]> = {
|
||||
rule: [
|
||||
'create',
|
||||
'delete',
|
||||
'update',
|
||||
'updateApiKey',
|
||||
'enable',
|
||||
'disable',
|
||||
'muteAll',
|
||||
'unmuteAll',
|
||||
'muteAlert',
|
||||
'unmuteAlert',
|
||||
],
|
||||
alert: ['update'],
|
||||
};
|
||||
const allOperations: Record<AlertingType, string[]> = {
|
||||
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<AlertingType, string[]>,
|
||||
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([
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
),
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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'
|
||||
),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue