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

[7.x] [Security Solution] [Detections] Disable edit button when user does not have actions privileges w/ rule + actions (#80220) #81055

Merged
merged 1 commit into from
Oct 20, 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
@@ -0,0 +1,49 @@
/*
* 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 { Rule } from '../../../detections/containers/detection_engine/rules';
import * as i18n from '../../../detections/pages/detection_engine/rules/translations';
import { isMlRule } from '../../../../common/machine_learning/helpers';
import * as detectionI18n from '../../../detections/pages/detection_engine/translations';

export const isBoolean = (obj: unknown): obj is boolean => typeof obj === 'boolean';

export const canEditRuleWithActions = (
rule: Rule | null | undefined,
privileges:
| boolean
| Readonly<{
[x: string]: boolean;
}>
): boolean => {
if (rule == null) {
return true;
}
if (rule.actions?.length > 0 && isBoolean(privileges)) {
return privileges;
}
return true;
};

export const getToolTipContent = (
rule: Rule | null | undefined,
hasMlPermissions: boolean,
hasReadActionsPrivileges:
| boolean
| Readonly<{
[x: string]: boolean;
}>
): string | undefined => {
if (rule == null) {
return undefined;
} else if (isMlRule(rule.type) && !hasMlPermissions) {
return detectionI18n.ML_RULES_DISABLED_MESSAGE;
} else if (!canEditRuleWithActions(rule, hasReadActionsPrivileges)) {
return i18n.EDIT_RULE_SETTINGS_TOOLTIP;
} else {
return undefined;
}
};

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 @@ -29,7 +29,11 @@ describe('RuleActionsOverflow', () => {
describe('snapshots', () => {
test('renders correctly against snapshot', () => {
const wrapper = shallow(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
expect(wrapper).toMatchSnapshot();
});
Expand All @@ -38,7 +42,11 @@ describe('RuleActionsOverflow', () => {
describe('rules details menu panel', () => {
test('there is at least one item when there is a rule within the rules-details-menu-panel', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -51,7 +59,13 @@ describe('RuleActionsOverflow', () => {
});

test('items are empty when there is a null rule within the rules-details-menu-panel', () => {
const wrapper = mount(<RuleActionsOverflow rule={null} userHasNoPermissions={false} />);
const wrapper = mount(
<RuleActionsOverflow
rule={null}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
expect(
Expand All @@ -60,7 +74,13 @@ describe('RuleActionsOverflow', () => {
});

test('items are empty when there is an undefined rule within the rules-details-menu-panel', () => {
const wrapper = mount(<RuleActionsOverflow rule={null} userHasNoPermissions={false} />);
const wrapper = mount(
<RuleActionsOverflow
rule={null}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
expect(
Expand All @@ -70,7 +90,11 @@ describe('RuleActionsOverflow', () => {

test('it opens the popover when rules-details-popover-button-icon is clicked', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -83,7 +107,11 @@ describe('RuleActionsOverflow', () => {
describe('rules details pop over button icon', () => {
test('it does not open the popover when rules-details-popover-button-icon is clicked when the user does not have permission', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={true} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={true}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -96,7 +124,13 @@ describe('RuleActionsOverflow', () => {
describe('rules details duplicate rule', () => {
test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => {
const rule = mockRule('id');
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={true} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={true}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="rules-details-delete-rule"] button').exists()).toEqual(
Expand All @@ -106,7 +140,11 @@ describe('RuleActionsOverflow', () => {

test('it opens the popover when rules-details-popover-button-icon is clicked', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -117,7 +155,11 @@ describe('RuleActionsOverflow', () => {

test('it closes the popover when rules-details-duplicate-rule is clicked', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -130,7 +172,11 @@ describe('RuleActionsOverflow', () => {

test('it calls duplicateRulesAction when rules-details-duplicate-rule is clicked', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -141,7 +187,13 @@ describe('RuleActionsOverflow', () => {

test('it calls duplicateRulesAction with the rule and rule.id when rules-details-duplicate-rule is clicked', () => {
const rule = mockRule('id');
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={false} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
wrapper.find('[data-test-subj="rules-details-duplicate-rule"] button').simulate('click');
Expand All @@ -158,7 +210,13 @@ describe('RuleActionsOverflow', () => {
describe('rules details export rule', () => {
test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => {
const rule = mockRule('id');
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={true} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={true}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="rules-details-export-rule"] button').exists()).toEqual(
Expand All @@ -168,7 +226,11 @@ describe('RuleActionsOverflow', () => {

test('it closes the popover when rules-details-export-rule is clicked', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -181,7 +243,13 @@ describe('RuleActionsOverflow', () => {

test('it sets the rule.rule_id on the generic downloader when rules-details-export-rule is clicked', () => {
const rule = mockRule('id');
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={false} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click');
Expand All @@ -194,7 +262,13 @@ describe('RuleActionsOverflow', () => {
test('it does not close the pop over on rules-details-export-rule when the rule is an immutable rule and the user does a click', () => {
const rule = mockRule('id');
rule.immutable = true;
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={false} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click');
Expand All @@ -207,7 +281,13 @@ describe('RuleActionsOverflow', () => {
test('it does not set the rule.rule_id on rules-details-export-rule when the rule is an immutable rule', () => {
const rule = mockRule('id');
rule.immutable = true;
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={false} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
wrapper.find('[data-test-subj="rules-details-export-rule"] button').simulate('click');
Expand All @@ -221,7 +301,13 @@ describe('RuleActionsOverflow', () => {
describe('rules details delete rule', () => {
test('it does not open the popover when rules-details-popover-button-icon is clicked and the user does not have permission', () => {
const rule = mockRule('id');
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={true} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={true}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
expect(wrapper.find('[data-test-subj="rules-details-delete-rule"] button').exists()).toEqual(
Expand All @@ -231,7 +317,11 @@ describe('RuleActionsOverflow', () => {

test('it closes the popover when rules-details-delete-rule is clicked', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -244,7 +334,11 @@ describe('RuleActionsOverflow', () => {

test('it calls deleteRulesAction when rules-details-delete-rule is clicked', () => {
const wrapper = mount(
<RuleActionsOverflow rule={mockRule('id')} userHasNoPermissions={false} />
<RuleActionsOverflow
rule={mockRule('id')}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
Expand All @@ -255,7 +349,13 @@ describe('RuleActionsOverflow', () => {

test('it calls deleteRulesAction with the rule.id when rules-details-delete-rule is clicked', () => {
const rule = mockRule('id');
const wrapper = mount(<RuleActionsOverflow rule={rule} userHasNoPermissions={false} />);
const wrapper = mount(
<RuleActionsOverflow
rule={rule}
userHasNoPermissions={false}
canDuplicateRuleWithActions={true}
/>
);
wrapper.find('[data-test-subj="rules-details-popover-button-icon"] button').simulate('click');
wrapper.update();
wrapper.find('[data-test-subj="rules-details-delete-rule"] button').simulate('click');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '../../../pages/detection_engine/rules/all/actions';
import { GenericDownloader } from '../../../../common/components/generic_downloader';
import { getRulesUrl } from '../../../../common/components/link_to/redirect_to_detection_engine';
import { getToolTipContent } from '../../../../common/utils/privileges';

const MyEuiButtonIcon = styled(EuiButtonIcon)`
&.euiButtonIcon {
Expand All @@ -41,6 +42,7 @@ const MyEuiButtonIcon = styled(EuiButtonIcon)`
interface RuleActionsOverflowComponentProps {
rule: Rule | null;
userHasNoPermissions: boolean;
canDuplicateRuleWithActions: boolean;
}

/**
Expand All @@ -49,6 +51,7 @@ interface RuleActionsOverflowComponentProps {
const RuleActionsOverflowComponent = ({
rule,
userHasNoPermissions,
canDuplicateRuleWithActions,
}: RuleActionsOverflowComponentProps) => {
const [isPopoverOpen, setIsPopoverOpen] = useState(false);
const [rulesToExport, setRulesToExport] = useState<string[]>([]);
Expand All @@ -66,14 +69,19 @@ const RuleActionsOverflowComponent = ({
<EuiContextMenuItem
key={i18nActions.DUPLICATE_RULE}
icon="copy"
disabled={userHasNoPermissions}
disabled={!canDuplicateRuleWithActions || userHasNoPermissions}
data-test-subj="rules-details-duplicate-rule"
onClick={async () => {
setIsPopoverOpen(false);
await duplicateRulesAction([rule], [rule.id], noop, dispatchToaster);
}}
>
{i18nActions.DUPLICATE_RULE}
<EuiToolTip
position="left"
content={getToolTipContent(rule, true, canDuplicateRuleWithActions)}
>
<>{i18nActions.DUPLICATE_RULE}</>
</EuiToolTip>
</EuiContextMenuItem>,
<EuiContextMenuItem
key={i18nActions.EXPORT_RULE}
Expand Down
Loading