Skip to content

Commit

Permalink
Prevent bulk replaying system events. (#21435)
Browse files Browse the repository at this point in the history
* Prevent bulk replaying system events.

* Cleaning up test.

* Prevent replaying event without replay info.

* Adjusting test.
  • Loading branch information
dennisoelkers authored Jan 28, 2025
1 parent 4e2dc8a commit f83ded0
Show file tree
Hide file tree
Showing 9 changed files with 58 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { simpleEventDefinition as mockEventDefinition } from 'fixtures/eventDefi
import useScopePermissions from 'hooks/useScopePermissions';
import useCurrentUser from 'hooks/useCurrentUser';
import useEventDefinitionConfigFromLocalStorage from 'components/event-definitions/hooks/useEventDefinitionConfigFromLocalStorage';
import { SYSTEM_EVENT_DEFINITION_TYPE as mockSYSTEM_EVENT_DEFINITION_TYPE } from 'components/event-definitions/constants';

import EventDefinitionFormContainer from './EventDefinitionFormContainer';

Expand Down Expand Up @@ -74,7 +75,7 @@ jest.mock('stores/connect', () => ({
stores: { [key: string]: any },
_mapProps: (args: { [key: string]: any }) => any,
) => {
const storeProps = Object.entries(stores).reduce((acc, [key, store]) => ({ ...acc, [key]: store.getInitialState() }), {});
const storeProps = Object.fromEntries(Object.entries(stores).map(([key, store]) => [key, store.getInitialState()]));
const componentProps = {
...storeProps,
eventDefinition: {
Expand All @@ -97,7 +98,7 @@ jest.mock('stores/event-definitions/AvailableEventDefinitionTypesStore', () => (
getInitialState: () => ({
aggregation_functions: ['avg', 'card', 'count', 'max', 'min', 'sum', 'stddev', 'sumofsquares', 'variance', 'percentage', 'percentile', 'latest'],
field_provider_types: ['template-v1', 'lookup-v1'],
processor_types: ['aggregation-v1', 'system-notifications-v1', 'correlation-v1', 'anomaly-v1', 'sigma-v1'],
processor_types: ['aggregation-v1', mockSYSTEM_EVENT_DEFINITION_TYPE, 'correlation-v1', 'anomaly-v1', 'sigma-v1'],
storage_handler_types: ['persist-to-streams-v1'],
}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import useLocation from 'routing/useLocation';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';

import type { EventDefinition } from '../event-definitions-types';
import { isSystemEventDefinition } from '../event-definitions-types';
import commonStyles from '../common/commonStyles.css';

const priorityOptions = map(EventDefinitionPriorityEnum.properties, (value, key) => ({ value: key, label: upperFirst(value.name) }))
Expand Down Expand Up @@ -70,7 +71,7 @@ const EventDetailsForm = ({ eventDefinition, validation, onChange, canEdit }: Pr
};

const readOnly = !canEdit
|| eventDefinition.config.type === 'system-notifications-v1'
|| isSystemEventDefinition(eventDefinition)
|| eventDefinition.config.type === 'sigma-v1';

return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import FieldForm from './FieldForm';
import FieldsList from './FieldsList';

import type { EventDefinition } from '../event-definitions-types';
import { isSystemEventDefinition } from '../event-definitions-types';
import commonStyles from '../common/commonStyles.css';

type Props = {
Expand Down Expand Up @@ -86,8 +87,7 @@ const FieldsForm = ({ currentUser, eventDefinition, validation, onChange, canEdi
toggleFieldForm();
};

const isSystemEventDefinition = eventDefinition.config.type === 'system-notifications-v1';
const canEditCondition = canEdit && !isSystemEventDefinition;
const canEditCondition = canEdit && !isSystemEventDefinition(eventDefinition);

if (showFieldForm) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { type SyntheticEvent } from 'react';

import type { StepsType } from 'components/common/Wizard';
import type { LookupTableParameterJson } from 'views/logic/parameters/LookupTableParameter';
import { SYSTEM_EVENT_DEFINITION_TYPE } from 'components/event-definitions/constants';

type Provider = {
type: string,
Expand Down Expand Up @@ -117,3 +118,9 @@ export type EventDefinitionFormControlsProps = {
onSubmit: (event: SyntheticEvent) => void,
steps: StepsType,
}

export const isSystemEventDefinition = (eventDefinition: EventDefinition) => eventDefinition?.config?.type === SYSTEM_EVENT_DEFINITION_TYPE;

export const isAggregationEventDefinition = (eventDefinition: EventDefinition) => eventDefinition?.config?.type === 'aggregation-v1';

export const isSigmaEventDefinition = (eventDefinition: EventDefinition) => eventDefinition?.config?.type === 'sigma-v1';
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import usePluginEntities from 'hooks/usePluginEntities';
import { useTableFetchContext } from 'components/common/PaginatedEntityTable';

import type { EventDefinition } from '../event-definitions-types';
import { isAggregationEventDefinition, isSystemEventDefinition, isSigmaEventDefinition } from '../event-definitions-types';

type SigmaEventDefinitionConfig = EventDefinition['config'] & {
sigma_rule_id: string,
Expand Down Expand Up @@ -93,18 +94,12 @@ const EventDefinitionActions = ({ eventDefinition }: Props) => {

const showActions = (): boolean => scopePermissions?.is_mutable;

const isSystemEventDefinition = (): boolean => eventDefinition?.config?.type === 'system-notifications-v1';

const isAggregationEventDefinition = (): boolean => eventDefinition?.config?.type === 'aggregation-v1';

const isSigmaEventDefinition = (): boolean => eventDefinition?.config?.type === 'sigma-v1';

const getDeleteActionTitle = () => {
if (isSystemEventDefinition()) {
if (isSystemEventDefinition(eventDefinition)) {
return 'System Event Definition cannot be deleted';
}

if (isSigmaEventDefinition()) {
if (isSigmaEventDefinition(eventDefinition)) {
return 'Sigma Rules must be deleted from the Sigma Rules page';
}

Expand Down Expand Up @@ -232,7 +227,7 @@ const EventDefinitionActions = ({ eventDefinition }: Props) => {
};

const onEditEventDefinition = () => {
if (isSigmaEventDefinition()) {
if (isSigmaEventDefinition(eventDefinition)) {
setShowSigmaModal(true);
} else {
navigate(Routes.ALERTS.DEFINITIONS.edit(eventDefinition.id));
Expand All @@ -259,27 +254,27 @@ const EventDefinitionActions = ({ eventDefinition }: Props) => {
Edit
</MenuItem>
</IfPermitted>
{!isSystemEventDefinition() && !isSigmaEventDefinition() && (
{!isSystemEventDefinition(eventDefinition) && !isSigmaEventDefinition(eventDefinition) && (
<MenuItem onClick={() => handleAction(DIALOG_TYPES.COPY, eventDefinition)}>Duplicate</MenuItem>
)}
<MenuItem divider />
<MenuItem disabled={isSystemEventDefinition()}
title={isSystemEventDefinition() ? 'System Event Definition cannot be disabled' : undefined}
onClick={isSystemEventDefinition() ? undefined : () => handleAction(isEnabled ? DIALOG_TYPES.DISABLE : DIALOG_TYPES.ENABLE, eventDefinition)}>
<MenuItem disabled={isSystemEventDefinition(eventDefinition)}
title={isSystemEventDefinition(eventDefinition) ? 'System Event Definition cannot be disabled' : undefined}
onClick={isSystemEventDefinition(eventDefinition) ? undefined : () => handleAction(isEnabled ? DIALOG_TYPES.DISABLE : DIALOG_TYPES.ENABLE, eventDefinition)}>
{isEnabled ? 'Disable' : 'Enable'}
</MenuItem>

{showActions() && (
<IfPermitted permissions={`eventdefinitions:delete:${eventDefinition.id}`}>
<MenuItem divider />
<DeleteMenuItem disabled={isSystemEventDefinition() || isSigmaEventDefinition()}
<DeleteMenuItem disabled={isSystemEventDefinition(eventDefinition) || isSigmaEventDefinition(eventDefinition)}
title={getDeleteActionTitle()}
onClick={isSystemEventDefinition() || isSigmaEventDefinition() ? undefined : () => handleAction(DIALOG_TYPES.DELETE, eventDefinition)}
onClick={isSystemEventDefinition(eventDefinition) || isSigmaEventDefinition(eventDefinition) ? undefined : () => handleAction(DIALOG_TYPES.DELETE, eventDefinition)}
data-testid="delete-button" />
</IfPermitted>
)}
{
isAggregationEventDefinition() && (
isAggregationEventDefinition(eventDefinition) && (
<>
<MenuItem divider />
<LinkContainer to={Routes.ALERTS.DEFINITIONS.replay_search(eventDefinition.id)}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { Icon } from 'components/common';
import { useTableFetchContext } from 'components/common/PaginatedEntityTable';

import type { EventDefinition } from '../event-definitions-types';
import { isSystemEventDefinition } from '../event-definitions-types';

const StatusLabel = styled(Label)<{ $clickable: boolean }>(({ $clickable }) => css`
cursor: ${$clickable ? 'pointer' : 'default'};
Expand Down Expand Up @@ -53,7 +54,7 @@ const StatusCell = ({ eventDefinition } : Props) => {
const [showConfirmDisableModal, setShowConfirmDisableModal] = useState<boolean>(false);
const { refetch: refetchEventDefinitions } = useTableFetchContext();
const isEnabled = eventDefinition?.state === 'ENABLED';
const disableChange = eventDefinition?.config?.type === 'system-notifications-v1';
const disableChange = isSystemEventDefinition(eventDefinition);
const description = isEnabled ? 'enabled' : 'disabled';
const title = _title(!isEnabled, disableChange, description);

Expand Down
30 changes: 26 additions & 4 deletions graylog2-web-interface/src/components/events/ReplaySearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ import ReplaySearchContext from 'components/event-definitions/replay-search/Repl
import type { LayoutState } from 'views/components/contexts/SearchPageLayoutContext';
import Spinner from 'components/common/Spinner';
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
import { isSystemEventDefinition } from 'components/event-definitions/event-definitions-types';
import type { Event } from 'components/events/events/types';
import type { EventDefinitionAggregation } from 'hooks/useEventDefinition';
import useCreateViewForEvent from 'views/logic/views/UseCreateViewForEvent';
import Center from 'components/common/Center';

type ReplaySearchProps = {
alertId: string,
Expand Down Expand Up @@ -79,14 +81,33 @@ type Props = {
forceSidebarPinned?: boolean,
}

const canReplayEvent = (event: Event, eventDefinition: EventDefinition) => {
const systemEvent = isSystemEventDefinition(eventDefinition);

if (systemEvent) {
return 'Event is a system event, these have no query/stream/time range attached.';
}

if (!event?.replay_info) {
return 'Event is missing replay information.';
}

return true;
};

const LoadingBarrier = ({
alertId, definitionId, replayEventDefinition = false, searchPageLayout = defaultSearchPageLayout, forceSidebarPinned = false,
}: Props) => {
const { eventDefinition, aggregations, eventData, isLoading } = useAlertAndEventDefinitionData(alertId, definitionId);

return isLoading
? <Spinner />
: (
if (isLoading) {
return <Spinner />;
}

const canReplay = canReplayEvent(eventData, eventDefinition);

return canReplay === true
? (
<ReplaySearch alertId={alertId}
definitionId={definitionId}
eventDefinition={eventDefinition}
Expand All @@ -95,7 +116,8 @@ const LoadingBarrier = ({
searchPageLayout={searchPageLayout}
replayEventDefinition={replayEventDefinition}
forceSidebarPinned={forceSidebarPinned} />
);
)
: <Center>Cannot replay this event: {canReplay} Please select a different one.</Center>;
};

export default LoadingBarrier;
5 changes: 2 additions & 3 deletions graylog2-web-interface/src/pages/ViewEventDefinitionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import EventsPageNavigation from 'components/events/EventsPageNavigation';
import useHistory from 'routing/useHistory';
import useSendTelemetry from 'logic/telemetry/useSendTelemetry';
import type { EventDefinition } from 'components/event-definitions/event-definitions-types';
import { isSystemEventDefinition } from 'components/event-definitions/event-definitions-types';
import { TELEMETRY_EVENT_TYPE } from 'logic/telemetry/Constants';
import usePluginEntities from 'hooks/usePluginEntities';

Expand All @@ -58,8 +59,6 @@ const ViewEventDefinitionPage = () => {
? pluggableSigmaModal.component as React.FC<{ ruleId: string, onCancel: () => void, onConfirm: () => void }>
: null;

const isSystemEventDefinition = (): boolean => eventDefinition?.config?.type === 'system-notifications-v1';

useEffect(() => {
if (currentUser && isPermitted(currentUser.permissions, `eventdefinitions:read:${params.definitionId}`) && refetch) {
EventDefinitionsActions.get(params.definitionId)
Expand Down Expand Up @@ -131,7 +130,7 @@ const ViewEventDefinitionPage = () => {
<IfPermitted permissions={`eventdefinitions:edit:${params.definitionId}`}>
<Button bsStyle="success" onClick={onEditEventDefinition}>Edit Event Definition</Button>
</IfPermitted>
{!isSystemEventDefinition() && (
{!isSystemEventDefinition(eventDefinition) && (
<IfPermitted permissions="eventdefinitions:create">
<Button onClick={() => setShowDialog(true)} bsStyle="success">Duplicate Event
Definition
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import EventDefinitionReplaySearchPage, { onErrorHandler } from 'views/pages/Eve
import useEventDefinition from 'hooks/useEventDefinition';
import {
mockedMappedAggregation,
mockEventDefinitionTwoAggregations,
mockEventDefinitionTwoAggregations, mockEventData,
} from 'helpers/mocking/EventAndEventDefinitions_mock';
import useParams from 'routing/useParams';
import type { Stream } from 'logic/streams/types';
Expand Down Expand Up @@ -91,7 +91,7 @@ describe('EventDefinitionReplaySearchPage', () => {

it('should run useEventDefinition, UseCreateViewForEvent with correct parameters', async () => {
asMock(useAlertAndEventDefinitionData).mockReturnValue({
eventData: undefined,
eventData: mockEventData.event,
eventDefinition: mockEventDefinitionTwoAggregations,
aggregations: mockedMappedAggregation,
alertId: undefined,
Expand All @@ -108,7 +108,7 @@ describe('EventDefinitionReplaySearchPage', () => {

await waitFor(() => {
expect(UseCreateViewForEvent).toHaveBeenCalledWith({
eventDefinition: mockEventDefinitionTwoAggregations, aggregations: mockedMappedAggregation,
eventDefinition: mockEventDefinitionTwoAggregations, aggregations: mockedMappedAggregation, eventData: mockEventData.event,
});
});
});
Expand Down

0 comments on commit f83ded0

Please sign in to comment.