Skip to content

Commit

Permalink
[Securitysolution] Add Risk score missing privileges callout to enabl…
Browse files Browse the repository at this point in the history
…ement flyout (elastic#199804)

## Summary

The entity analytics enablement model doesn't show the missing
privileges warning for the Entity Risk score.

To fix it, I added to the model the same callout we display on the risk
engine page.

### What is not included
* Improvement to the risk engine callout
* Fix risk engine callout bugs
elastic/security-team#11138

### How to test it
* Create a user with no privileges except to the security solution app
and the `logs*` index
* Login and open the entity analytics page with the non-privileged user
* Click enable and check if the model displays the missing privileges
callout for the risk engine

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
  • Loading branch information
machadoum authored Nov 14, 2024
1 parent 94d7df3 commit 54c6144
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 54c6144

Please sign in to comment.