Skip to content

Commit 631fd9d

Browse files
authored
[Security Solution] Correct Policy Config to current license level on fetch (#85206) (#86503)
1 parent c82ee2a commit 631fd9d

File tree

10 files changed

+126
-10
lines changed

10 files changed

+126
-10
lines changed

x-pack/plugins/security_solution/common/license/license.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,5 +51,5 @@ export class LicenseService {
5151
}
5252

5353
export const isAtLeast = (license: ILicense | null, level: LicenseType): boolean => {
54-
return license !== null && license.isAvailable && license.isActive && license.hasAtLeast(level);
54+
return !!license && license.isAvailable && license.isActive && license.hasAtLeast(level);
5555
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
import React, { FC, memo, useEffect } from 'react';
7+
import { useDispatch } from 'react-redux';
8+
import { Dispatch } from 'redux';
9+
import { licenseService } from '../../hooks/use_license';
10+
import { AppAction } from '../../store/actions';
11+
import { ILicense } from '../../../../../licensing/common/types';
12+
13+
export const CurrentLicense: FC = memo(({ children }) => {
14+
const dispatch = useDispatch<Dispatch<AppAction>>();
15+
useEffect(() => {
16+
const subscription = licenseService
17+
.getLicenseInformation$()
18+
?.subscribe((licenseInformation: ILicense) => {
19+
dispatch({
20+
type: 'licenseChanged',
21+
payload: licenseInformation,
22+
});
23+
});
24+
return () => subscription?.unsubscribe();
25+
}, [dispatch]);
26+
return <>{children}</>;
27+
});
28+
29+
CurrentLicense.displayName = 'CurrentLicense';

x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/action.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
import { ILicense } from '../../../../../../../licensing/common/types';
78
import { GetAgentStatusResponse } from '../../../../../../../fleet/common/types/rest_spec';
89
import { PolicyData, UIPolicyConfig } from '../../../../../../common/endpoint/types';
910
import { ServerApiError } from '../../../../../common/types';
@@ -62,6 +63,11 @@ interface UserClickedPolicyDetailsSaveButton {
6263
type: 'userClickedPolicyDetailsSaveButton';
6364
}
6465

66+
interface LicenseChanged {
67+
type: 'licenseChanged';
68+
payload: ILicense;
69+
}
70+
6571
export type PolicyDetailsAction =
6672
| ServerReturnedPolicyDetailsData
6773
| UserClickedPolicyDetailsSaveButton
@@ -70,4 +76,5 @@ export type PolicyDetailsAction =
7076
| ServerReturnedUpdatedPolicyDetailsData
7177
| ServerFailedToReturnPolicyDetailsData
7278
| UserChangedPolicyConfig
73-
| UserChangedAntivirusRegistration;
79+
| UserChangedAntivirusRegistration
80+
| LicenseChanged;

x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
} from '../../../../../common/mock/endpoint';
2121
import { HttpFetchOptions } from 'kibana/public';
2222
import { cloneDeep } from 'lodash';
23+
import { licenseMock } from '../../../../../../../licensing/common/licensing.mock';
2324

2425
describe('policy details: ', () => {
2526
let store: Store;
@@ -151,6 +152,49 @@ describe('policy details: ', () => {
151152
expect(config!.linux.events.file).toEqual(true);
152153
});
153154
});
155+
156+
describe('when the policy config has paid features enabled', () => {
157+
const CustomMessage = 'Some Popup message change';
158+
const Basic = licenseMock.createLicense({ license: { type: 'basic', mode: 'basic' } });
159+
const Platinum = licenseMock.createLicense({
160+
license: { type: 'platinum', mode: 'platinum' },
161+
});
162+
163+
beforeEach(() => {
164+
const config = policyConfig(getState());
165+
if (!config) {
166+
throw new Error();
167+
}
168+
169+
// have a paid-policy field existing in the store from a previous time
170+
const newPayload1 = cloneDeep(config);
171+
newPayload1.windows.popup.malware.message = CustomMessage;
172+
dispatch({
173+
type: 'userChangedPolicyConfig',
174+
payload: { policyConfig: newPayload1 },
175+
});
176+
});
177+
178+
it('preserves paid fields when license level allows', () => {
179+
dispatch({
180+
type: 'licenseChanged',
181+
payload: Platinum,
182+
});
183+
const config = policyConfig(getState());
184+
185+
expect(config.windows.popup.malware.message).toEqual(CustomMessage);
186+
});
187+
188+
it('reverts paid fields to default when license level does not allow', () => {
189+
dispatch({
190+
type: 'licenseChanged',
191+
payload: Basic,
192+
});
193+
const config = policyConfig(getState());
194+
195+
expect(config.windows.popup.malware.message).not.toEqual(CustomMessage);
196+
});
197+
});
154198
});
155199

156200
describe('when saving policy data', () => {

x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory<PolicyDe
2626
coreStart
2727
) => {
2828
const http = coreStart.http;
29-
3029
return ({ getState, dispatch }) => (next) => async (action) => {
3130
next(action);
3231
const state = getState();

x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/reducer.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ export const initialPolicyDetailsState: () => Immutable<PolicyDetailsState> = ()
4545
total: 0,
4646
other: 0,
4747
},
48+
license: undefined,
4849
});
4950

5051
export const policyDetailsReducer: ImmutableReducer<PolicyDetailsState, AppAction> = (
@@ -93,6 +94,13 @@ export const policyDetailsReducer: ImmutableReducer<PolicyDetailsState, AppActio
9394
};
9495
}
9596

97+
if (action.type === 'licenseChanged') {
98+
return {
99+
...state,
100+
license: action.payload,
101+
};
102+
}
103+
96104
if (action.type === 'userChangedUrl') {
97105
const newState: Immutable<PolicyDetailsState> = {
98106
...state,

x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/selectors.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
import { createSelector } from 'reselect';
88
import { matchPath } from 'react-router-dom';
9+
import { ILicense } from '../../../../../../../licensing/common/types';
10+
import { unsetPolicyFeaturesAboveLicenseLevel } from '../../../../../../common/license/policy_config';
911
import { PolicyDetailsState } from '../../types';
1012
import {
1113
Immutable,
@@ -20,6 +22,24 @@ import { ManagementRoutePolicyDetailsParams } from '../../../../types';
2022

2123
/** Returns the policy details */
2224
export const policyDetails = (state: Immutable<PolicyDetailsState>) => state.policyItem;
25+
/** Returns current active license */
26+
export const licenseState = (state: Immutable<PolicyDetailsState>) => state.license;
27+
28+
export const licensedPolicy: (
29+
state: Immutable<PolicyDetailsState>
30+
) => Immutable<PolicyData> | undefined = createSelector(
31+
policyDetails,
32+
licenseState,
33+
(policyData, license) => {
34+
if (policyData) {
35+
unsetPolicyFeaturesAboveLicenseLevel(
36+
policyData?.inputs[0]?.config.policy.value,
37+
license as ILicense
38+
);
39+
}
40+
return policyData;
41+
}
42+
);
2343

2444
/**
2545
* Given a Policy Data (package policy) object, return back a new object with only the field
@@ -75,7 +95,7 @@ export const getPolicyDataForUpdate = (
7595
*/
7696
export const policyDetailsForUpdate: (
7797
state: Immutable<PolicyDetailsState>
78-
) => Immutable<NewPolicyData> | undefined = createSelector(policyDetails, (policy) => {
98+
) => Immutable<NewPolicyData> | undefined = createSelector(licensedPolicy, (policy) => {
7999
if (policy) {
80100
return getPolicyDataForUpdate(policy);
81101
}
@@ -111,7 +131,7 @@ const defaultFullPolicy: Immutable<PolicyConfig> = policyConfigFactory();
111131
* Note: this will return a default full policy if the `policyItem` is `undefined`
112132
*/
113133
export const fullPolicy: (s: Immutable<PolicyDetailsState>) => PolicyConfig = createSelector(
114-
policyDetails,
134+
licensedPolicy,
115135
(policyData) => {
116136
return policyData?.inputs[0]?.config?.policy?.value ?? defaultFullPolicy;
117137
}

x-pack/plugins/security_solution/public/management/pages/policy/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* you may not use this file except in compliance with the Elastic License.
55
*/
66

7+
import { ILicense } from '../../../../../licensing/common/types';
78
import {
89
AppLocation,
910
Immutable,
@@ -66,6 +67,8 @@ export interface PolicyDetailsState {
6667
success: boolean;
6768
error?: ServerApiError;
6869
};
70+
/** current license */
71+
license?: ILicense;
6972
}
7073

7174
/**

x-pack/plugins/security_solution/public/management/pages/policy/view/ingest_manager_integration/with_security_context.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import React, { ComponentType, memo } from 'react';
88
import { CoreStart } from 'kibana/public';
99
import { combineReducers, createStore, compose, applyMiddleware } from 'redux';
1010
import { Provider as ReduxStoreProvider } from 'react-redux';
11+
import { CurrentLicense } from '../../../../../common/components/current_license';
1112
import { StartPlugins } from '../../../../../types';
1213
import { managementReducer } from '../../../../store/reducer';
1314
import { managementMiddlewareFactory } from '../../../../store/middleware';
@@ -57,7 +58,9 @@ export const withSecurityContext = <P extends {}>({
5758

5859
return (
5960
<ReduxStoreProvider store={store}>
60-
<WrappedComponent {...props} />
61+
<CurrentLicense>
62+
<WrappedComponent {...props} />
63+
</CurrentLicense>
6164
</ReduxStoreProvider>
6265
);
6366
});

x-pack/plugins/security_solution/public/management/routes.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,16 @@ import React from 'react';
88
import { Route, Switch } from 'react-router-dom';
99
import { ManagementContainer } from './pages';
1010
import { NotFoundPage } from '../app/404';
11+
import { CurrentLicense } from '../common/components/current_license';
1112

1213
/**
1314
* Returns the React Router Routes for the management area
1415
*/
1516
export const ManagementRoutes = () => (
16-
<Switch>
17-
<Route path="/" component={ManagementContainer} />
18-
<Route render={() => <NotFoundPage />} />
19-
</Switch>
17+
<CurrentLicense>
18+
<Switch>
19+
<Route path="/" component={ManagementContainer} />
20+
<Route render={() => <NotFoundPage />} />
21+
</Switch>
22+
</CurrentLicense>
2023
);

0 commit comments

Comments
 (0)