[Security Solution][Endpoint][Admin] Malware user notification is a platinum tiered feature (#82894)
This commit is contained in:
parent
b9fc45bb5d
commit
69e3ceb474
60
x-pack/plugins/security_solution/common/license/license.ts
Normal file
60
x-pack/plugins/security_solution/common/license/license.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { ILicense } from '../../../licensing/common/types';
|
||||
|
||||
// Generic license service class that works with the license observable
|
||||
// Both server and client plugins instancates a singleton version of this class
|
||||
export class LicenseService {
|
||||
private observable: Observable<ILicense> | null = null;
|
||||
private subscription: Subscription | null = null;
|
||||
private licenseInformation: ILicense | null = null;
|
||||
|
||||
private updateInformation(licenseInformation: ILicense) {
|
||||
this.licenseInformation = licenseInformation;
|
||||
}
|
||||
|
||||
public start(license$: Observable<ILicense>) {
|
||||
this.observable = license$;
|
||||
this.subscription = this.observable.subscribe(this.updateInformation.bind(this));
|
||||
}
|
||||
|
||||
public stop() {
|
||||
if (this.subscription) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
public getLicenseInformation() {
|
||||
return this.licenseInformation;
|
||||
}
|
||||
|
||||
public getLicenseInformation$() {
|
||||
return this.observable;
|
||||
}
|
||||
|
||||
public isGoldPlus() {
|
||||
return (
|
||||
this.licenseInformation?.isAvailable &&
|
||||
this.licenseInformation?.isActive &&
|
||||
this.licenseInformation?.hasAtLeast('gold')
|
||||
);
|
||||
}
|
||||
public isPlatinumPlus() {
|
||||
return (
|
||||
this.licenseInformation?.isAvailable &&
|
||||
this.licenseInformation?.isActive &&
|
||||
this.licenseInformation?.hasAtLeast('platinum')
|
||||
);
|
||||
}
|
||||
public isEnterprise() {
|
||||
return (
|
||||
this.licenseInformation?.isAvailable &&
|
||||
this.licenseInformation?.isActive &&
|
||||
this.licenseInformation?.hasAtLeast('enterprise')
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { LicenseService } from '../../../common/license/license';
|
||||
|
||||
export const licenseService = new LicenseService();
|
||||
|
||||
export function useLicense() {
|
||||
return licenseService;
|
||||
}
|
|
@ -13,8 +13,20 @@ import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_da
|
|||
import { AppContextTestRender, createAppRootMockRenderer } from '../../../../common/mock/endpoint';
|
||||
import { getPolicyDetailPath, getEndpointListPath } from '../../../common/routing';
|
||||
import { policyListApiPathHandlers } from '../store/policy_list/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;
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
describe('Policy Details', () => {
|
||||
type FindReactWrapperResponse = ReturnType<ReturnType<typeof render>['find']>;
|
||||
|
@ -275,5 +287,40 @@ describe('Policy Details', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
describe('when the subscription tier is platinum or higher', () => {
|
||||
beforeEach(() => {
|
||||
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(true);
|
||||
policyView = render(<PolicyDetails />);
|
||||
});
|
||||
|
||||
it('malware popup and message customization options are shown', () => {
|
||||
// use query for finding stuff, if it doesn't find it, just returns null
|
||||
const userNotificationCheckbox = policyView.find(
|
||||
'EuiCheckbox[data-test-subj="malwareUserNotificationCheckbox"]'
|
||||
);
|
||||
const userNotificationCustomMessageTextArea = policyView.find(
|
||||
'EuiTextArea[data-test-subj="malwareUserNotificationCustomMessage"]'
|
||||
);
|
||||
expect(userNotificationCheckbox).toHaveLength(1);
|
||||
expect(userNotificationCustomMessageTextArea).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
describe('when the subscription tier is gold or lower', () => {
|
||||
beforeEach(() => {
|
||||
(licenseService.isPlatinumPlus as jest.Mock).mockReturnValue(false);
|
||||
policyView = render(<PolicyDetails />);
|
||||
});
|
||||
|
||||
it('malware popup and message customization options are hidden', () => {
|
||||
const userNotificationCheckbox = policyView.find(
|
||||
'EuiCheckbox[data-test-subj="malwareUserNotificationCheckbox"]'
|
||||
);
|
||||
const userNotificationCustomMessageTextArea = policyView.find(
|
||||
'EuiTextArea[data-test-subj="malwareUserNotificationCustomMessage"]'
|
||||
);
|
||||
expect(userNotificationCheckbox).toHaveLength(0);
|
||||
expect(userNotificationCustomMessageTextArea).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -30,6 +30,7 @@ import { policyConfig } from '../../../store/policy_details/selectors';
|
|||
import { usePolicyDetailsSelector } from '../../policy_hooks';
|
||||
import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app';
|
||||
import { popupVersionsMap } from './popup_options_to_versions';
|
||||
import { useLicense } from '../../../../../../common/hooks/use_license';
|
||||
|
||||
const ProtectionRadioGroup = styled.div`
|
||||
display: flex;
|
||||
|
@ -116,6 +117,7 @@ export const MalwareProtections = React.memo(() => {
|
|||
policyDetailsConfig && policyDetailsConfig.windows.popup.malware.enabled;
|
||||
const userNotificationMessage =
|
||||
policyDetailsConfig && policyDetailsConfig.windows.popup.malware.message;
|
||||
const isPlatinumPlus = useLicense().isPlatinumPlus();
|
||||
|
||||
const radios: Immutable<Array<{
|
||||
id: ProtectionModes;
|
||||
|
@ -217,24 +219,31 @@ export const MalwareProtections = React.memo(() => {
|
|||
);
|
||||
})}
|
||||
</ProtectionRadioGroup>
|
||||
<EuiSpacer size="s" />
|
||||
<ConfigFormHeading>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.userNotification"
|
||||
defaultMessage="User Notification"
|
||||
/>
|
||||
</ConfigFormHeading>
|
||||
<SupportedVersionNotice optionName="malware" />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCheckbox
|
||||
id="xpack.securitySolution.endpoint.policyDetail.malware.userNotification"
|
||||
onChange={handleUserNotificationCheckbox}
|
||||
checked={userNotificationSelected}
|
||||
label={i18n.translate('xpack.securitySolution.endpoint.policyDetail.malware.notifyUser', {
|
||||
defaultMessage: 'Notify User',
|
||||
})}
|
||||
/>
|
||||
{userNotificationSelected && (
|
||||
{isPlatinumPlus && (
|
||||
<>
|
||||
<ConfigFormHeading>
|
||||
<FormattedMessage
|
||||
id="xpack.securitySolution.endpoint.policyDetailsConfig.userNotification"
|
||||
defaultMessage="User Notification"
|
||||
/>
|
||||
</ConfigFormHeading>
|
||||
<SupportedVersionNotice optionName="malware" />
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCheckbox
|
||||
data-test-subj="malwareUserNotificationCheckbox"
|
||||
id="xpack.securitySolution.endpoint.policyDetail.malware.userNotification"
|
||||
onChange={handleUserNotificationCheckbox}
|
||||
checked={userNotificationSelected}
|
||||
label={i18n.translate(
|
||||
'xpack.securitySolution.endpoint.policyDetail.malware.notifyUser',
|
||||
{
|
||||
defaultMessage: 'Notify User',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{isPlatinumPlus && userNotificationSelected && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiText size="xs">
|
||||
|
@ -256,6 +265,7 @@ export const MalwareProtections = React.memo(() => {
|
|||
value={userNotificationMessage}
|
||||
onChange={handleCustomUserNotification}
|
||||
fullWidth={true}
|
||||
data-test-subj="malwareUserNotificationCustomMessage"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
@ -263,6 +273,7 @@ export const MalwareProtections = React.memo(() => {
|
|||
);
|
||||
}, [
|
||||
radios,
|
||||
isPlatinumPlus,
|
||||
handleUserNotificationCheckbox,
|
||||
userNotificationSelected,
|
||||
userNotificationMessage,
|
||||
|
|
|
@ -60,6 +60,7 @@ import {
|
|||
} from '../common/search_strategy/index_fields';
|
||||
import { SecurityAppStore } from './common/store/store';
|
||||
import { getCaseConnectorUI } from './common/lib/connectors';
|
||||
import { licenseService } from './common/hooks/use_license';
|
||||
import { LazyEndpointPolicyEditExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_edit_extension';
|
||||
import { LazyEndpointPolicyCreateExtension } from './management/pages/policy/view/ingest_manager_integration/lazy_endpoint_policy_create_extension';
|
||||
|
||||
|
@ -345,6 +346,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
component: LazyEndpointPolicyCreateExtension,
|
||||
});
|
||||
}
|
||||
licenseService.start(plugins.licensing.license$);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ import { Network } from './network';
|
|||
import { Overview } from './overview';
|
||||
import { Timelines } from './timelines';
|
||||
import { Management } from './management';
|
||||
import { LicensingPluginStart } from '../../licensing/public';
|
||||
|
||||
export interface SetupPlugins {
|
||||
home?: HomePublicPluginSetup;
|
||||
|
@ -49,6 +50,7 @@ export interface StartPlugins {
|
|||
inspector: InspectorStart;
|
||||
ingestManager?: IngestManagerStart;
|
||||
lists?: ListsPluginStart;
|
||||
licensing: LicensingPluginStart;
|
||||
newsfeed?: NewsfeedPublicPluginStart;
|
||||
triggersActionsUi: TriggersActionsStart;
|
||||
uiActions: UiActionsStart;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { LicenseService } from '../../../common/license/license';
|
||||
|
||||
export const licenseService = new LicenseService();
|
|
@ -33,7 +33,7 @@ import { MlPluginSetup as MlSetup } from '../../ml/server';
|
|||
import { ListPluginSetup } from '../../lists/server';
|
||||
import { EncryptedSavedObjectsPluginSetup as EncryptedSavedObjectsSetup } from '../../encrypted_saved_objects/server';
|
||||
import { SpacesPluginSetup as SpacesSetup } from '../../spaces/server';
|
||||
import { LicensingPluginSetup } from '../../licensing/server';
|
||||
import { ILicense, LicensingPluginStart } from '../../licensing/server';
|
||||
import { IngestManagerStartContract, ExternalCallback } from '../../fleet/server';
|
||||
import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server';
|
||||
import { initServer } from './init_server';
|
||||
|
@ -74,13 +74,13 @@ import {
|
|||
TelemetryPluginStart,
|
||||
TelemetryPluginSetup,
|
||||
} from '../../../../src/plugins/telemetry/server';
|
||||
import { licenseService } from './lib/license/license';
|
||||
|
||||
export interface SetupPlugins {
|
||||
alerts: AlertingSetup;
|
||||
data: DataPluginSetup;
|
||||
encryptedSavedObjects?: EncryptedSavedObjectsSetup;
|
||||
features: FeaturesSetup;
|
||||
licensing: LicensingPluginSetup;
|
||||
lists?: ListPluginSetup;
|
||||
ml?: MlSetup;
|
||||
security?: SecuritySetup;
|
||||
|
@ -94,6 +94,7 @@ export interface StartPlugins {
|
|||
alerts: AlertPluginStartContract;
|
||||
data: DataPluginStart;
|
||||
ingestManager?: IngestManagerStartContract;
|
||||
licensing: LicensingPluginStart;
|
||||
taskManager?: TaskManagerStartContract;
|
||||
telemetry?: TelemetryPluginStart;
|
||||
}
|
||||
|
@ -125,6 +126,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
private readonly telemetryEventsSender: TelemetryEventsSender;
|
||||
|
||||
private lists: ListPluginSetup | undefined; // TODO: can we create ListPluginStart?
|
||||
private licensing$!: Observable<ILicense>;
|
||||
|
||||
private manifestTask: ManifestTask | undefined;
|
||||
private exceptionsCache: LRU<string, Buffer>;
|
||||
|
@ -364,6 +366,8 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
}
|
||||
|
||||
this.telemetryEventsSender.start(core, plugins.telemetry);
|
||||
this.licensing$ = plugins.licensing.license$;
|
||||
licenseService.start(this.licensing$);
|
||||
|
||||
return {};
|
||||
}
|
||||
|
@ -372,5 +376,6 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
this.logger.debug('Stopping plugin');
|
||||
this.telemetryEventsSender.stop();
|
||||
this.endpointAppContextService.stop();
|
||||
licenseService.stop();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue