Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SIEM] [Detection engine] Permission II #54292

Merged
merged 19 commits into from
Jan 11, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -149,20 +149,23 @@ const SearchTimelineSuperSelectComponent: React.FC<SearchTimelineSuperSelectProp
);
}, []);

const handleTimelineChange = useCallback(options => {
const selectedTimeline = options.filter(
(option: { checked: string }) => option.checked === 'on'
);
if (selectedTimeline != null && selectedTimeline.length > 0 && onTimelineChange != null) {
onTimelineChange(
isEmpty(selectedTimeline[0].title)
? i18nTimeline.UNTITLED_TIMELINE
: selectedTimeline[0].title,
selectedTimeline[0].id
const handleTimelineChange = useCallback(
options => {
const selectedTimeline = options.filter(
(option: { checked: string }) => option.checked === 'on'
);
}
setIsPopoverOpen(false);
}, []);
if (selectedTimeline != null && selectedTimeline.length > 0) {
onTimelineChange(
isEmpty(selectedTimeline[0].title)
? i18nTimeline.UNTITLED_TIMELINE
: selectedTimeline[0].title,
selectedTimeline[0].id === '-1' ? null : selectedTimeline[0].id
);
}
setIsPopoverOpen(false);
},
[onTimelineChange]
);

const handleOnScroll = useCallback(
(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ import {
NewRule,
Rule,
FetchRuleProps,
BasicFetchProps,
} from './types';
import { throwIfNotOk } from '../../../hooks/api/api';
import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants';
import {
DETECTION_ENGINE_RULES_URL,
DETECTION_ENGINE_PREPACKAGED_URL,
} from '../../../../common/constants';

/**
* Add provided Rule
Expand Down Expand Up @@ -199,3 +203,22 @@ export const duplicateRules = async ({ rules }: DuplicateRulesProps): Promise<Ru
responses.map<Promise<Rule>>(response => response.json())
);
};

/**
* Create Prepackaged Rules
*
* @param signal AbortSignal for cancelling request
*/
export const createPrepackagedRules = async ({ signal }: BasicFetchProps): Promise<boolean> => {
const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_URL}`, {
method: 'PUT',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-xsrf': 'true',
},
signal,
});
await throwIfNotOk(response);
return true;
};
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,7 @@ export interface DeleteRulesProps {
export interface DuplicateRulesProps {
rules: Rules;
}

export interface BasicFetchProps {
signal: AbortSignal;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,24 @@ export const SIGNAL_FETCH_FAILURE = i18n.translate(
defaultMessage: 'Failed to query signals',
}
);

export const PRIVILEGE_FETCH_FAILURE = i18n.translate(
'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription',
{
defaultMessage: 'Failed to query signals',
}
);

export const SIGNAL_GET_NAME_FAILURE = i18n.translate(
'xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription',
{
defaultMessage: 'Failed to get signal index name',
}
);

export const SIGNAL_POST_FAILURE = i18n.translate(
'xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription',
{
defaultMessage: 'Failed to create signal index',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@

import { useEffect, useState } from 'react';

import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
import { useStateToaster } from '../../../components/toasters';
import { getUserPrivilege } from './api';
import * as i18n from './translations';

type Return = [boolean, boolean | null, boolean | null];
type Return = [boolean, boolean | null, boolean | null, boolean | null];

/**
* Hook to get user privilege from
Expand All @@ -17,7 +20,9 @@ type Return = [boolean, boolean | null, boolean | null];
export const usePrivilegeUser = (): Return => {
const [loading, setLoading] = useState(true);
const [isAuthenticated, setAuthenticated] = useState<boolean | null>(null);
const [hasWrite, setHasWrite] = useState<boolean | null>(null);
const [hasIndexWrite, setHasIndexWrite] = useState<boolean | null>(null);
const [hasManageApiKey, setHasManageApiKey] = useState<boolean | null>(null);
const [, dispatchToaster] = useStateToaster();

useEffect(() => {
let isSubscribed = true;
Expand All @@ -34,13 +39,19 @@ export const usePrivilegeUser = (): Return => {
setAuthenticated(privilege.isAuthenticated);
if (privilege.index != null && Object.keys(privilege.index).length > 0) {
const indexName = Object.keys(privilege.index)[0];
setHasWrite(privilege.index[indexName].create_index);
setHasIndexWrite(privilege.index[indexName].manage);
setHasManageApiKey(
privilege.cluster.manage_security ||
privilege.cluster.manage_api_key ||
privilege.cluster.manage_own_api_key
);
}
}
} catch (error) {
if (isSubscribed) {
setAuthenticated(false);
setHasWrite(false);
setHasIndexWrite(false);
errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster });
}
}
if (isSubscribed) {
Expand All @@ -55,5 +66,5 @@ export const usePrivilegeUser = (): Return => {
};
}, []);

return [loading, isAuthenticated, hasWrite];
return [loading, isAuthenticated, hasIndexWrite, hasManageApiKey];
};
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { useEffect, useState, useRef } from 'react';

import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
import { useStateToaster } from '../../../components/toasters';
import { createPrepackagedRules } from '../rules';
import { createSignalIndex, getSignalIndex } from './api';
import * as i18n from './translations';
import { PostSignalError } from './types';
import { PostSignalError, SignalIndexError } from './types';

type Func = () => void;

Expand Down Expand Up @@ -40,11 +41,15 @@ export const useSignalIndex = (): Return => {
if (isSubscribed && signal != null) {
setSignalIndexName(signal.name);
setSignalIndexExists(true);
createPrepackagedRules({ signal: abortCtrl.signal });
}
} catch (error) {
if (isSubscribed) {
setSignalIndexName(null);
setSignalIndexExists(false);
if (error instanceof SignalIndexError && error.statusCode !== 404) {
errorToToaster({ title: i18n.SIGNAL_GET_NAME_FAILURE, error, dispatchToaster });
}
}
}
if (isSubscribed) {
Expand All @@ -69,7 +74,7 @@ export const useSignalIndex = (): Return => {
} else {
setSignalIndexName(null);
setSignalIndexExists(false);
errorToToaster({ title: i18n.SIGNAL_FETCH_FAILURE, error, dispatchToaster });
errorToToaster({ title: i18n.SIGNAL_POST_FAILURE, error, dispatchToaster });
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,55 +168,64 @@ export const requiredFieldsForActions = [
];

export const getSignalsActions = ({
canUserCRUD,
setEventsLoading,
setEventsDeleted,
createTimeline,
status,
}: {
canUserCRUD: boolean;
setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void;
setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void;
createTimeline: CreateTimeline;
status: 'open' | 'closed';
}): TimelineAction[] => [
{
getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => (
<EuiToolTip
data-test-subj="send-signal-to-timeline-tool-tip"
content={i18n.ACTION_VIEW_IN_TIMELINE}
>
<EuiButtonIcon
data-test-subj={'send-signal-to-timeline-tool-tip'}
onClick={() => sendSignalsToTimelineAction({ createTimeline, data: [data] })}
iconType="tableDensityNormal"
aria-label="Next"
/>
</EuiToolTip>
),
id: 'sendSignalToTimeline',
width: 26,
},
{
getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => (
<EuiToolTip
data-test-subj="update-signal-status-tool-tip"
content={status === FILTER_OPEN ? i18n.ACTION_OPEN_SIGNAL : i18n.ACTION_CLOSE_SIGNAL}
>
<EuiButtonIcon
data-test-subj={'update-signal-status-button'}
onClick={() =>
updateSignalStatusAction({
signalIds: [eventId],
status,
setEventsLoading,
setEventsDeleted,
})
}
iconType={status === FILTER_OPEN ? 'indexOpen' : 'indexClose'}
aria-label="Next"
/>
</EuiToolTip>
),
id: 'updateSignalStatus',
width: 26,
},
];
}): TimelineAction[] => {
const actions = [
{
getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => (
<EuiToolTip
data-test-subj="send-signal-to-timeline-tool-tip"
content={i18n.ACTION_VIEW_IN_TIMELINE}
>
<EuiButtonIcon
data-test-subj={'send-signal-to-timeline-tool-tip'}
onClick={() => sendSignalsToTimelineAction({ createTimeline, data: [data] })}
iconType="tableDensityNormal"
aria-label="Next"
/>
</EuiToolTip>
),
id: 'sendSignalToTimeline',
width: 26,
},
];
return canUserCRUD
? [
...actions,
{
getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => (
<EuiToolTip
data-test-subj="update-signal-status-tool-tip"
content={status === FILTER_OPEN ? i18n.ACTION_OPEN_SIGNAL : i18n.ACTION_CLOSE_SIGNAL}
>
<EuiButtonIcon
data-test-subj={'update-signal-status-button'}
onClick={() =>
updateSignalStatusAction({
signalIds: [eventId],
status,
setEventsLoading,
setEventsDeleted,
})
}
iconType={status === FILTER_OPEN ? 'indexOpen' : 'indexClose'}
aria-label="Next"
/>
</EuiToolTip>
),
id: 'updateSignalStatus',
width: 26,
},
]
: actions;
};
Loading