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 Solution][Lists] - Updates exception flyout edit error messages #126875

Merged
merged 4 commits into from
Mar 8, 2022
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 @@ -29,10 +29,21 @@ import {
EXCEPTION_ITEM_CONTAINER,
ADD_EXCEPTIONS_BTN,
EXCEPTION_FIELD_LIST,
EDIT_EXCEPTIONS_BTN,
EXCEPTION_EDIT_FLYOUT_SAVE_BTN,
EXCEPTION_FLYOUT_VERSION_CONFLICT,
EXCEPTION_FLYOUT_LIST_DELETED_ERROR,
} from '../../screens/exceptions';

import { DETECTIONS_RULE_MANAGEMENT_URL } from '../../urls/navigation';
import { cleanKibana, reload } from '../../tasks/common';
import {
createExceptionList,
createExceptionListItem,
updateExceptionListItem,
deleteExceptionList,
} from '../../tasks/api_calls/exceptions';
import { getExceptionList } from '../../objects/exception';

// NOTE: You might look at these tests and feel they're overkill,
// but the exceptions flyout has a lot of logic making it difficult
Expand All @@ -42,18 +53,28 @@ import { cleanKibana, reload } from '../../tasks/common';
describe('Exceptions flyout', () => {
before(() => {
cleanKibana();
loginAndWaitForPageWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
createCustomRule({ ...getNewRule(), index: ['exceptions-*'] });
reload();
goToRuleDetails();

cy.get(RULE_STATUS).should('have.text', '—');

// this is a made-up index that has just the necessary
// mappings to conduct tests, avoiding loading large
// amounts of data like in auditbeat_exceptions
esArchiverLoad('exceptions');

loginAndWaitForPageWithoutDateRange(DETECTIONS_RULE_MANAGEMENT_URL);
createExceptionList(getExceptionList(), getExceptionList().list_id).then((response) =>
createCustomRule({
...getNewRule(),
index: ['exceptions-*'],
exceptionLists: [
{
id: response.body.id,
list_id: getExceptionList().list_id,
type: getExceptionList().type,
namespace_type: getExceptionList().namespace_type,
},
],
})
);
reload();
goToRuleDetails();
cy.get(RULE_STATUS).should('have.text', '—');
goToExceptionsTab();
});

Expand All @@ -62,7 +83,12 @@ describe('Exceptions flyout', () => {
});

it('Does not overwrite values and-ed together', () => {
cy.get(ADD_EXCEPTIONS_BTN).click({ force: true });
cy.root()
.pipe(($el) => {
$el.find(ADD_EXCEPTIONS_BTN).trigger('click');
return $el.find(ADD_AND_BTN);
})
.should('be.visible');

// add multiple entries with invalid field values
addExceptionEntryFieldValue('agent.name', 0);
Expand All @@ -80,8 +106,12 @@ describe('Exceptions flyout', () => {
});

it('Does not overwrite values or-ed together', () => {
cy.get(ADD_EXCEPTIONS_BTN).click({ force: true });

cy.root()
.pipe(($el) => {
$el.find(ADD_EXCEPTIONS_BTN).trigger('click');
return $el.find(ADD_AND_BTN);
})
.should('be.visible');
// exception item 1
addExceptionEntryFieldValueOfItemX('agent.name', 0, 0);
cy.get(ADD_AND_BTN).click();
Expand Down Expand Up @@ -197,11 +227,89 @@ describe('Exceptions flyout', () => {
});

it('Contains custom index fields', () => {
cy.get(ADD_EXCEPTIONS_BTN).click({ force: true });

cy.root()
.pipe(($el) => {
$el.find(ADD_EXCEPTIONS_BTN).trigger('click');
return $el.find(ADD_AND_BTN);
})
.should('be.visible');
cy.get(FIELD_INPUT).eq(0).click({ force: true });
cy.get(EXCEPTION_FIELD_LIST).contains('unique_value.test');

closeExceptionBuilderFlyout();
});

describe('flyout errors', () => {
before(() => {
// create exception item via api
createExceptionListItem(getExceptionList().list_id, {
list_id: getExceptionList().list_id,
item_id: 'simple_list_item',
tags: [],
type: 'simple',
description: 'Test exception item',
name: 'Sample Exception List Item',
namespace_type: 'single',
entries: [
{
field: 'host.name',
operator: 'included',
type: 'match_any',
value: ['some host', 'another host'],
},
],
});

reload();
cy.get(RULE_STATUS).should('have.text', '—');
goToExceptionsTab();
});

context('When updating an item with version conflict', () => {
it('Displays version conflict error', () => {
cy.get(EDIT_EXCEPTIONS_BTN).should('be.visible');
cy.get(EDIT_EXCEPTIONS_BTN).click({ force: true });

// update exception item via api
updateExceptionListItem('simple_list_item', {
name: 'Updated item name',
item_id: 'simple_list_item',
tags: [],
type: 'simple',
description: 'Test exception item',
namespace_type: 'single',
entries: [
{
field: 'host.name',
operator: 'included',
type: 'match_any',
value: ['some host', 'another host'],
},
],
});

// try to save and see version conflict error
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).click({ force: true });

cy.get(EXCEPTION_FLYOUT_VERSION_CONFLICT).should('be.visible');

closeExceptionBuilderFlyout();
});
});

context('When updating an item for a list that has since been deleted', () => {
it('Displays missing exception list error', () => {
cy.get(EDIT_EXCEPTIONS_BTN).should('be.visible');
cy.get(EDIT_EXCEPTIONS_BTN).click({ force: true });

// delete exception list via api
deleteExceptionList(getExceptionList().list_id, getExceptionList().namespace_type);

// try to save and see error
cy.get(EXCEPTION_EDIT_FLYOUT_SAVE_BTN).click({ force: true });

cy.get(EXCEPTION_FLYOUT_LIST_DELETED_ERROR).should('be.visible');
});
});
});
});
11 changes: 11 additions & 0 deletions x-pack/plugins/security_solution/cypress/objects/exception.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,17 @@ export interface ExceptionList {
type: 'detection' | 'endpoint';
}

export interface ExceptionListItem {
description: string;
list_id: string;
item_id: string;
name: string;
namespace_type: 'single' | 'agnostic';
tags: string[];
type: 'simple';
entries: Array<{ field: string; operator: string; type: string; value: string[] }>;
}

export const getExceptionList = (): ExceptionList => ({
description: 'Test exception list description',
list_id: 'test_exception_list',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

export const EDIT_EXCEPTIONS_BTN = '[data-test-subj="exceptionsViewerEditBtn"]';

export const ADD_EXCEPTIONS_BTN = '[data-test-subj="exceptionsHeaderAddExceptionBtn"]';

export const CLOSE_ALERTS_CHECKBOX =
Expand Down Expand Up @@ -61,3 +63,10 @@ export const EXCEPTION_FIELD_LIST =
'[data-test-subj="comboBoxOptionsList fieldAutocompleteComboBox-optionsList"]';

export const EXCEPTION_FLYOUT_TITLE = '[data-test-subj="exception-flyout-title"]';

export const EXCEPTION_EDIT_FLYOUT_SAVE_BTN = '[data-test-subj="edit-exception-confirm-button"]';

export const EXCEPTION_FLYOUT_VERSION_CONFLICT =
'[data-test-subj="exceptionsFlyoutVersionConflict"]';

export const EXCEPTION_FLYOUT_LIST_DELETED_ERROR = '[data-test-subj="errorCalloutContainer"]';
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export const DETAILS_TITLE = '.euiDescriptionList__title';

export const EXCEPTIONS_TAB = '[data-test-subj="exceptionsTab"]';

export const EXCEPTIONS_TAB_SEARCH = '[data-test-subj="exceptionsHeaderSearch"]';

export const FALSE_POSITIVES_DETAILS = 'False positive examples';

export const INDEX_PATTERNS_DETAILS = 'Index patterns';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

import { ExceptionList } from '../../objects/exception';
import { ExceptionList, ExceptionListItem } from '../../objects/exception';

export const createExceptionList = (
exceptionList: ExceptionList,
Expand All @@ -23,3 +23,58 @@ export const createExceptionList = (
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});

export const createExceptionListItem = (
exceptionListId: string,
exceptionListItem?: ExceptionListItem
) =>
cy.request({
method: 'POST',
url: '/api/exception_lists/items',
body: {
list_id: exceptionListItem?.list_id ?? exceptionListId,
item_id: exceptionListItem?.item_id ?? 'simple_list_item',
tags: exceptionListItem?.tags ?? ['user added string for a tag', 'malware'],
type: exceptionListItem?.type ?? 'simple',
description: exceptionListItem?.description ?? 'This is a sample endpoint type exception',
name: exceptionListItem?.name ?? 'Sample Exception List Item',
entries: exceptionListItem?.entries ?? [
{
field: 'actingProcess.file.signer',
operator: 'excluded',
type: 'exists',
},
{
field: 'host.name',
operator: 'included',
type: 'match_any',
value: ['some host', 'another host'],
},
],
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});

export const updateExceptionListItem = (
exceptionListItemId: string,
exceptionListItemUpdate?: Partial<ExceptionListItem>
) =>
cy.request({
method: 'PUT',
url: '/api/exception_lists/items',
body: {
item_id: exceptionListItemId,
...exceptionListItemUpdate,
},
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});

export const deleteExceptionList = (listId: string, namespaceType: string) =>
cy.request({
method: 'DELETE',
url: `/api/exception_lists?list_id=${listId}&namespace_type=${namespaceType}`,
headers: { 'kbn-xsrf': 'cypress-creds' },
failOnStatusCode: false,
});
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ export const goToAlertsTab = () => {
};

export const goToExceptionsTab = () => {
cy.get(EXCEPTIONS_TAB).click();
cy.root()
.pipe(($el) => {
$el.find(EXCEPTIONS_TAB).trigger('click');
return $el.find(ADD_EXCEPTIONS_BTN);
})
.should('be.visible');
};

export const removeException = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -410,27 +410,35 @@ export const EditExceptionFlyout = memo(function EditExceptionFlyout({
</FlyoutCheckboxesSection>
</>
)}
{updateError != null && (
<EuiFlyoutFooter>
<ErrorCallout
http={http}
errorInfo={updateError}
rule={maybeRule}
onCancel={onCancel}
onSuccess={handleDissasociationSuccess}
onError={handleDissasociationError}
/>
</EuiFlyoutFooter>
)}
{hasVersionConflict && (
<EuiFlyoutFooter>
<EuiCallOut title={i18n.VERSION_CONFLICT_ERROR_TITLE} color="danger" iconType="alert">
<p>{i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}</p>
</EuiCallOut>
</EuiFlyoutFooter>
)}
{updateError == null && (
<EuiFlyoutFooter>

<EuiFlyoutFooter>
{hasVersionConflict && (
<>
<EuiCallOut
title={i18n.VERSION_CONFLICT_ERROR_TITLE}
color="danger"
iconType="alert"
data-test-subj="exceptionsFlyoutVersionConflict"
>
<p>{i18n.VERSION_CONFLICT_ERROR_DESCRIPTION}</p>
</EuiCallOut>
<EuiSpacer size="m" />
</>
)}
{updateError != null && (
<>
<ErrorCallout
http={http}
errorInfo={updateError}
rule={maybeRule}
onCancel={onCancel}
onSuccess={handleDissasociationSuccess}
onError={handleDissasociationError}
/>
<EuiSpacer size="m" />
</>
)}
{updateError === null && (
<FlyoutFooterGroup justifyContent="spaceBetween">
<EuiButtonEmpty data-test-subj="cancelExceptionAddButton" onClick={onCancel}>
{i18n.CANCEL}
Expand All @@ -446,8 +454,8 @@ export const EditExceptionFlyout = memo(function EditExceptionFlyout({
{i18n.EDIT_EXCEPTION_SAVE_BUTTON}
</EuiButton>
</FlyoutFooterGroup>
</EuiFlyoutFooter>
)}
)}
</EuiFlyoutFooter>
</EuiFlyout>
);
});