Skip to content

Commit

Permalink
[SIEM] Add support for IP details flow target in url (#54546)
Browse files Browse the repository at this point in the history
  • Loading branch information
patrykkopycinski authored Jan 15, 2020
1 parent 884fe91 commit b758b78
Show file tree
Hide file tree
Showing 40 changed files with 412 additions and 681 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { TestProviders } from '../../../mock';
import { getEmptyStringTag } from '../../empty_value';
import { HostDetailsLink, IPDetailsLink } from '../../links';
import { useMountAppended } from '../../../utils/use_mount_appended';
import { FlowTarget } from '../../../graphql/types';

jest.mock('../../search_bar', () => ({
siemFilterManager: {
Expand Down Expand Up @@ -91,13 +92,15 @@ describe('PointToolTipContent', () => {

test('it returns IPDetailsLink if field is source.ip', () => {
const value = '127.0.0.1';
expect(getRenderedFieldValue('source.ip', value)).toStrictEqual(<IPDetailsLink ip={value} />);
expect(getRenderedFieldValue('source.ip', value)).toStrictEqual(
<IPDetailsLink ip={value} flowTarget={FlowTarget.source} />
);
});

test('it returns IPDetailsLink if field is destination.ip', () => {
const value = '127.0.0.1';
expect(getRenderedFieldValue('destination.ip', value)).toStrictEqual(
<IPDetailsLink ip={value} />
<IPDetailsLink ip={value} flowTarget={FlowTarget.destination} />
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DescriptionListStyled } from '../../page';
import { FeatureProperty } from '../types';
import { HostDetailsLink, IPDetailsLink } from '../../links';
import { DefaultFieldRenderer } from '../../field_renderers/field_renderers';
import { FlowTarget } from '../../../graphql/types';

interface PointToolTipContentProps {
contextId: string;
Expand Down Expand Up @@ -66,7 +67,8 @@ export const getRenderedFieldValue = (field: string, value: string) => {
} else if (['host.name'].includes(field)) {
return <HostDetailsLink hostName={value} />;
} else if (['source.ip', 'destination.ip'].includes(field)) {
return <IPDetailsLink ip={value} />;
const flowTarget = field.split('.')[0] as FlowTarget;
return <IPDetailsLink ip={value} flowTarget={flowTarget} />;
}
return <>{value}</>;
};

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import { mount, shallow } from 'enzyme';
import { clone } from 'lodash/fp';
import React from 'react';
import { ActionCreator } from 'typescript-fsa';

import { FlowDirection, FlowTarget } from '../../graphql/types';

Expand All @@ -21,9 +20,7 @@ describe('FlowTargetSelect Component', () => {
selectedDirection: FlowDirection.uniDirectional,
isLoading: false,
selectedTarget: FlowTarget.source,
updateFlowTargetAction: (jest.fn() as unknown) as ActionCreator<{
flowTarget: FlowTarget;
}>,
updateFlowTargetAction: jest.fn(),
};

describe('rendering', () => {
Expand Down Expand Up @@ -51,10 +48,7 @@ describe('FlowTargetSelect Component', () => {

wrapper.update();

// @ts-ignore property mock does not exists
expect(mockProps.updateFlowTargetAction.mock.calls[0][0]).toEqual({
flowTarget: 'destination',
});
expect(mockProps.updateFlowTargetAction.mock.calls[0][0]).toEqual('destination');
});

test('when selectedDirection=unidirectional only source/destination are options', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
*/

import { EuiSuperSelect } from '@elastic/eui';
import React, { useCallback } from 'react';
import { ActionCreator } from 'typescript-fsa';
import React from 'react';

import { FlowDirection, FlowTarget } from '../../graphql/types';

Expand Down Expand Up @@ -45,47 +44,31 @@ interface OwnProps {
selectedTarget: FlowTarget;
displayTextOverride?: string[];
selectedDirection?: FlowDirection;
updateFlowTargetAction: ActionCreator<{ flowTarget: FlowTarget }>;
updateFlowTargetAction: (flowTarget: FlowTarget) => void;
}

const onChangeTarget = (
flowTarget: FlowTarget,
updateFlowTargetSelectAction: ActionCreator<{ flowTarget: FlowTarget }>
) => {
updateFlowTargetSelectAction({ flowTarget });
};

export type FlowTargetSelectProps = OwnProps;

export const FlowTargetSelect = React.memo<FlowTargetSelectProps>(
({
id,
isLoading = false,
selectedDirection,
selectedTarget,
displayTextOverride = [],
updateFlowTargetAction,
}) => {
const handleChange = useCallback(
(newFlowTarget: FlowTarget) => onChangeTarget(newFlowTarget, updateFlowTargetAction),
[updateFlowTargetAction]
);

return (
<EuiSuperSelect
options={
selectedDirection
? toggleTargetOptions(id, displayTextOverride).filter(option =>
option.directions.includes(selectedDirection)
)
: toggleTargetOptions(id, displayTextOverride)
}
valueOfSelected={selectedTarget}
onChange={handleChange}
isLoading={isLoading}
/>
);
}
const FlowTargetSelectComponent: React.FC<FlowTargetSelectProps> = ({
id,
isLoading = false,
selectedDirection,
selectedTarget,
displayTextOverride = [],
updateFlowTargetAction,
}) => (
<EuiSuperSelect
options={
selectedDirection
? toggleTargetOptions(id, displayTextOverride).filter(option =>
option.directions.includes(selectedDirection)
)
: toggleTargetOptions(id, displayTextOverride)
}
valueOfSelected={selectedTarget}
onChange={updateFlowTargetAction}
isLoading={isLoading}
/>
);

FlowTargetSelect.displayName = 'FlowTargetSelect';
export const FlowTargetSelect = React.memo(FlowTargetSelectComponent);
24 changes: 12 additions & 12 deletions x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ const getDataProvider = ({
and: [],
});

const NonDecoratedIp = React.memo<{
const NonDecoratedIpComponent: React.FC<{
contextId: string;
eventId: string;
fieldName: string;
truncate?: boolean;
value: string | object | null | undefined;
}>(({ contextId, eventId, fieldName, truncate, value }) => (
}> = ({ contextId, eventId, fieldName, truncate, value }) => (
<DraggableWrapper
dataProvider={getDataProvider({ contextId, eventId, fieldName, address: value })}
key={`non-decorated-ip-draggable-wrapper-${getUniqueId({
Expand All @@ -87,17 +87,17 @@ const NonDecoratedIp = React.memo<{
}
truncate={truncate}
/>
));
);

NonDecoratedIp.displayName = 'NonDecoratedIp';
const NonDecoratedIp = React.memo(NonDecoratedIpComponent);

const AddressLinks = React.memo<{
const AddressLinksComponent: React.FC<{
addresses: string[];
contextId: string;
eventId: string;
fieldName: string;
truncate?: boolean;
}>(({ addresses, contextId, eventId, fieldName, truncate }) => (
}> = ({ addresses, contextId, eventId, fieldName, truncate }) => (
<>
{uniq(addresses).map(address => (
<DraggableWrapper
Expand All @@ -123,17 +123,17 @@ const AddressLinks = React.memo<{
/>
))}
</>
));
);

AddressLinks.displayName = 'AddressLinks';
const AddressLinks = React.memo(AddressLinksComponent);

export const FormattedIp = React.memo<{
const FormattedIpComponent: React.FC<{
contextId: string;
eventId: string;
fieldName: string;
truncate?: boolean;
value: string | object | null | undefined;
}>(({ contextId, eventId, fieldName, truncate, value }) => {
}> = ({ contextId, eventId, fieldName, truncate, value }) => {
if (isString(value) && !isEmpty(value)) {
try {
const addresses = JSON.parse(value);
Expand Down Expand Up @@ -173,6 +173,6 @@ export const FormattedIp = React.memo<{
/>
);
}
});
};

FormattedIp.displayName = 'FormattedIp';
export const FormattedIp = React.memo(FormattedIpComponent);

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,6 @@ describe('Port', () => {
.find('a')
.first()
.props().href
).toEqual('#/link-to/network/ip/10.1.2.3');
).toEqual('#/link-to/network/ip/10.1.2.3/source');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export const LinkToPage = React.memo<LinkToPageProps>(({ match }) => (
/>
<Route
component={RedirectToNetworkPage}
path={`${match.url}/:pageName(${SiemPageName.network})/ip/:detailName`}
path={`${match.url}/:pageName(${SiemPageName.network})/ip/:detailName/:flowTarget`}
/>
<Route
component={RedirectToDetectionEnginePage}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,32 @@ import { RouteComponentProps } from 'react-router-dom';

import { RedirectWrapper } from './redirect_wrapper';
import { SiemPageName } from '../../pages/home/types';
import { FlowTarget, FlowTargetSourceDest } from '../../graphql/types';

export type NetworkComponentProps = RouteComponentProps<{
detailName: string;
detailName?: string;
flowTarget?: string;
search: string;
}>;

export const RedirectToNetworkPage = ({
match: {
params: { detailName },
params: { detailName, flowTarget },
},
location: { search },
}: NetworkComponentProps) => (
<RedirectWrapper
to={
detailName
? `/${SiemPageName.network}/ip/${detailName}${search}`
? `/${SiemPageName.network}/ip/${detailName}/${flowTarget}${search}`
: `/${SiemPageName.network}${search}`
}
/>
);

const baseNetworkUrl = `#/link-to/${SiemPageName.network}`;
export const getNetworkUrl = () => baseNetworkUrl;
export const getIPDetailsUrl = (detailName: string) => `${baseNetworkUrl}/ip/${detailName}`;
export const getIPDetailsUrl = (
detailName: string,
flowTarget?: FlowTarget | FlowTargetSourceDest
) => `${baseNetworkUrl}/ip/${detailName}/${flowTarget || FlowTarget.source}`;
Original file line number Diff line number Diff line change
Expand Up @@ -50,23 +50,23 @@ describe('Custom Links', () => {
test('should render valid link to IP Details with ipv4 as the display text', () => {
const wrapper = mount(<IPDetailsLink ip={ipv4} />);
expect(wrapper.find('EuiLink').prop('href')).toEqual(
`#/link-to/network/ip/${encodeURIComponent(ipv4)}`
`#/link-to/network/ip/${encodeURIComponent(ipv4)}/source`
);
expect(wrapper.text()).toEqual(ipv4);
});

test('should render valid link to IP Details with child text as the display text', () => {
const wrapper = mount(<IPDetailsLink ip={ipv4}>{hostName}</IPDetailsLink>);
expect(wrapper.find('EuiLink').prop('href')).toEqual(
`#/link-to/network/ip/${encodeURIComponent(ipv4)}`
`#/link-to/network/ip/${encodeURIComponent(ipv4)}/source`
);
expect(wrapper.text()).toEqual(hostName);
});

test('should render valid link to IP Details with ipv6 as the display text', () => {
const wrapper = mount(<IPDetailsLink ip={ipv6} />);
expect(wrapper.find('EuiLink').prop('href')).toEqual(
`#/link-to/network/ip/${encodeURIComponent(ipv6Encoded)}`
`#/link-to/network/ip/${encodeURIComponent(ipv6Encoded)}/source`
);
expect(wrapper.text()).toEqual(ipv6);
});
Expand Down
32 changes: 18 additions & 14 deletions x-pack/legacy/plugins/siem/public/components/links/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,31 @@ import React from 'react';

import { encodeIpv6 } from '../../lib/helpers';
import { getHostDetailsUrl, getIPDetailsUrl } from '../link_to';
import { FlowTarget, FlowTargetSourceDest } from '../../graphql/types';

// Internal Links
export const HostDetailsLink = React.memo<{ children?: React.ReactNode; hostName: string }>(
({ children, hostName }) => (
<EuiLink href={getHostDetailsUrl(encodeURIComponent(hostName))}>
{children ? children : hostName}
</EuiLink>
)
const HostDetailsLinkComponent: React.FC<{ children?: React.ReactNode; hostName: string }> = ({
children,
hostName,
}) => (
<EuiLink href={getHostDetailsUrl(encodeURIComponent(hostName))}>
{children ? children : hostName}
</EuiLink>
);

HostDetailsLink.displayName = 'HostDetailsLink';
export const HostDetailsLink = React.memo(HostDetailsLinkComponent);

export const IPDetailsLink = React.memo<{ children?: React.ReactNode; ip: string }>(
({ children, ip }) => (
<EuiLink href={`${getIPDetailsUrl(encodeURIComponent(encodeIpv6(ip)))}`}>
{children ? children : ip}
</EuiLink>
)
const IPDetailsLinkComponent: React.FC<{
children?: React.ReactNode;
ip: string;
flowTarget?: FlowTarget | FlowTargetSourceDest;
}> = ({ children, ip, flowTarget = FlowTarget.source }) => (
<EuiLink href={`${getIPDetailsUrl(encodeURIComponent(encodeIpv6(ip)), flowTarget)}`}>
{children ? children : ip}
</EuiLink>
);

IPDetailsLink.displayName = 'IPDetailsLink';
export const IPDetailsLink = React.memo(IPDetailsLinkComponent);

// External Links
export const GoogleLink = React.memo<{ children?: React.ReactNode; link: string }>(
Expand Down
Loading

0 comments on commit b758b78

Please sign in to comment.