Skip to content

Commit

Permalink
[8.x] [Securitysolution] Add Risk score missing privileges callout to…
Browse files Browse the repository at this point in the history
… enablement flyout (#199804) (#200123)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Securitysolution] Add Risk score missing privileges callout to
enablement flyout
(#199804)](#199804)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Pablo
Machado","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-11-14T08:40:27Z","message":"[Securitysolution]
Add Risk score missing privileges callout to enablement flyout
(#199804)\n\n## Summary\r\n\r\nThe entity analytics enablement model
doesn't show the missing\r\nprivileges warning for the Entity Risk
score.\r\n\r\nTo fix it, I added to the model the same callout we
display on the risk\r\nengine page.\r\n\r\n### What is not included\r\n*
Improvement to the risk engine callout\r\n* Fix risk engine callout
bugs\r\nhttps://github.com/elastic/security-team/issues/11138\r\n\r\n###
How to test it\r\n* Create a user with no privileges except to the
security solution app\r\nand the `logs*` index\r\n* Login and open the
entity analytics page with the non-privileged user\r\n* Click enable and
check if the model displays the missing privileges\r\ncallout for the
risk engine\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"54c6144bbc9e041dad5c2fd9f296ea7d838cc614","branchLabelMapping":{"^v9.0.0$":"main","^v8.17.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:skip","v9.0.0","Team:
SecuritySolution","Theme: entity_analytics","Feature:Entity
Analytics","Team:Entity
Analytics","backport:version","v8.17.0","v8.16.1"],"title":"[Securitysolution]
Add Risk score missing privileges callout to enablement
flyout","number":199804,"url":"https://github.com/elastic/kibana/pull/199804","mergeCommit":{"message":"[Securitysolution]
Add Risk score missing privileges callout to enablement flyout
(#199804)\n\n## Summary\r\n\r\nThe entity analytics enablement model
doesn't show the missing\r\nprivileges warning for the Entity Risk
score.\r\n\r\nTo fix it, I added to the model the same callout we
display on the risk\r\nengine page.\r\n\r\n### What is not included\r\n*
Improvement to the risk engine callout\r\n* Fix risk engine callout
bugs\r\nhttps://github.com/elastic/security-team/issues/11138\r\n\r\n###
How to test it\r\n* Create a user with no privileges except to the
security solution app\r\nand the `logs*` index\r\n* Login and open the
entity analytics page with the non-privileged user\r\n* Click enable and
check if the model displays the missing privileges\r\ncallout for the
risk engine\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"54c6144bbc9e041dad5c2fd9f296ea7d838cc614"}},"sourceBranch":"main","suggestedTargetBranches":["8.x","8.16"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/199804","number":199804,"mergeCommit":{"message":"[Securitysolution]
Add Risk score missing privileges callout to enablement flyout
(#199804)\n\n## Summary\r\n\r\nThe entity analytics enablement model
doesn't show the missing\r\nprivileges warning for the Entity Risk
score.\r\n\r\nTo fix it, I added to the model the same callout we
display on the risk\r\nengine page.\r\n\r\n### What is not included\r\n*
Improvement to the risk engine callout\r\n* Fix risk engine callout
bugs\r\nhttps://github.com/elastic/security-team/issues/11138\r\n\r\n###
How to test it\r\n* Create a user with no privileges except to the
security solution app\r\nand the `logs*` index\r\n* Login and open the
entity analytics page with the non-privileged user\r\n* Click enable and
check if the model displays the missing privileges\r\ncallout for the
risk engine\r\n\r\n### Checklist\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common
scenarios","sha":"54c6144bbc9e041dad5c2fd9f296ea7d838cc614"}},{"branch":"8.x","label":"v8.17.0","branchLabelMappingKey":"^v8.17.0$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"8.16","label":"v8.16.1","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Pablo Machado <[email protected]>
  • Loading branch information
kibanamachine and machadoum authored Nov 14, 2024
1 parent d9d93fa commit 6e7d436
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,21 @@
import React from 'react';
import { fireEvent, render, screen } from '@testing-library/react';
import { EntityStoreEnablementModal } from './enablement_modal';
import { useEntityEnginePrivileges } from '../hooks/use_entity_engine_privileges';
import { TestProviders } from '../../../../common/mock';
import type { EntityAnalyticsPrivileges } from '../../../../../common/api/entity_analytics';
import type { RiskEngineMissingPrivilegesResponse } from '../../../hooks/use_missing_risk_engine_privileges';

const mockToggle = jest.fn();
const mockEnableStore = jest.fn(() => jest.fn());

const mockUseEntityEnginePrivileges = jest.fn();
jest.mock('../hooks/use_entity_engine_privileges', () => ({
useEntityEnginePrivileges: jest.fn(),
useEntityEnginePrivileges: () => mockUseEntityEnginePrivileges(),
}));

const mockUseMissingRiskEnginePrivileges = jest.fn();
jest.mock('../../../hooks/use_missing_risk_engine_privileges', () => ({
useMissingRiskEnginePrivileges: () => mockUseMissingRiskEnginePrivileges(),
}));

const defaultProps = {
Expand All @@ -25,65 +33,144 @@ const defaultProps = {
entityStore: { disabled: false, checked: false },
};

const allEntityEnginePrivileges: EntityAnalyticsPrivileges = {
has_all_required: true,
privileges: {
elasticsearch: {
cluster: {
manage_enrich: true,
},
index: { 'logs-*': { read: false, view_index_metadata: true } },
},
kibana: {
'saved_object:entity-engine-status/all': true,
},
},
};

const missingEntityEnginePrivileges: EntityAnalyticsPrivileges = {
has_all_required: false,
privileges: {
elasticsearch: {
cluster: {
manage_enrich: false,
},
index: { 'logs-*': { read: false, view_index_metadata: false } },
},
kibana: {
'saved_object:entity-engine-status/all': false,
},
},
};

const allRiskEnginePrivileges: RiskEngineMissingPrivilegesResponse = {
hasAllRequiredPrivileges: true,
isLoading: false,
};

const missingRiskEnginePrivileges: RiskEngineMissingPrivilegesResponse = {
isLoading: false,
hasAllRequiredPrivileges: false,
missingPrivileges: {
clusterPrivileges: [],
indexPrivileges: [],
},
};

const renderComponent = (props = defaultProps) => {
return render(<EntityStoreEnablementModal {...props} />, { wrapper: TestProviders });
};

describe('EntityStoreEnablementModal', () => {
beforeEach(() => {
jest.clearAllMocks();
(useEntityEnginePrivileges as jest.Mock).mockReturnValue({
data: {
privileges: {
elasticsearch: {
index: {},
},
kibana: {},
},
},
isLoading: false,
});
});

it('should render the modal when visible is true', () => {
renderComponent();
expect(screen.getByRole('dialog')).toBeInTheDocument();
});
describe('with all privileges', () => {
beforeEach(() => {
mockUseEntityEnginePrivileges.mockReturnValue({
data: allEntityEnginePrivileges,
isLoading: false,
});

it('should not render the modal when visible is false', () => {
renderComponent({ ...defaultProps, visible: false });
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
mockUseMissingRiskEnginePrivileges.mockReturnValue(allRiskEnginePrivileges);
});

it('should call toggle function when cancel button is clicked', () => {
renderComponent();
fireEvent.click(screen.getByText('Cancel'));
expect(mockToggle).toHaveBeenCalledWith(false);
});
it('should render the modal when visible is true', () => {
renderComponent();
expect(screen.getByRole('dialog')).toBeInTheDocument();
});

it('should call enableStore function when enable button is clicked', () => {
renderComponent({
...defaultProps,
riskScore: { ...defaultProps.riskScore, checked: true },
entityStore: { ...defaultProps.entityStore, checked: true },
it('should not render the modal when visible is false', () => {
renderComponent({ ...defaultProps, visible: false });
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});
fireEvent.click(screen.getByText('Enable'));
expect(mockEnableStore).toHaveBeenCalledWith({ riskScore: true, entityStore: true });
});

it('should display proceed warning when no enablement options are selected', () => {
renderComponent();
expect(screen.getByText('Please enable at least one option to proceed.')).toBeInTheDocument();
it('should call toggle function when cancel button is clicked', () => {
renderComponent();
fireEvent.click(screen.getByText('Cancel'));
expect(mockToggle).toHaveBeenCalledWith(false);
});

it('should call enableStore function when enable button is clicked', () => {
renderComponent({
...defaultProps,
riskScore: { ...defaultProps.riskScore, checked: true },
entityStore: { ...defaultProps.entityStore, checked: true },
});
fireEvent.click(screen.getByText('Enable'));
expect(mockEnableStore).toHaveBeenCalledWith({ riskScore: true, entityStore: true });
});

it('should display proceed warning when no enablement options are selected', () => {
renderComponent();
expect(screen.getByText('Please enable at least one option to proceed.')).toBeInTheDocument();
});

it('should disable the enable button when enablementOptions are false', () => {
renderComponent({
...defaultProps,
riskScore: { ...defaultProps.riskScore, checked: false },
entityStore: { ...defaultProps.entityStore, checked: false },
});

const enableButton = screen.getByRole('button', { name: /Enable/i });
expect(enableButton).toBeDisabled();
});

it('should not show entity engine missing privileges warning when no missing privileges', () => {
renderComponent();
expect(
screen.queryByTestId('callout-missing-entity-store-privileges')
).not.toBeInTheDocument();
});

it('should not show risk engine missing privileges warning when no missing privileges', () => {
renderComponent();
expect(
screen.queryByTestId('callout-missing-risk-engine-privileges')
).not.toBeInTheDocument();
});
});

it('should disable the enable button when enablementOptions are false', () => {
renderComponent({
...defaultProps,
riskScore: { ...defaultProps.riskScore, checked: false },
entityStore: { ...defaultProps.entityStore, checked: false },
describe('with no privileges', () => {
beforeEach(() => {
mockUseEntityEnginePrivileges.mockReturnValue({
data: missingEntityEnginePrivileges,
isLoading: false,
});

mockUseMissingRiskEnginePrivileges.mockReturnValue(missingRiskEnginePrivileges);
});

it('should show entity engine missing privileges warning when missing privileges', () => {
renderComponent();
expect(screen.getByTestId('callout-missing-entity-store-privileges')).toBeInTheDocument();
});

const enableButton = screen.getByRole('button', { name: /Enable/i });
expect(enableButton).toBeDisabled();
it('should show risk engine missing privileges warning when missing privileges', () => {
renderComponent();
expect(screen.getByTestId('callout-missing-risk-engine-privileges')).toBeInTheDocument();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import {
} from '../translations';
import { useEntityEnginePrivileges } from '../hooks/use_entity_engine_privileges';
import { MissingPrivilegesCallout } from './missing_privileges_callout';
import { useMissingRiskEnginePrivileges } from '../../../hooks/use_missing_risk_engine_privileges';
import { RiskEnginePrivilegesCallOut } from '../../risk_engine_privileges_callout';

export interface Enablements {
riskScore: boolean;
Expand Down Expand Up @@ -66,7 +68,9 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp
riskScore: !!riskScore.checked,
entityStore: !!entityStore.checked,
});
const { data: privileges, isLoading: isLoadingPrivileges } = useEntityEnginePrivileges();
const { data: entityEnginePrivileges, isLoading: isLoadingEntityEnginePrivileges } =
useEntityEnginePrivileges();
const riskEnginePrivileges = useMissingRiskEnginePrivileges();
const enablementOptions = enablements.riskScore || enablements.entityStore;

if (!visible) {
Expand Down Expand Up @@ -105,15 +109,22 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp
/>
}
checked={enablements.riskScore}
disabled={riskScore.disabled || false}
disabled={
riskScore.disabled ||
(!riskEnginePrivileges.isLoading && !riskEnginePrivileges?.hasAllRequiredPrivileges)
}
onChange={() => setEnablements((prev) => ({ ...prev, riskScore: !prev.riskScore }))}
/>
</EuiFlexItem>
{!riskEnginePrivileges.isLoading && !riskEnginePrivileges.hasAllRequiredPrivileges && (
<EuiFlexItem>
<RiskEnginePrivilegesCallOut privileges={riskEnginePrivileges} />
</EuiFlexItem>
)}
<EuiFlexItem>
<EuiText>{ENABLEMENT_DESCRIPTION_RISK_ENGINE_ONLY}</EuiText>
</EuiFlexItem>
<EuiHorizontalRule margin="none" />

<EuiFlexItem>
<EuiFlexGroup justifyContent="flexStart">
<EuiSwitch
Expand All @@ -125,7 +136,8 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp
}
checked={enablements.entityStore}
disabled={
entityStore.disabled || (!isLoadingPrivileges && !privileges?.has_all_required)
entityStore.disabled ||
(!isLoadingEntityEnginePrivileges && !entityEnginePrivileges?.has_all_required)
}
onChange={() =>
setEnablements((prev) => ({ ...prev, entityStore: !prev.entityStore }))
Expand All @@ -136,9 +148,9 @@ export const EntityStoreEnablementModal: React.FC<EntityStoreEnablementModalProp
</EuiToolTip>
</EuiFlexGroup>
</EuiFlexItem>
{!privileges || privileges.has_all_required ? null : (
{!entityEnginePrivileges || entityEnginePrivileges.has_all_required ? null : (
<EuiFlexItem>
<MissingPrivilegesCallout privileges={privileges} />
<MissingPrivilegesCallout privileges={entityEnginePrivileges} />
</EuiFlexItem>
)}
<EuiFlexItem>
Expand Down

0 comments on commit 6e7d436

Please sign in to comment.