Skip to content

Commit

Permalink
[Security Solution][Endpoint] Isolate Action should only be available…
Browse files Browse the repository at this point in the history
… to Platinum+ licenses (#102374) (#102434)

* Isolate action should only be available for platinum license
* Moved `useLicense` hook mock into `__mocks__`

Co-authored-by: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
  • Loading branch information
kibanamachine and paul-tavares committed Jun 17, 2021
1 parent 6f16229 commit 2dbb716
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 46 deletions.
21 changes: 21 additions & 0 deletions x-pack/plugins/security_solution/common/license/mocks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { LicenseService } from './license';

export const createLicenseServiceMock = (): jest.Mocked<LicenseService> => {
return ({
start: jest.fn(),
stop: jest.fn(),
getLicenseInformation: jest.fn(),
getLicenseInformation$: jest.fn(),
isAtLeast: jest.fn(),
isGoldPlus: jest.fn().mockReturnValue(true),
isPlatinumPlus: jest.fn().mockReturnValue(true),
isEnterprise: jest.fn().mockReturnValue(true),
} as unknown) as jest.Mocked<LicenseService>;
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { createLicenseServiceMock } from '../../../../common/license/mocks';

export const licenseService = createLicenseServiceMock();
export const useLicense = () => licenseService;
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ import React from 'react';
import { act } from '@testing-library/react';
import { endpointPageHttpMock } from '../../../mocks';
import { fireEvent } from '@testing-library/dom';
import { licenseService } from '../../../../../../common/hooks/use_license';

jest.mock('../../../../../../common/lib/kibana');
jest.mock('../../../../../../common/hooks/use_license');

describe('When using the Endpoint Details Actions Menu', () => {
let render: () => Promise<ReturnType<AppContextTestRender['render']>>;
Expand Down Expand Up @@ -112,4 +114,25 @@ describe('When using the Endpoint Details Actions Menu', () => {
expect(coreStart.application.navigateToApp).toHaveBeenCalled();
});
});

describe('and license is NOT PlatinumPlus', () => {
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;

beforeEach(() => licenseServiceMock.isPlatinumPlus.mockReturnValue(false));

afterEach(() => licenseServiceMock.isPlatinumPlus.mockReturnValue(true));

it('should not show the `isoalte` action', async () => {
setEndpointMetadataResponse();
await render();
expect(renderResult.queryByTestId('isolateLink')).toBeNull();
});

it('should still show `unisolate` action for endpoints that are currently isolated', async () => {
setEndpointMetadataResponse(true);
await render();
expect(renderResult.queryByTestId('isolateLink')).toBeNull();
expect(renderResult.getByTestId('unIsolateLink')).not.toBeNull();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { useEndpointSelector } from './hooks';
import { agentPolicies, uiQueryParams } from '../../store/selectors';
import { useKibana } from '../../../../../common/lib/kibana';
import { ContextMenuItemNavByRouterProps } from '../components/context_menu_item_nav_by_rotuer';
import { isEndpointHostIsolated } from '../../../../../common/utils/validators/is_endpoint_host_isolated';
import { isEndpointHostIsolated } from '../../../../../common/utils/validators';
import { useLicense } from '../../../../../common/hooks/use_license';

/**
* Returns a list (array) of actions for an individual endpoint
Expand All @@ -26,6 +27,7 @@ import { isEndpointHostIsolated } from '../../../../../common/utils/validators/i
export const useEndpointActionItems = (
endpointMetadata: MaybeImmutable<HostMetadata> | undefined
): ContextMenuItemNavByRouterProps[] => {
const isPlatinumPlus = useLicense().isPlatinumPlus();
const { formatUrl } = useFormatUrl(SecurityPageName.administration);
const fleetAgentPolicies = useEndpointSelector(agentPolicies);
const allCurrentUrlParams = useEndpointSelector(uiQueryParams);
Expand Down Expand Up @@ -58,40 +60,48 @@ export const useEndpointActionItems = (
selected_endpoint: endpointId,
});

const isolationActions = [];

if (isIsolated) {
// Un-isolate is always available to users regardless of license level
isolationActions.push({
'data-test-subj': 'unIsolateLink',
icon: 'logoSecurity',
key: 'unIsolateHost',
navigateAppId: MANAGEMENT_APP_ID,
navigateOptions: {
path: endpointUnIsolatePath,
},
href: formatUrl(endpointUnIsolatePath),
children: (
<FormattedMessage
id="xpack.securitySolution.endpoint.actions.unIsolateHost"
defaultMessage="Unisolate host"
/>
),
});
} else if (isPlatinumPlus) {
// For Platinum++ licenses, users also have ability to isolate
isolationActions.push({
'data-test-subj': 'isolateLink',
icon: 'logoSecurity',
key: 'isolateHost',
navigateAppId: MANAGEMENT_APP_ID,
navigateOptions: {
path: endpointIsolatePath,
},
href: formatUrl(endpointIsolatePath),
children: (
<FormattedMessage
id="xpack.securitySolution.endpoint.actions.isolateHost"
defaultMessage="Isolate host"
/>
),
});
}

return [
isIsolated
? {
'data-test-subj': 'unIsolateLink',
icon: 'logoSecurity',
key: 'unIsolateHost',
navigateAppId: MANAGEMENT_APP_ID,
navigateOptions: {
path: endpointUnIsolatePath,
},
href: formatUrl(endpointUnIsolatePath),
children: (
<FormattedMessage
id="xpack.securitySolution.endpoint.actions.unIsolateHost"
defaultMessage="Unisolate host"
/>
),
}
: {
'data-test-subj': 'isolateLink',
icon: 'logoSecurity',
key: 'isolateHost',
navigateAppId: MANAGEMENT_APP_ID,
navigateOptions: {
path: endpointIsolatePath,
},
href: formatUrl(endpointIsolatePath),
children: (
<FormattedMessage
id="xpack.securitySolution.endpoint.actions.isolateHost"
defaultMessage="Isolate host"
/>
),
},
...isolationActions,
{
'data-test-subj': 'hostLink',
icon: 'logoSecurity',
Expand Down Expand Up @@ -183,5 +193,12 @@ export const useEndpointActionItems = (
}

return [];
}, [allCurrentUrlParams, endpointMetadata, fleetAgentPolicies, formatUrl, getUrlForApp]);
}, [
allCurrentUrlParams,
endpointMetadata,
fleetAgentPolicies,
formatUrl,
getUrlForApp,
isPlatinumPlus,
]);
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
isUninitialisedResourceState,
} from '../../../state';
import { getCurrentIsolationRequestState } from '../store/selectors';
import { licenseService } from '../../../../common/hooks/use_license';

// not sure why this can't be imported from '../../../../common/mock/formatted_relative';
// but sure enough it needs to be inline in this one file
Expand All @@ -59,6 +60,7 @@ jest.mock('../../policy/store/services/ingest', () => {
});

jest.mock('../../../../common/lib/kibana');
jest.mock('../../../../common/hooks/use_license');

describe('when on the endpoint list page', () => {
const docGenerator = new EndpointDocGenerator();
Expand All @@ -70,6 +72,9 @@ describe('when on the endpoint list page', () => {
let coreStart: AppContextTestRender['coreStart'];
let middlewareSpy: AppContextTestRender['middlewareSpy'];
let abortSpy: jest.SpyInstance;

(licenseService as jest.Mocked<typeof licenseService>).isPlatinumPlus.mockReturnValue(true);

beforeAll(() => {
const mockAbort = new AbortController();
mockAbort.abort();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,7 @@ import { policyListApiPathHandlers } from '../store/test_mock_utils';
import { licenseService } from '../../../../common/hooks/use_license';

jest.mock('../../../../common/components/link_to');
jest.mock('../../../../common/hooks/use_license', () => {
const licenseServiceInstance = {
isPlatinumPlus: jest.fn(),
};
return {
licenseService: licenseServiceInstance,
useLicense: () => {
return licenseServiceInstance;
},
};
});
jest.mock('../../../../common/hooks/use_license');

describe('Policy Details', () => {
type FindReactWrapperResponse = ReturnType<ReturnType<typeof render>['find']>;
Expand Down

0 comments on commit 2dbb716

Please sign in to comment.