Skip to content

Commit 2dbb716

Browse files
[Security Solution][Endpoint] Isolate Action should only be available 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>
1 parent 6f16229 commit 2dbb716

6 files changed

Lines changed: 113 additions & 46 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { LicenseService } from './license';
9+
10+
export const createLicenseServiceMock = (): jest.Mocked<LicenseService> => {
11+
return ({
12+
start: jest.fn(),
13+
stop: jest.fn(),
14+
getLicenseInformation: jest.fn(),
15+
getLicenseInformation$: jest.fn(),
16+
isAtLeast: jest.fn(),
17+
isGoldPlus: jest.fn().mockReturnValue(true),
18+
isPlatinumPlus: jest.fn().mockReturnValue(true),
19+
isEnterprise: jest.fn().mockReturnValue(true),
20+
} as unknown) as jest.Mocked<LicenseService>;
21+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
import { createLicenseServiceMock } from '../../../../common/license/mocks';
9+
10+
export const licenseService = createLicenseServiceMock();
11+
export const useLicense = () => licenseService;

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/details/components/actions_menu.test.tsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import React from 'react';
1515
import { act } from '@testing-library/react';
1616
import { endpointPageHttpMock } from '../../../mocks';
1717
import { fireEvent } from '@testing-library/dom';
18+
import { licenseService } from '../../../../../../common/hooks/use_license';
1819

1920
jest.mock('../../../../../../common/lib/kibana');
21+
jest.mock('../../../../../../common/hooks/use_license');
2022

2123
describe('When using the Endpoint Details Actions Menu', () => {
2224
let render: () => Promise<ReturnType<AppContextTestRender['render']>>;
@@ -112,4 +114,25 @@ describe('When using the Endpoint Details Actions Menu', () => {
112114
expect(coreStart.application.navigateToApp).toHaveBeenCalled();
113115
});
114116
});
117+
118+
describe('and license is NOT PlatinumPlus', () => {
119+
const licenseServiceMock = licenseService as jest.Mocked<typeof licenseService>;
120+
121+
beforeEach(() => licenseServiceMock.isPlatinumPlus.mockReturnValue(false));
122+
123+
afterEach(() => licenseServiceMock.isPlatinumPlus.mockReturnValue(true));
124+
125+
it('should not show the `isoalte` action', async () => {
126+
setEndpointMetadataResponse();
127+
await render();
128+
expect(renderResult.queryByTestId('isolateLink')).toBeNull();
129+
});
130+
131+
it('should still show `unisolate` action for endpoints that are currently isolated', async () => {
132+
setEndpointMetadataResponse(true);
133+
await render();
134+
expect(renderResult.queryByTestId('isolateLink')).toBeNull();
135+
expect(renderResult.getByTestId('unIsolateLink')).not.toBeNull();
136+
});
137+
});
115138
});

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/hooks/use_endpoint_action_items.tsx

Lines changed: 52 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import { useEndpointSelector } from './hooks';
1717
import { agentPolicies, uiQueryParams } from '../../store/selectors';
1818
import { useKibana } from '../../../../../common/lib/kibana';
1919
import { ContextMenuItemNavByRouterProps } from '../components/context_menu_item_nav_by_rotuer';
20-
import { isEndpointHostIsolated } from '../../../../../common/utils/validators/is_endpoint_host_isolated';
20+
import { isEndpointHostIsolated } from '../../../../../common/utils/validators';
21+
import { useLicense } from '../../../../../common/hooks/use_license';
2122

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

63+
const isolationActions = [];
64+
65+
if (isIsolated) {
66+
// Un-isolate is always available to users regardless of license level
67+
isolationActions.push({
68+
'data-test-subj': 'unIsolateLink',
69+
icon: 'logoSecurity',
70+
key: 'unIsolateHost',
71+
navigateAppId: MANAGEMENT_APP_ID,
72+
navigateOptions: {
73+
path: endpointUnIsolatePath,
74+
},
75+
href: formatUrl(endpointUnIsolatePath),
76+
children: (
77+
<FormattedMessage
78+
id="xpack.securitySolution.endpoint.actions.unIsolateHost"
79+
defaultMessage="Unisolate host"
80+
/>
81+
),
82+
});
83+
} else if (isPlatinumPlus) {
84+
// For Platinum++ licenses, users also have ability to isolate
85+
isolationActions.push({
86+
'data-test-subj': 'isolateLink',
87+
icon: 'logoSecurity',
88+
key: 'isolateHost',
89+
navigateAppId: MANAGEMENT_APP_ID,
90+
navigateOptions: {
91+
path: endpointIsolatePath,
92+
},
93+
href: formatUrl(endpointIsolatePath),
94+
children: (
95+
<FormattedMessage
96+
id="xpack.securitySolution.endpoint.actions.isolateHost"
97+
defaultMessage="Isolate host"
98+
/>
99+
),
100+
});
101+
}
102+
61103
return [
62-
isIsolated
63-
? {
64-
'data-test-subj': 'unIsolateLink',
65-
icon: 'logoSecurity',
66-
key: 'unIsolateHost',
67-
navigateAppId: MANAGEMENT_APP_ID,
68-
navigateOptions: {
69-
path: endpointUnIsolatePath,
70-
},
71-
href: formatUrl(endpointUnIsolatePath),
72-
children: (
73-
<FormattedMessage
74-
id="xpack.securitySolution.endpoint.actions.unIsolateHost"
75-
defaultMessage="Unisolate host"
76-
/>
77-
),
78-
}
79-
: {
80-
'data-test-subj': 'isolateLink',
81-
icon: 'logoSecurity',
82-
key: 'isolateHost',
83-
navigateAppId: MANAGEMENT_APP_ID,
84-
navigateOptions: {
85-
path: endpointIsolatePath,
86-
},
87-
href: formatUrl(endpointIsolatePath),
88-
children: (
89-
<FormattedMessage
90-
id="xpack.securitySolution.endpoint.actions.isolateHost"
91-
defaultMessage="Isolate host"
92-
/>
93-
),
94-
},
104+
...isolationActions,
95105
{
96106
'data-test-subj': 'hostLink',
97107
icon: 'logoSecurity',
@@ -183,5 +193,12 @@ export const useEndpointActionItems = (
183193
}
184194

185195
return [];
186-
}, [allCurrentUrlParams, endpointMetadata, fleetAgentPolicies, formatUrl, getUrlForApp]);
196+
}, [
197+
allCurrentUrlParams,
198+
endpointMetadata,
199+
fleetAgentPolicies,
200+
formatUrl,
201+
getUrlForApp,
202+
isPlatinumPlus,
203+
]);
187204
};

x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
isUninitialisedResourceState,
3838
} from '../../../state';
3939
import { getCurrentIsolationRequestState } from '../store/selectors';
40+
import { licenseService } from '../../../../common/hooks/use_license';
4041

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

6162
jest.mock('../../../../common/lib/kibana');
63+
jest.mock('../../../../common/hooks/use_license');
6264

6365
describe('when on the endpoint list page', () => {
6466
const docGenerator = new EndpointDocGenerator();
@@ -70,6 +72,9 @@ describe('when on the endpoint list page', () => {
7072
let coreStart: AppContextTestRender['coreStart'];
7173
let middlewareSpy: AppContextTestRender['middlewareSpy'];
7274
let abortSpy: jest.SpyInstance;
75+
76+
(licenseService as jest.Mocked<typeof licenseService>).isPlatinumPlus.mockReturnValue(true);
77+
7378
beforeAll(() => {
7479
const mockAbort = new AbortController();
7580
mockAbort.abort();

x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,7 @@ import { policyListApiPathHandlers } from '../store/test_mock_utils';
1717
import { licenseService } from '../../../../common/hooks/use_license';
1818

1919
jest.mock('../../../../common/components/link_to');
20-
jest.mock('../../../../common/hooks/use_license', () => {
21-
const licenseServiceInstance = {
22-
isPlatinumPlus: jest.fn(),
23-
};
24-
return {
25-
licenseService: licenseServiceInstance,
26-
useLicense: () => {
27-
return licenseServiceInstance;
28-
},
29-
};
30-
});
20+
jest.mock('../../../../common/hooks/use_license');
3121

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

0 commit comments

Comments
 (0)