From 70d77323f49f0e5bcfda05b40384aca94e6100cc Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Tue, 15 Jun 2021 15:59:28 -0400 Subject: [PATCH 1/6] Isolate action should only be available for platinum license --- .../view/hooks/use_endpoint_action_items.tsx | 31 ++++++++++++++----- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index 31069b1939ce98..0555266bc39178 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -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 @@ -26,6 +27,7 @@ import { isEndpointHostIsolated } from '../../../../../common/utils/validators/i export const useEndpointActionItems = ( endpointMetadata: MaybeImmutable | undefined ): ContextMenuItemNavByRouterProps[] => { + const isPlatinumPlus = useLicense().isPlatinumPlus(); const { formatUrl } = useFormatUrl(SecurityPageName.administration); const fleetAgentPolicies = useEndpointSelector(agentPolicies); const allCurrentUrlParams = useEndpointSelector(uiQueryParams); @@ -58,9 +60,9 @@ export const useEndpointActionItems = ( selected_endpoint: endpointId, }); - return [ - isIsolated - ? { + const isolationActions = isIsolated // Un-isolate is always available to users regardless of license level + ? [ + { 'data-test-subj': 'unIsolateLink', icon: 'logoSecurity', key: 'unIsolateHost', @@ -75,8 +77,11 @@ export const useEndpointActionItems = ( defaultMessage="Unisolate host" /> ), - } - : { + }, + ] + : isPlatinumPlus // For Platinum++ licenses, users also have ability to isolate + ? [ + { 'data-test-subj': 'isolateLink', icon: 'logoSecurity', key: 'isolateHost', @@ -92,6 +97,11 @@ export const useEndpointActionItems = ( /> ), }, + ] + : []; + + return [ + ...isolationActions, { 'data-test-subj': 'hostLink', icon: 'logoSecurity', @@ -183,5 +193,12 @@ export const useEndpointActionItems = ( } return []; - }, [allCurrentUrlParams, endpointMetadata, fleetAgentPolicies, formatUrl, getUrlForApp]); + }, [ + allCurrentUrlParams, + endpointMetadata, + fleetAgentPolicies, + formatUrl, + getUrlForApp, + isPlatinumPlus, + ]); }; From f7f93ae498f751d6cd7f962a86866a2f3f2fe719 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Tue, 15 Jun 2021 17:08:40 -0400 Subject: [PATCH 2/6] Moved `useLicense` hook mock into `__mocks__` --- .../security_solution/common/license/mocks.ts | 21 +++++++++++++++++++ .../common/hooks/__mocks__/use_license.ts | 11 ++++++++++ .../pages/policy/view/policy_details.test.tsx | 12 +---------- 3 files changed, 33 insertions(+), 11 deletions(-) create mode 100644 x-pack/plugins/security_solution/common/license/mocks.ts create mode 100644 x-pack/plugins/security_solution/public/common/hooks/__mocks__/use_license.ts diff --git a/x-pack/plugins/security_solution/common/license/mocks.ts b/x-pack/plugins/security_solution/common/license/mocks.ts new file mode 100644 index 00000000000000..f352932b446139 --- /dev/null +++ b/x-pack/plugins/security_solution/common/license/mocks.ts @@ -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 => { + 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; +}; diff --git a/x-pack/plugins/security_solution/public/common/hooks/__mocks__/use_license.ts b/x-pack/plugins/security_solution/public/common/hooks/__mocks__/use_license.ts new file mode 100644 index 00000000000000..2d34e4b22a14e9 --- /dev/null +++ b/x-pack/plugins/security_solution/public/common/hooks/__mocks__/use_license.ts @@ -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; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index d01ccea5ba1f4a..1766048a3985aa 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -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['find']>; From c3540cc8677c6277fdd9c1a5e98e217d3758f347 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Wed, 16 Jun 2021 11:20:50 -0400 Subject: [PATCH 3/6] Tests for actions menu button to cover license level --- .../details/components/actions_menu.test.tsx | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx index 356d44a8105287..71b6732491b5dd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx @@ -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>; @@ -112,4 +114,21 @@ describe('When using the Endpoint Details Actions Menu', () => { expect(coreStart.application.navigateToApp).toHaveBeenCalled(); }); }); + + describe('and license is NOT PlatinumPlus', () => { + (licenseService as jest.Mocked).isPlatinumPlus.mockReturnValue(false); + + 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(); + }); + }); }); From b9773531310d2aaf21cd66663582c91ff1f8baa8 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Wed, 16 Jun 2021 14:19:12 -0400 Subject: [PATCH 4/6] Fix test (maybe) --- .../view/details/components/actions_menu.test.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx index 71b6732491b5dd..04708ea90cd349 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx @@ -116,7 +116,11 @@ describe('When using the Endpoint Details Actions Menu', () => { }); describe('and license is NOT PlatinumPlus', () => { - (licenseService as jest.Mocked).isPlatinumPlus.mockReturnValue(false); + const licenseServiceMock = licenseService as jest.Mocked; + + beforeEach(() => licenseServiceMock.isPlatinumPlus.mockReturnValue(false)); + + afterEach(() => licenseServiceMock.isPlatinumPlus.mockReturnValue(true)); it('should not show the `isoalte` action', async () => { setEndpointMetadataResponse(); From 87ea5cb3c2615bd085949f15c1acce2e9e111b85 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Wed, 16 Jun 2021 14:24:18 -0400 Subject: [PATCH 5/6] code review suggestion --- .../view/hooks/use_endpoint_action_items.tsx | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx index a24c747a83ae29..7c38c935a0b9f3 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx @@ -60,45 +60,45 @@ export const useEndpointActionItems = ( selected_endpoint: endpointId, }); - const isolationActions = isIsolated // Un-isolate is always available to users regardless of license level - ? [ - { - 'data-test-subj': 'unIsolateLink', - icon: 'logoSecurity', - key: 'unIsolateHost', - navigateAppId: MANAGEMENT_APP_ID, - navigateOptions: { - path: endpointUnIsolatePath, - }, - href: formatUrl(endpointUnIsolatePath), - children: ( - - ), - }, - ] - : isPlatinumPlus // For Platinum++ licenses, users also have ability to isolate - ? [ - { - 'data-test-subj': 'isolateLink', - icon: 'logoSecurity', - key: 'isolateHost', - navigateAppId: MANAGEMENT_APP_ID, - navigateOptions: { - path: endpointIsolatePath, - }, - href: formatUrl(endpointIsolatePath), - children: ( - - ), - }, - ] - : []; + 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: ( + + ), + }); + } 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: ( + + ), + }); + } return [ ...isolationActions, From 33c69f41bfc5e740a1243e3286bc62e102981303 Mon Sep 17 00:00:00 2001 From: Paul Tavares Date: Wed, 16 Jun 2021 16:10:10 -0400 Subject: [PATCH 6/6] Fix test - again --- .../management/pages/endpoint_hosts/view/index.test.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx index 1ac5c289c87cf7..14f9662ad9b0bb 100644 --- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx @@ -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 @@ -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(); @@ -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).isPlatinumPlus.mockReturnValue(true); + beforeAll(() => { const mockAbort = new AbortController(); mockAbort.abort();