Skip to content

Commit

Permalink
[SIEM] Cases clean up Phase II (#61750)
Browse files Browse the repository at this point in the history
* allow case to work without security

* disable configure button + add call out if license does not match and if kibana config does not allow if

* add skeleton for crud in case

* fix link to timeline + disable action on case on read only + allow read only access to our saved object in siem + manage no x-pack.security + show msg when read-only + fix reporters bug

* add actions required in plugins

* review I

* review II

* review III

* review IV

* fix types

* review V
  • Loading branch information
XavierM authored Mar 31, 2020
1 parent 1687e8e commit 4083d66
Show file tree
Hide file tree
Showing 52 changed files with 755 additions and 330 deletions.

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 @@ -34,12 +34,18 @@ const MySpinner = styled(EuiLoadingSpinner)`
`;

interface Props {
disabled?: boolean;
isLoading: boolean;
title: string | React.ReactNode;
onSubmit: (title: string) => void;
}

const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title }) => {
const EditableTitleComponent: React.FC<Props> = ({
disabled = false,
onSubmit,
isLoading,
title,
}) => {
const [editMode, setEditMode] = useState(false);
const [changedTitle, onTitleChange] = useState<string>(typeof title === 'string' ? title : '');

Expand Down Expand Up @@ -104,6 +110,7 @@ const EditableTitleComponent: React.FC<Props> = ({ onSubmit, isLoading, title })
{isLoading && <MySpinner />}
{!isLoading && (
<MyEuiButtonIcon
isDisabled={disabled}
aria-label={i18n.EDIT_TITLE_ARIA(title as string)}
iconType="pencil"
onClick={onClickEditIcon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const useInsertTimeline = <T extends FormData>(form: FormHook<T>, fieldNa
});
const handleOnTimelineChange = useCallback(
(title: string, id: string | null) => {
const builtLink = `${basePath}/app/siem#/timelines?timeline=(id:${id},isOpen:!t)`;
const builtLink = `${basePath}/app/siem#/timelines?timeline=(id:'${id}',isOpen:!t)`;
const currentValue = form.getFormData()[fieldName];
const newValue: string = [
currentValue.slice(0, cursorPosition.start),
Expand Down
2 changes: 1 addition & 1 deletion x-pack/legacy/plugins/siem/public/containers/case/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export const getCases = async ({
signal,
}: FetchCasesProps): Promise<AllCases> => {
const query = {
reporters: filterOptions.reporters.map(r => r.username),
reporters: filterOptions.reporters.map(r => r.username ?? '').filter(r => r !== ''),
tags: filterOptions.tags,
...(filterOptions.status !== '' ? { status: filterOptions.status } : {}),
...(filterOptions.search.length > 0 ? { search: filterOptions.search } : {}),
Expand Down
2 changes: 1 addition & 1 deletion x-pack/legacy/plugins/siem/public/containers/case/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export enum SortFieldCase {
export interface ElasticUser {
readonly email?: string | null;
readonly fullName?: string | null;
readonly username: string;
readonly username?: string | null;
}

export interface FetchCasesProps extends ApiProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import { useCallback, useEffect, useState } from 'react';

import { isEmpty } from 'lodash/fp';
import { User } from '../../../../../../plugins/case/common/api';
import { errorToToaster, useStateToaster } from '../../components/toasters';
import { getReporters } from './api';
Expand Down Expand Up @@ -44,9 +45,12 @@ export const useGetReporters = (): UseGetReporters => {
});
try {
const response = await getReporters(abortCtrl.signal);
const myReporters = response
.map(r => (r.full_name == null || isEmpty(r.full_name) ? r.username ?? '' : r.full_name))
.filter(u => !isEmpty(u));
if (!didCancel) {
setReporterState({
reporters: response.map(r => r.full_name ?? r.username ?? 'N/A'),
reporters: myReporters,
respReporters: response,
isLoading: false,
isError: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => {
createdAt,
createdBy: {
fullName: createdBy.fullName ?? null,
username: createdBy?.username,
username: createdBy?.username ?? '',
},
comments: comments
.filter(c => {
Expand All @@ -168,14 +168,14 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => {
createdAt: c.createdAt,
createdBy: {
fullName: c.createdBy.fullName ?? null,
username: c.createdBy.username,
username: c.createdBy.username ?? '',
},
updatedAt: c.updatedAt,
updatedBy:
c.updatedBy != null
? {
fullName: c.updatedBy.fullName ?? null,
username: c.updatedBy.username,
username: c.updatedBy.username ?? '',
}
: null,
})),
Expand All @@ -187,7 +187,7 @@ const formatServiceRequestData = (myCase: Case): ServiceConnectorCaseParams => {
updatedBy != null
? {
fullName: updatedBy.fullName ?? null,
username: updatedBy.username,
username: updatedBy.username ?? '',
}
: null,
};
Expand Down
47 changes: 44 additions & 3 deletions x-pack/legacy/plugins/siem/public/lib/kibana/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,24 @@ export const useCurrentUser = (): AuthenticatedElasticUser | null => {
let didCancel = false;
const fetchData = async () => {
try {
const response = await security.authc.getCurrentUser();
if (!didCancel) {
setUser(convertToCamelCase<AuthenticatedUser, AuthenticatedElasticUser>(response));
if (security != null) {
const response = await security.authc.getCurrentUser();
if (!didCancel) {
setUser(convertToCamelCase<AuthenticatedUser, AuthenticatedElasticUser>(response));
}
} else {
setUser({
username: i18n.translate('xpack.siem.getCurrentUser.unknownUser', {
defaultMessage: 'Unknown',
}),
email: '',
fullName: '',
roles: [],
enabled: false,
authenticationRealm: { name: '', type: '' },
lookupRealm: { name: '', type: '' },
authenticationProvider: '',
});
}
} catch (error) {
if (!didCancel) {
Expand All @@ -81,3 +96,29 @@ export const useCurrentUser = (): AuthenticatedElasticUser | null => {
}, []);
return user;
};

export interface UseGetUserSavedObjectPermissions {
crud: boolean;
read: boolean;
}

export const useGetUserSavedObjectPermissions = () => {
const [
savedObjectsPermissions,
setSavedObjectsPermissions,
] = useState<UseGetUserSavedObjectPermissions | null>(null);
const uiCapabilities = useKibana().services.application.capabilities;

useEffect(() => {
const capabilitiesCanUserCRUD: boolean =
typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false;
const capabilitiesCanUserRead: boolean =
typeof uiCapabilities.siem.show === 'boolean' ? uiCapabilities.siem.show : false;
setSavedObjectsPermissions({
crud: capabilitiesCanUserCRUD,
read: capabilitiesCanUserRead,
});
}, [uiCapabilities]);

return savedObjectsPermissions;
};
36 changes: 27 additions & 9 deletions x-pack/legacy/plugins/siem/public/pages/case/case.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,34 @@
import React from 'react';

import { WrapperPage } from '../../components/wrapper_page';
import { AllCases } from './components/all_cases';
import { useGetUserSavedObjectPermissions } from '../../lib/kibana';
import { SpyRoute } from '../../utils/route/spy_routes';
import { AllCases } from './components/all_cases';

import { getSavedObjectReadOnly, CaseCallOut } from './components/callout';
import { CaseSavedObjectNoPermissions } from './saved_object_no_permissions';

const infoReadSavedObject = getSavedObjectReadOnly();

export const CasesPage = React.memo(() => {
const userPermissions = useGetUserSavedObjectPermissions();

export const CasesPage = React.memo(() => (
<>
<WrapperPage>
<AllCases />
</WrapperPage>
<SpyRoute />
</>
));
return userPermissions == null || userPermissions?.read ? (
<>
<WrapperPage>
{userPermissions != null && !userPermissions?.crud && userPermissions?.read && (
<CaseCallOut
title={infoReadSavedObject.title}
message={infoReadSavedObject.description}
/>
)}
<AllCases userCanCrud={userPermissions?.crud ?? false} />
</WrapperPage>
<SpyRoute />
</>
) : (
<CaseSavedObjectNoPermissions />
);
});

CasesPage.displayName = 'CasesPage';
28 changes: 21 additions & 7 deletions x-pack/legacy/plugins/siem/public/pages/case/case_details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,36 @@
*/

import React from 'react';
import { useParams } from 'react-router-dom';
import { useParams, Redirect } from 'react-router-dom';

import { CaseView } from './components/case_view';
import { useGetUrlSearch } from '../../components/navigation/use_get_url_search';
import { useGetUserSavedObjectPermissions } from '../../lib/kibana';
import { SpyRoute } from '../../utils/route/spy_routes';
import { getCaseUrl } from '../../components/link_to';
import { navTabs } from '../home/home_navigations';
import { CaseView } from './components/case_view';
import { getSavedObjectReadOnly, CaseCallOut } from './components/callout';

const infoReadSavedObject = getSavedObjectReadOnly();

export const CaseDetailsPage = React.memo(() => {
const userPermissions = useGetUserSavedObjectPermissions();
const { detailName: caseId } = useParams();
if (!caseId) {
return null;
const search = useGetUrlSearch(navTabs.case);

if (userPermissions != null && !userPermissions.read) {
return <Redirect to={getCaseUrl(search)} />;
}
return (

return caseId != null ? (
<>
<CaseView caseId={caseId} />
{userPermissions != null && !userPermissions?.crud && userPermissions?.read && (
<CaseCallOut title={infoReadSavedObject.title} message={infoReadSavedObject.description} />
)}
<CaseView caseId={caseId} userCanCrud={userPermissions?.crud ?? false} />
<SpyRoute />
</>
);
) : null;
});

CaseDetailsPage.displayName = 'CaseDetailsPage';
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,15 @@ const initialCommentValue: CommentRequest = {

interface AddCommentProps {
caseId: string;
disabled?: boolean;
insertQuote: string | null;
onCommentSaving?: () => void;
onCommentPosted: (newCase: Case) => void;
showLoading?: boolean;
}

export const AddComment = React.memo<AddCommentProps>(
({ caseId, insertQuote, showLoading = true, onCommentPosted, onCommentSaving }) => {
({ caseId, disabled, insertQuote, showLoading = true, onCommentPosted, onCommentSaving }) => {
const { isLoading, postComment } = usePostComment(caseId);
const { form } = useForm<CommentRequest>({
defaultValue: initialCommentValue,
Expand Down Expand Up @@ -87,7 +88,7 @@ export const AddComment = React.memo<AddCommentProps>(
bottomRightContent: (
<EuiButton
iconType="plusInCircle"
isDisabled={isLoading}
isDisabled={isLoading || disabled}
isLoading={isLoading}
onClick={onSubmit}
size="s"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,11 @@ export const getCasesColumns = (
<>
<EuiAvatar
className="userAction__circle"
name={createdBy.fullName ? createdBy.fullName : createdBy.username}
name={createdBy.fullName ? createdBy.fullName : createdBy.username ?? ''}
size="s"
/>
<Spacer data-test-subj="case-table-column-createdBy">
{createdBy.fullName ?? createdBy.username ?? 'N/A'}
{createdBy.fullName ?? createdBy.username ?? ''}
</Spacer>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ describe('AllCases', () => {
it('should render AllCases', () => {
const wrapper = mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
expect(
Expand Down Expand Up @@ -132,7 +132,7 @@ describe('AllCases', () => {
it('should tableHeaderSortButton AllCases', () => {
const wrapper = mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
wrapper
Expand All @@ -149,7 +149,7 @@ describe('AllCases', () => {
it('closes case when row action icon clicked', () => {
const wrapper = mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
wrapper
Expand Down Expand Up @@ -182,7 +182,7 @@ describe('AllCases', () => {

const wrapper = mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
wrapper
Expand Down Expand Up @@ -213,7 +213,7 @@ describe('AllCases', () => {

const wrapper = mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
wrapper
Expand All @@ -238,7 +238,7 @@ describe('AllCases', () => {

const wrapper = mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
wrapper
Expand All @@ -259,7 +259,7 @@ describe('AllCases', () => {

mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
expect(refetchCases).toBeCalled();
Expand All @@ -274,7 +274,7 @@ describe('AllCases', () => {

mount(
<TestProviders>
<AllCases />
<AllCases userCanCrud={true} />
</TestProviders>
);
expect(refetchCases).toBeCalled();
Expand Down
Loading

0 comments on commit 4083d66

Please sign in to comment.