[RAC][o11y] fix o11y privileges when rule created in stack (#110167)

* fix o11y privileges when rule created in stack

* fix merge

* fix dsl alerts

* fix privileges on o11y

* after discussion with o11y, we agree to simplify logic of the count of alerts

* remove unused variable

* fix one more type error

Co-authored-by: mgiota <panagiota.mitsopoulou@elastic.co>
This commit is contained in:
Xavier Mouligneau 2021-08-31 07:31:38 -04:00 committed by GitHub
parent 03469515cf
commit 782f29a407
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 73 additions and 27 deletions

View file

@ -64,7 +64,7 @@ export const APM_FEATURE = {
management: {
insightsAndAlerting: ['triggersActions'],
},
ui: ['show', 'alerting:show', 'alerting:save'],
ui: ['show', 'alerting:show'],
},
},
subFeatures: [

View file

@ -12,7 +12,7 @@ import { Capabilities } from '../../../../../src/core/types';
export interface UseGetUserAlertsPermissionsProps {
crud: boolean;
read: boolean;
loading: boolean;
loading?: boolean;
featureId: string | null;
}
@ -30,9 +30,12 @@ export const getAlertsPermissions = (
}
return {
crud: uiCapabilities[featureId].save as boolean,
read: uiCapabilities[featureId].show as boolean,
loading: false,
crud: (featureId === 'apm'
? uiCapabilities[featureId]['alerting:save']
: uiCapabilities[featureId].save) as boolean,
read: (featureId === 'apm'
? uiCapabilities[featureId]['alerting:show']
: uiCapabilities[featureId].show) as boolean,
featureId,
};
};

View file

@ -14,6 +14,7 @@ import {
ALERT_DURATION as ALERT_DURATION_TYPED,
ALERT_REASON as ALERT_REASON_TYPED,
ALERT_RULE_CONSUMER,
ALERT_RULE_PRODUCER,
ALERT_STATUS as ALERT_STATUS_TYPED,
ALERT_WORKFLOW_STATUS as ALERT_WORKFLOW_STATUS_TYPED,
} from '@kbn/rule-data-utils';
@ -173,6 +174,9 @@ function ObservabilityActions({
const alertDataConsumer = useMemo<string>(() => get(dataFieldEs, ALERT_RULE_CONSUMER, [''])[0], [
dataFieldEs,
]);
const alertDataProducer = useMemo<string>(() => get(dataFieldEs, ALERT_RULE_PRODUCER, [''])[0], [
dataFieldEs,
]);
const alert = parseObservabilityAlert(dataFieldEs);
const { prepend } = core.http.basePath;
@ -204,7 +208,10 @@ function ObservabilityActions({
}
}, [setActionsPopover, refetch]);
const alertPermissions = useGetUserAlertsPermissions(capabilities, alertDataConsumer);
const alertPermissions = useGetUserAlertsPermissions(
capabilities,
alertDataConsumer === 'alerts' ? alertDataProducer : alertDataConsumer
);
const statusActionItems = useStatusBulkActionItems({
eventIds: [eventId],
@ -298,8 +305,11 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
const casePermissions = useGetUserCasesPermissions();
const hasAlertsCrudPermissions = useCallback(
(featureId: string) => {
return getAlertsPermissions(capabilities, featureId).crud;
({ ruleConsumer, ruleProducer }: { ruleConsumer: string; ruleProducer?: string }) => {
if (ruleConsumer === 'alerts' && ruleProducer) {
return getAlertsPermissions(capabilities, ruleProducer).crud;
}
return getAlertsPermissions(capabilities, ruleConsumer).crud;
},
[capabilities]
);

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { ALERT_RULE_CONSUMER, ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils';
import { isEmpty } from 'lodash/fp';
import { EuiDataGridCellValueElementProps } from '@elastic/eui';
@ -42,16 +42,26 @@ export const getEventIdToDataMapping = (
eventIds: string[],
fieldsToKeep: string[],
hasAlertsCrud: boolean,
hasAlertsCrudPermissionsByFeatureId?: (featureId: string) => boolean
hasAlertsCrudPermissionsByRule?: ({
ruleConsumer,
ruleProducer,
}: {
ruleConsumer: string;
ruleProducer?: string;
}) => boolean
): Record<string, TimelineNonEcsData[]> =>
timelineData.reduce((acc, v) => {
// FUTURE DEVELOPER
// We only have one featureId for security solution therefore we can just use hasAlertsCrud
// but for o11y we can multiple featureIds so we need to check every consumer
// of the alert to see if they have the permission to update the alert
const alertConsumers = v.data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? [];
const hasPermissions = hasAlertsCrudPermissionsByFeatureId
? alertConsumers.some((consumer) => hasAlertsCrudPermissionsByFeatureId(consumer))
const ruleConsumers = v.data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? [];
const ruleProducers = v.data.find((d) => d.field === ALERT_RULE_PRODUCER)?.value ?? [];
const hasPermissions = hasAlertsCrudPermissionsByRule
? hasAlertsCrudPermissionsByRule({
ruleConsumer: ruleConsumers.length > 0 ? ruleConsumers[0] : '',
ruleProducer: ruleProducers.length > 0 ? ruleProducers[0] : undefined,
})
: hasAlertsCrud;
const fvm =

View file

@ -32,7 +32,7 @@ import React, {
import { connect, ConnectedProps, useDispatch } from 'react-redux';
import styled, { ThemeContext } from 'styled-components';
import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { ALERT_RULE_CONSUMER, ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils';
import {
TGridCellAction,
BulkActionsProp,
@ -105,7 +105,13 @@ interface OwnProps {
trailingControlColumns?: ControlColumnProps[];
unit?: (total: number) => React.ReactNode;
hasAlertsCrud?: boolean;
hasAlertsCrudPermissions?: (featureId: string) => boolean;
hasAlertsCrudPermissions?: ({
ruleConsumer,
ruleProducer,
}: {
ruleConsumer: string;
ruleProducer?: string;
}) => boolean;
totalSelectAllAlerts?: number;
}
@ -180,7 +186,13 @@ const transformControlColumns = ({
theme: EuiTheme;
setEventsLoading: SetEventsLoading;
setEventsDeleted: SetEventsDeleted;
hasAlertsCrudPermissions?: (featureId: string) => boolean;
hasAlertsCrudPermissions?: ({
ruleConsumer,
ruleProducer,
}: {
ruleConsumer: string;
ruleProducer?: string;
}) => boolean;
}): EuiDataGridControlColumn[] =>
controlColumns.map(
({ id: columnId, headerCellRender = EmptyHeaderCellRender, rowCellRender, width }, i) => ({
@ -208,7 +220,6 @@ const transformControlColumns = ({
</>
);
},
// eslint-disable-next-line react/display-name
rowCellRender: ({
isDetails,
@ -224,9 +235,15 @@ const transformControlColumns = ({
if (rowData) {
addBuildingBlockStyle(rowData.ecs, theme, setCellProps);
if (columnId === 'checkbox-control-column' && hasAlertsCrudPermissions != null) {
const alertConsumers =
// FUTURE ENGINEER, the assumption here is you can only have one producer and consumer at this time
const ruleConsumers =
rowData.data.find((d) => d.field === ALERT_RULE_CONSUMER)?.value ?? [];
disabled = alertConsumers.some((consumer) => !hasAlertsCrudPermissions(consumer));
const ruleProducers =
rowData.data.find((d) => d.field === ALERT_RULE_PRODUCER)?.value ?? [];
disabled = !hasAlertsCrudPermissions({
ruleConsumer: ruleConsumers.length > 0 ? ruleConsumers[0] : '',
ruleProducer: ruleProducers.length > 0 ? ruleProducers[0] : undefined,
});
}
} else {
// disable the cell when it has no data

View file

@ -94,7 +94,13 @@ export interface TGridStandaloneProps {
filters: Filter[];
footerText: React.ReactNode;
filterStatus: AlertStatus;
hasAlertsCrudPermissions: (featureId: string) => boolean;
hasAlertsCrudPermissions: ({
ruleConsumer,
ruleProducer,
}: {
ruleConsumer: string;
ruleProducer?: string;
}) => boolean;
height?: number;
indexNames: string[];
itemsPerPageOptions: number[];
@ -229,8 +235,8 @@ const TGridStandaloneComponent: React.FC<TGridStandaloneProps> = ({
hasAlertsCrud: boolean;
totalSelectAllAlerts: number;
}>(
(acc, [featureId, nbrAlerts]) => {
const featureHasPermission = hasAlertsCrudPermissions(featureId);
(acc, [ruleConsumer, nbrAlerts]) => {
const featureHasPermission = hasAlertsCrudPermissions({ ruleConsumer });
return {
hasAlertsCrud: featureHasPermission || acc.hasAlertsCrud,
totalSelectAllAlerts: featureHasPermission

View file

@ -38,7 +38,7 @@ export const timelineEventsAll: TimelineFactory<TimelineEventsQueries.all> = {
let { fieldRequested, ...queryOptions } = cloneDeep(options);
queryOptions.fields = buildFieldsRequest(fieldRequested, queryOptions.excludeEcsData);
const { activePage, querySize } = options.pagination;
const buckets = getOr([], 'aggregations.consumers.buckets', response.rawResponse);
const producerBuckets = getOr([], 'aggregations.producers.buckets', response.rawResponse);
const totalCount = response.rawResponse.hits.total || 0;
const hits = response.rawResponse.hits.hits;
@ -62,7 +62,7 @@ export const timelineEventsAll: TimelineFactory<TimelineEventsQueries.all> = {
)
);
const consumers = buckets.reduce(
const consumers: Record<string, number> = producerBuckets.reduce(
(acc: Record<string, number>, b: { key: string; doc_count: number }) => ({
...acc,
[b.key]: b.doc_count,

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { ALERT_RULE_PRODUCER } from '@kbn/rule-data-utils';
import { isEmpty } from 'lodash/fp';
import {
@ -69,8 +69,8 @@ export const buildTimelineEventsAllQuery = ({
body: {
...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}),
aggregations: {
consumers: {
terms: { field: ALERT_RULE_CONSUMER },
producers: {
terms: { field: ALERT_RULE_PRODUCER, exclude: ['alerts'] },
},
},
query: {