Skip to content

Commit

Permalink
[RAC] [o11y] add permission in alerts table from kibana privilege/con…
Browse files Browse the repository at this point in the history
…sumer (elastic#109759) (elastic#109988)

* add alert permission in o11y

* review I

* review II

* fix selection all when checkbox disabled

* fix selected on bulk actions

Co-authored-by: Xavier Mouligneau <189600+XavierM@users.noreply.github.com>
  • Loading branch information
kibanamachine and XavierM committed Aug 25, 2021
1 parent 056cbd6 commit 9f2fe19
Show file tree
Hide file tree
Showing 14 changed files with 174 additions and 50 deletions.
39 changes: 24 additions & 15 deletions x-pack/plugins/observability/public/hooks/use_alert_permission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { useEffect, useState } from 'react';
import { RecursiveReadonly } from '@kbn/utility-types';
import { Capabilities } from '../../../../../src/core/types';

export interface UseGetUserAlertsPermissionsProps {
crud: boolean;
Expand All @@ -15,8 +16,29 @@ export interface UseGetUserAlertsPermissionsProps {
featureId: string | null;
}

export const getAlertsPermissions = (
uiCapabilities: RecursiveReadonly<Capabilities>,
featureId: string
) => {
if (!featureId || !uiCapabilities[featureId]) {
return {
crud: false,
read: false,
loading: false,
featureId,
};
}

return {
crud: uiCapabilities[featureId].save as boolean,
read: uiCapabilities[featureId].show as boolean,
loading: false,
featureId,
};
};

export const useGetUserAlertsPermissions = (
uiCapabilities: RecursiveReadonly<Record<string, any>>,
uiCapabilities: RecursiveReadonly<Capabilities>,
featureId?: string
): UseGetUserAlertsPermissionsProps => {
const [alertsPermissions, setAlertsPermissions] = useState<UseGetUserAlertsPermissionsProps>({
Expand All @@ -39,20 +61,7 @@ export const useGetUserAlertsPermissions = (
if (currentAlertPermissions.featureId === featureId) {
return currentAlertPermissions;
}
const capabilitiesCanUserCRUD: boolean =
typeof uiCapabilities[featureId].save === 'boolean'
? uiCapabilities[featureId].save
: false;
const capabilitiesCanUserRead: boolean =
typeof uiCapabilities[featureId].show === 'boolean'
? uiCapabilities[featureId].show
: false;
return {
crud: capabilitiesCanUserCRUD,
read: capabilitiesCanUserRead,
loading: false,
featureId,
};
return getAlertsPermissions(uiCapabilities, featureId);
});
}
}, [alertsPermissions.featureId, featureId, uiCapabilities]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ import { i18n } from '@kbn/i18n';
import styled from 'styled-components';
import React, { Suspense, useMemo, useState, useCallback } from 'react';
import { get } from 'lodash';
import { useGetUserAlertsPermissions } from '../../hooks/use_alert_permission';
import {
getAlertsPermissions,
useGetUserAlertsPermissions,
} from '../../hooks/use_alert_permission';
import type { TimelinesUIStart, TGridType, SortDirection } from '../../../../timelines/public';
import { useStatusBulkActionItems } from '../../../../timelines/public';
import type { TopAlert } from './';
Expand Down Expand Up @@ -279,12 +282,22 @@ function ObservabilityActions({

export function AlertsTableTGrid(props: AlertsTableTGridProps) {
const { indexNames, rangeFrom, rangeTo, kuery, workflowStatus, setRefetch, addToQuery } = props;
const { timelines } = useKibana<{ timelines: TimelinesUIStart }>().services;
const {
timelines,
application: { capabilities },
} = useKibana<CoreStart & { timelines: TimelinesUIStart }>().services;

const [flyoutAlert, setFlyoutAlert] = useState<TopAlert | undefined>(undefined);

const casePermissions = useGetUserCasesPermissions();

const hasAlertsCrudPermissions = useCallback(
(featureId: string) => {
return getAlertsPermissions(capabilities, featureId).crud;
},
[capabilities]
);

const leadingControlColumns = useMemo(() => {
return [
{
Expand Down Expand Up @@ -324,6 +337,7 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
defaultCellActions: getDefaultCellActions({ addToQuery }),
end: rangeTo,
filters: [],
hasAlertsCrudPermissions,
indexNames,
itemsPerPageOptions: [10, 25, 50],
loadingText: i18n.translate('xpack.observability.alertsTable.loadingTextLabel', {
Expand Down Expand Up @@ -358,14 +372,15 @@ export function AlertsTableTGrid(props: AlertsTableTGridProps) {
};
}, [
casePermissions,
addToQuery,
rangeTo,
hasAlertsCrudPermissions,
indexNames,
workflowStatus,
kuery,
leadingControlColumns,
rangeFrom,
rangeTo,
setRefetch,
workflowStatus,
addToQuery,
leadingControlColumns,
]);
const handleFlyoutClose = () => setFlyoutAlert(undefined);
const { observabilityRuleTypeRegistry } = usePluginContext();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface TimelineNonEcsData {
}

export interface TimelineEventsAllStrategyResponse extends IEsSearchResponse {
consumers: Record<string, number>;
edges: TimelineEdges[];
totalCount: number;
pageInfo: Pick<PaginationInputPaginated, 'activePage' | 'querySize'>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface ActionProps {
columnId: string;
columnValues: string;
checked: boolean;
disabled?: boolean;
onRowSelected: OnRowSelected;
eventId: string;
loadingEventIds: Readonly<string[]>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,19 @@ export const RowCheckBox = ({
checked,
ariaRowindex,
columnValues,
disabled,
loadingEventIds,
}: ActionProps) => {
const handleSelectEvent = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) =>
onRowSelected({
eventIds: [eventId],
isSelected: event.currentTarget.checked,
}),
[eventId, onRowSelected]
(event: React.ChangeEvent<HTMLInputElement>) => {
if (!disabled) {
onRowSelected({
eventIds: [eventId],
isSelected: event.currentTarget.checked,
});
}
},
[eventId, onRowSelected, disabled]
);

return loadingEventIds.includes(eventId) ? (
Expand All @@ -33,7 +37,8 @@ export const RowCheckBox = ({
<EuiCheckbox
data-test-subj="select-event"
id={eventId}
checked={checked}
checked={checked && !disabled}
disabled={disabled}
onChange={handleSelectEvent}
aria-label={i18n.CHECKBOX_FOR_ROW({ ariaRowindex, columnValues, checked })}
/>
Expand Down
21 changes: 17 additions & 4 deletions x-pack/plugins/timelines/public/components/t_grid/body/helpers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

import { ALERT_RULE_CONSUMER } from '@kbn/rule-data-utils';
import { isEmpty } from 'lodash/fp';

import { EuiDataGridCellValueElementProps } from '@elastic/eui';
Expand Down Expand Up @@ -39,12 +40,24 @@ export const stringifyEvent = (ecs: Ecs): string => JSON.stringify(ecs, omitType
export const getEventIdToDataMapping = (
timelineData: TimelineItem[],
eventIds: string[],
fieldsToKeep: string[]
fieldsToKeep: string[],
hasAlertsCrud: boolean,
hasAlertsCrudPermissionsByFeatureId?: (featureId: string) => boolean
): Record<string, TimelineNonEcsData[]> =>
timelineData.reduce((acc, v) => {
const fvm = eventIds.includes(v._id)
? { [v._id]: v.data.filter((ti) => fieldsToKeep.includes(ti.field)) }
: {};
// 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))
: hasAlertsCrud;

const fvm =
hasPermissions && eventIds.includes(v._id)
? { [v._id]: v.data.filter((ti) => fieldsToKeep.includes(ti.field)) }
: {};
return {
...acc,
...fvm,
Expand Down
Loading

0 comments on commit 9f2fe19

Please sign in to comment.