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

[SECURITY] Timeline bug 7.9 #71748

Merged
merged 14 commits into from
Jul 15, 2020
Merged
Show file tree
Hide file tree
Changes from all 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 @@ -8,7 +8,6 @@ import { EuiButton, EuiLoadingSpinner } from '@elastic/eui';
import React, { useCallback, useEffect } from 'react';
import styled from 'styled-components';

import { useDispatch } from 'react-redux';
import { CommentRequest } from '../../../../../case/common/api';
import { usePostComment } from '../../containers/use_post_comment';
import { Case } from '../../containers/types';
Expand All @@ -19,12 +18,7 @@ import { Form, useForm, UseField } from '../../../shared_imports';

import * as i18n from './translations';
import { schema } from './schema';
import {
dispatchUpdateTimeline,
queryTimelineById,
} from '../../../timelines/components/open_timeline/helpers';
import { updateIsLoading as dispatchUpdateIsLoading } from '../../../timelines/store/timeline/actions';
import { useApolloClient } from '../../../common/utils/apollo_context';
import { useTimelineClick } from '../utils/use_timeline_click';

const MySpinner = styled(EuiLoadingSpinner)`
position: absolute;
Expand Down Expand Up @@ -53,8 +47,7 @@ export const AddComment = React.memo<AddCommentProps>(
options: { stripEmptyFields: false },
schema,
});
const dispatch = useDispatch();
const apolloClient = useApolloClient();

const { handleCursorChange, handleOnTimelineChange } = useInsertTimeline<CommentRequest>(
form,
'comment'
Expand All @@ -68,30 +61,9 @@ export const AddComment = React.memo<AddCommentProps>(
`${comment}${comment.length > 0 ? '\n\n' : ''}${insertQuote}`
);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [insertQuote]);
}, [form, insertQuote]);

const handleTimelineClick = useCallback(
(timelineId: string) => {
queryTimelineById({
apolloClient,
timelineId,
updateIsLoading: ({
id: currentTimelineId,
isLoading: isLoadingTimeline,
}: {
id: string;
isLoading: boolean;
}) =>
dispatch(
dispatchUpdateIsLoading({ id: currentTimelineId, isLoading: isLoadingTimeline })
),
updateTimeline: dispatchUpdateTimeline(dispatch),
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[apolloClient]
);
const handleTimelineClick = useTimelineClick();

const onSubmit = useCallback(async () => {
const { isValid, data } = await form.submit();
Expand All @@ -102,8 +74,8 @@ export const AddComment = React.memo<AddCommentProps>(
postComment(data, onCommentPosted);
form.reset();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [form, onCommentPosted, onCommentSaving]);
}, [form, onCommentPosted, onCommentSaving, postComment]);

return (
<span id="add-comment-permLink">
{isLoading && showLoading && <MySpinner data-test-subj="loading-spinner" size="xl" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,6 @@ const useGetCasesMock = useGetCases as jest.Mock;
const useGetCasesStatusMock = useGetCasesStatus as jest.Mock;
const useUpdateCasesMock = useUpdateCases as jest.Mock;

jest.mock('react-router-dom', () => {
const originalModule = jest.requireActual('react-router-dom');
return {
...originalModule,
useHistory: jest.fn(),
};
});

jest.mock('../../../common/components/link_to');

describe('AllCases', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
*/
/* eslint-disable react-hooks/exhaustive-deps */
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useHistory } from 'react-router-dom';
import {
EuiBasicTable,
EuiContextMenuPanel,
Expand Down Expand Up @@ -50,6 +49,8 @@ import { ConfigureCaseButton } from '../configure_cases/button';
import { ERROR_PUSH_SERVICE_CALLOUT_TITLE } from '../use_push_to_service/translations';
import { LinkButton } from '../../../common/components/links';
import { SecurityPageName } from '../../../app/types';
import { useKibana } from '../../../common/lib/kibana';
import { APP_ID } from '../../../../common/constants';

const Div = styled.div`
margin-top: ${({ theme }) => theme.eui.paddingSizes.m};
Expand Down Expand Up @@ -81,13 +82,13 @@ const getSortField = (field: string): SortFieldCase => {
};

interface AllCasesProps {
onRowClick?: (id: string) => void;
onRowClick?: (id?: string) => void;
isModal?: boolean;
userCanCrud: boolean;
}
export const AllCases = React.memo<AllCasesProps>(
({ onRowClick = () => {}, isModal = false, userCanCrud }) => {
const history = useHistory();
({ onRowClick, isModal = false, userCanCrud }) => {
const { navigateToApp } = useKibana().services.application;
const { formatUrl, search: urlSearch } = useFormatUrl(SecurityPageName.case);
const { actionLicense } = useGetActionLicense();
const {
Expand Down Expand Up @@ -234,9 +235,15 @@ export const AllCases = React.memo<AllCasesProps>(
const goToCreateCase = useCallback(
(ev) => {
ev.preventDefault();
history.push(getCreateCaseUrl(urlSearch));
if (isModal && onRowClick != null) {
onRowClick();
} else {
navigateToApp(`${APP_ID}:${SecurityPageName.case}`, {
path: getCreateCaseUrl(urlSearch),
});
}
},
[history, urlSearch]
[navigateToApp, isModal, onRowClick, urlSearch]
);

const actions = useMemo(
Expand Down Expand Up @@ -445,7 +452,11 @@ export const AllCases = React.memo<AllCasesProps>(
rowProps={(item) =>
isModal
? {
onClick: () => onRowClick(item.id),
onClick: () => {
if (onRowClick != null) {
onRowClick(item.id);
}
},
}
: {}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import * as i18n from './translations';
interface AllCasesModalProps {
onCloseCaseModal: () => void;
showCaseModal: boolean;
onRowClick: (id: string) => void;
onRowClick: (id?: string) => void;
}

export const AllCasesModalComponent = ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import * as i18n from '../../translations';
import { MarkdownEditorForm } from '../../../common/components//markdown_editor/form';
import { useGetTags } from '../../containers/use_get_tags';
import { getCaseDetailsUrl } from '../../../common/components/link_to';
import { useTimelineClick } from '../utils/use_timeline_click';

export const CommonUseField = getUseField({ component: Field });

Expand Down Expand Up @@ -87,15 +88,15 @@ export const Create = React.memo(() => {
form,
'description'
);
const handleTimelineClick = useTimelineClick();

const onSubmit = useCallback(async () => {
const { isValid, data } = await form.submit();
if (isValid) {
// `postCase`'s type is incorrect, it actually returns a promise
await postCase(data);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [form]);
}, [form, postCase]);

const handleSetIsCancel = useCallback(() => {
history.push('/');
Expand Down Expand Up @@ -145,6 +146,7 @@ export const Create = React.memo(() => {
dataTestSubj: 'caseDescription',
idAria: 'caseDescription',
isDisabled: isLoading,
onClickTimeline: handleTimelineClick,
onCursorPositionUpdate: handleCursorChange,
topRightContent: (
<InsertTimelinePopover
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ describe('UserActionMarkdown ', () => {

expect(queryTimelineByIdSpy).toBeCalledWith({
apolloClient: mockUseApolloClient(),
graphEventId: '',
timelineId,
updateIsLoading: expect.any(Function),
updateTimeline: expect.any(Function),
Expand All @@ -62,6 +63,7 @@ describe('UserActionMarkdown ', () => {
wrapper.find(`[data-test-subj="markdown-timeline-link"]`).first().simulate('click');
expect(queryTimelineByIdSpy).toBeCalledWith({
apolloClient: mockUseApolloClient(),
graphEventId: '',
timelineId,
updateIsLoading: expect.any(Function),
updateTimeline: expect.any(Function),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,14 @@ import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty, EuiButton } from '@elastic/e
import React, { useCallback } from 'react';
import styled, { css } from 'styled-components';

import { useDispatch } from 'react-redux';
import * as i18n from '../case_view/translations';
import { Markdown } from '../../../common/components/markdown';
import { Form, useForm, UseField } from '../../../shared_imports';
import { schema, Content } from './schema';
import { InsertTimelinePopover } from '../../../timelines/components/timeline/insert_timeline_popover';
import { useInsertTimeline } from '../../../timelines/components/timeline/insert_timeline_popover/use_insert_timeline';
import { MarkdownEditorForm } from '../../../common/components//markdown_editor/form';
import {
dispatchUpdateTimeline,
queryTimelineById,
} from '../../../timelines/components/open_timeline/helpers';

import { updateIsLoading as dispatchUpdateIsLoading } from '../../../timelines/store/timeline/actions';
import { useApolloClient } from '../../../common/utils/apollo_context';
import { useTimelineClick } from '../utils/use_timeline_click';

const ContentWrapper = styled.div`
${({ theme }) => css`
Expand All @@ -44,8 +37,6 @@ export const UserActionMarkdown = ({
onChangeEditable,
onSaveContent,
}: UserActionMarkdownProps) => {
const dispatch = useDispatch();
const apolloClient = useApolloClient();
const { form } = useForm<Content>({
defaultValue: { content },
options: { stripEmptyFields: false },
Expand All @@ -59,24 +50,7 @@ export const UserActionMarkdown = ({
onChangeEditable(id);
}, [id, onChangeEditable]);

const handleTimelineClick = useCallback(
(timelineId: string) => {
queryTimelineById({
apolloClient,
timelineId,
updateIsLoading: ({
id: currentTimelineId,
isLoading,
}: {
id: string;
isLoading: boolean;
}) => dispatch(dispatchUpdateIsLoading({ id: currentTimelineId, isLoading })),
updateTimeline: dispatchUpdateTimeline(dispatch),
});
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[apolloClient]
);
const handleTimelineClick = useTimelineClick();

const handleSaveAction = useCallback(async () => {
const { isValid, data } = await form.submit();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { useCallback } from 'react';
import { useDispatch } from 'react-redux';
import { useApolloClient } from '../../../common/utils/apollo_context';
import {
dispatchUpdateTimeline,
queryTimelineById,
} from '../../../timelines/components/open_timeline/helpers';
import { updateIsLoading as dispatchUpdateIsLoading } from '../../../timelines/store/timeline/actions';

export const useTimelineClick = () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

const dispatch = useDispatch();
const apolloClient = useApolloClient();

const handleTimelineClick = useCallback(
(timelineId: string, graphEventId?: string) => {
queryTimelineById({
apolloClient,
graphEventId,
timelineId,
updateIsLoading: ({
id: currentTimelineId,
isLoading,
}: {
id: string;
isLoading: boolean;
}) => dispatch(dispatchUpdateIsLoading({ id: currentTimelineId, isLoading })),
updateTimeline: dispatchUpdateTimeline(dispatch),
});
},
[apolloClient, dispatch]
);

return handleTimelineClick;
};
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ const EventsViewerComponent: React.FC<Props> = ({

useEffect(() => {
setIsTimelineLoading({ id, isLoading: isQueryLoading });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isQueryLoading]);
}, [id, isQueryLoading, setIsTimelineLoading]);

const { queryFields, title, unit } = useMemo(() => getManageTimelineById(id), [
getManageTimelineById,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,19 @@ describe('Markdown', () => {
);
wrapper.find('[data-test-subj="markdown-timeline-link"]').first().simulate('click');

expect(onClickTimeline).toHaveBeenCalledWith(timelineId);
expect(onClickTimeline).toHaveBeenCalledWith(timelineId, '');
});

test('timeline link onClick calls onClickTimeline with timelineId and graphEventId', () => {
const graphEventId = '2bc51864784c';
const markdownWithTimelineAndGraphEventLink = `A link to a timeline [timeline](http://localhost:5601/app/siem#/timelines?timeline=(id:'${timelineId}',isOpen:!t,graphEventId:'${graphEventId}'))`;

const wrapper = mount(
<Markdown raw={markdownWithTimelineAndGraphEventLink} onClickTimeline={onClickTimeline} />
);
wrapper.find('[data-test-subj="markdown-timeline-link"]').first().simulate('click');

expect(onClickTimeline).toHaveBeenCalledWith(timelineId, graphEventId);
});
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/* eslint-disable react/display-name */

import { EuiLink, EuiTableRow, EuiTableRowCell, EuiText, EuiToolTip } from '@elastic/eui';
import { clone } from 'lodash/fp';
import React from 'react';
import ReactMarkdown from 'react-markdown';
import styled, { css } from 'styled-components';
Expand Down Expand Up @@ -38,7 +39,7 @@ const REL_NOREFERRER = 'noreferrer';
export const Markdown = React.memo<{
disableLinks?: boolean;
raw?: string;
onClickTimeline?: (timelineId: string) => void;
onClickTimeline?: (timelineId: string, graphEventId?: string) => void;
size?: 'xs' | 's' | 'm';
}>(({ disableLinks = false, onClickTimeline, raw, size = 's' }) => {
const markdownRenderers = {
Expand All @@ -63,11 +64,14 @@ export const Markdown = React.memo<{
),
link: ({ children, href }: { children: React.ReactNode[]; href?: string }) => {
if (onClickTimeline != null && href != null && href.indexOf(`timelines?timeline=(id:`) > -1) {
const timelineId = href.split('timelines?timeline=(id:')[1].split("'")[1] ?? '';
const timelineId = clone(href).split('timeline=(id:')[1].split("'")[1] ?? '';
const graphEventId = href.includes('graphEventId:')
? clone(href).split('graphEventId:')[1].split("'")[1] ?? ''
: '';
return (
<EuiToolTip content={i18n.TIMELINE_ID(timelineId)}>
<EuiLink
onClick={() => onClickTimeline(timelineId)}
onClick={() => onClickTimeline(timelineId, graphEventId)}
data-test-subj="markdown-timeline-link"
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ interface IMarkdownEditorForm {
field: FieldHook;
idAria: string;
isDisabled: boolean;
onClickTimeline?: (timelineId: string) => void;
onClickTimeline?: (timelineId: string, graphEventId?: string) => void;
onCursorPositionUpdate?: (cursorPosition: CursorPosition) => void;
placeholder?: string;
topRightContent?: React.ReactNode;
Expand Down
Loading