[Security Solution] Refactor AppFeatures to ProductFeatures (#177005)

## Summary

This PR renames `AppFeatures` -> `ProductFeatures`.

This module is responsible for managing the Security _features_ that are
enabled according to the _product_ type used. After talking with
different teams we agreed it would be more intuitive and easier to
understand if we named it `ProductFeatures`, since `AppFeatures` is too
vague and generic.

This refactoring does not introduce any change in the application
behavior.

Internal docs will also be updated.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Sergi Massaneda 2024-02-19 12:55:05 +01:00 committed by GitHub
parent 5ae321f8f5
commit 872929bd60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
72 changed files with 1063 additions and 962 deletions

View file

@ -5,8 +5,8 @@
* 2.0. * 2.0.
*/ */
export { securityDefaultAppFeaturesConfig } from './src/security/app_feature_config'; export { securityDefaultProductFeaturesConfig } from './src/security/product_feature_config';
export { getCasesDefaultAppFeaturesConfig } from './src/cases/app_feature_config'; export { getCasesDefaultProductFeaturesConfig } from './src/cases/product_feature_config';
export { assistantDefaultAppFeaturesConfig } from './src/assistant/app_feature_config'; export { assistantDefaultProductFeaturesConfig } from './src/assistant/product_feature_config';
export { createEnabledAppFeaturesConfigMap } from './src/helpers'; export { createEnabledProductFeaturesConfigMap } from './src/helpers';

View file

@ -5,4 +5,4 @@
* 2.0. * 2.0.
*/ */
export * from './src/app_features_keys'; export * from './src/product_features_keys';

View file

@ -4,4 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
export { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from './src/app_features_privileges'; export {
ProductFeaturesPrivilegeId,
ProductFeaturesPrivileges,
} from './src/product_features_privileges';

View file

@ -4,15 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import type { AssistantSubFeatureId } from '../app_features_keys'; import type { AssistantSubFeatureId } from '../product_features_keys';
import type { AppFeatureParams } from '../types'; import type { ProductFeatureParams } from '../types';
import { getAssistantBaseKibanaFeature } from './kibana_features'; import { getAssistantBaseKibanaFeature } from './kibana_features';
import { import {
getAssistantBaseKibanaSubFeatureIds, getAssistantBaseKibanaSubFeatureIds,
assistantSubFeaturesMap, assistantSubFeaturesMap,
} from './kibana_sub_features'; } from './kibana_sub_features';
export const getAssistantFeature = (): AppFeatureParams<AssistantSubFeatureId> => ({ export const getAssistantFeature = (): ProductFeatureParams<AssistantSubFeatureId> => ({
baseKibanaFeature: getAssistantBaseKibanaFeature(), baseKibanaFeature: getAssistantBaseKibanaFeature(),
baseKibanaSubFeatureIds: getAssistantBaseKibanaSubFeatureIds(), baseKibanaSubFeatureIds: getAssistantBaseKibanaSubFeatureIds(),
subFeaturesMap: assistantSubFeaturesMap, subFeaturesMap: assistantSubFeaturesMap,

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import type { AssistantSubFeatureId } from '../app_features_keys'; import type { AssistantSubFeatureId } from '../product_features_keys';
import { AppFeatureAssistantKey } from '../app_features_keys'; import { ProductFeatureAssistantKey } from '../product_features_keys';
import type { AppFeatureKibanaConfig } from '../types'; import type { ProductFeatureKibanaConfig } from '../types';
/** /**
* App features privileges configuration for the Security Assistant Kibana Feature app. * App features privileges configuration for the Security Assistant Kibana Feature app.
@ -19,11 +19,11 @@ import type { AppFeatureKibanaConfig } from '../types';
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/ */
export const assistantDefaultAppFeaturesConfig: Record< export const assistantDefaultProductFeaturesConfig: Record<
AppFeatureAssistantKey, ProductFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId> ProductFeatureKibanaConfig<AssistantSubFeatureId>
> = { > = {
[AppFeatureAssistantKey.assistant]: { [ProductFeatureAssistantKey.assistant]: {
privileges: { privileges: {
all: { all: {
ui: ['ai-assistant'], ui: ['ai-assistant'],

View file

@ -4,15 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import type { CasesSubFeatureId } from '../app_features_keys'; import type { CasesSubFeatureId } from '../product_features_keys';
import type { AppFeatureParams } from '../types'; import type { ProductFeatureParams } from '../types';
import { getCasesBaseKibanaFeature } from './kibana_features'; import { getCasesBaseKibanaFeature } from './kibana_features';
import { getCasesBaseKibanaSubFeatureIds, getCasesSubFeaturesMap } from './kibana_sub_features'; import { getCasesBaseKibanaSubFeatureIds, getCasesSubFeaturesMap } from './kibana_sub_features';
import type { CasesFeatureParams } from './types'; import type { CasesFeatureParams } from './types';
export const getCasesFeature = ( export const getCasesFeature = (
params: CasesFeatureParams params: CasesFeatureParams
): AppFeatureParams<CasesSubFeatureId> => ({ ): ProductFeatureParams<CasesSubFeatureId> => ({
baseKibanaFeature: getCasesBaseKibanaFeature(params), baseKibanaFeature: getCasesBaseKibanaFeature(params),
baseKibanaSubFeatureIds: getCasesBaseKibanaSubFeatureIds(), baseKibanaSubFeatureIds: getCasesBaseKibanaSubFeatureIds(),
subFeaturesMap: getCasesSubFeaturesMap(params), subFeaturesMap: getCasesSubFeaturesMap(params),

View file

@ -7,7 +7,7 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import type { SubFeatureConfig } from '@kbn/features-plugin/common'; import type { SubFeatureConfig } from '@kbn/features-plugin/common';
import { CasesSubFeatureId } from '../app_features_keys'; import { CasesSubFeatureId } from '../product_features_keys';
import { APP_ID } from '../constants'; import { APP_ID } from '../constants';
import type { CasesFeatureParams } from './types'; import type { CasesFeatureParams } from './types';

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import { AppFeatureCasesKey } from '../app_features_keys'; import { ProductFeatureCasesKey } from '../product_features_keys';
import { APP_ID } from '../constants'; import { APP_ID } from '../constants';
import type { DefaultCasesAppFeaturesConfig } from './types'; import type { DefaultCasesProductFeaturesConfig } from './types';
/** /**
* App features privileges configuration for the Security Cases Kibana Feature app. * App features privileges configuration for the Security Cases Kibana Feature app.
@ -19,14 +19,14 @@ import type { DefaultCasesAppFeaturesConfig } from './types';
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/ */
export const getCasesDefaultAppFeaturesConfig = ({ export const getCasesDefaultProductFeaturesConfig = ({
apiTags, apiTags,
uiCapabilities, uiCapabilities,
}: { }: {
apiTags: { connectors: string }; apiTags: { connectors: string };
uiCapabilities: { connectors: string }; uiCapabilities: { connectors: string };
}): DefaultCasesAppFeaturesConfig => ({ }): DefaultCasesProductFeaturesConfig => ({
[AppFeatureCasesKey.casesConnectors]: { [ProductFeatureCasesKey.casesConnectors]: {
privileges: { privileges: {
all: { all: {
api: [apiTags.connectors], api: [apiTags.connectors],

View file

@ -6,8 +6,8 @@
*/ */
import type { CasesUiCapabilities, CasesApiTags } from '@kbn/cases-plugin/common'; import type { CasesUiCapabilities, CasesApiTags } from '@kbn/cases-plugin/common';
import type { AppFeatureCasesKey, CasesSubFeatureId } from '../app_features_keys'; import type { ProductFeatureCasesKey, CasesSubFeatureId } from '../product_features_keys';
import type { AppFeatureKibanaConfig } from '../types'; import type { ProductFeatureKibanaConfig } from '../types';
export interface CasesFeatureParams { export interface CasesFeatureParams {
uiCapabilities: CasesUiCapabilities; uiCapabilities: CasesUiCapabilities;
@ -15,7 +15,7 @@ export interface CasesFeatureParams {
savedObjects: { files: string[] }; savedObjects: { files: string[] };
} }
export type DefaultCasesAppFeaturesConfig = Record< export type DefaultCasesProductFeaturesConfig = Record<
AppFeatureCasesKey, ProductFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId> ProductFeatureKibanaConfig<CasesSubFeatureId>
>; >;

View file

@ -5,26 +5,29 @@
* 2.0. * 2.0.
*/ */
import type { AppFeatureKeys, AppFeatureKeyType, AppFeatureKibanaConfig } from './types'; import type {
ProductFeatureKeys,
ProductFeatureKeyType,
ProductFeatureKibanaConfig,
} from './types';
/** /**
* Creates the AppFeaturesConfig Map from the given appFeatures object and a set of enabled appFeatures keys. * Creates the ProductFeaturesConfig Map from the given productFeatures object and a set of enabled productFeatures keys.
*/ */
export const createEnabledAppFeaturesConfigMap = < export const createEnabledProductFeaturesConfigMap = <
K extends AppFeatureKeyType, K extends ProductFeatureKeyType,
T extends string = string T extends string = string
>( >(
appFeatures: Record<K, AppFeatureKibanaConfig<T>>, productFeatures: Record<K, ProductFeatureKibanaConfig<T>>,
enabledAppFeaturesKeys: AppFeatureKeys enabledProductFeaturesKeys: ProductFeatureKeys
) => { ) =>
return new Map( new Map(
Object.entries<AppFeatureKibanaConfig<T>>(appFeatures).reduce< Object.entries<ProductFeatureKibanaConfig<T>>(productFeatures).reduce<
Array<[K, AppFeatureKibanaConfig<T>]> Array<[K, ProductFeatureKibanaConfig<T>]>
>((acc, [key, value]) => { >((acc, [key, value]) => {
if (enabledAppFeaturesKeys.includes(key as K)) { if (enabledProductFeaturesKeys.includes(key as K)) {
acc.push([key as K, value]); acc.push([key as K, value]);
} }
return acc; return acc;
}, []) }, [])
); );
};

View file

@ -5,7 +5,7 @@
* 2.0. * 2.0.
*/ */
export enum AppFeatureSecurityKey { export enum ProductFeatureSecurityKey {
/** Enables Advanced Insights (Entity Risk, GenAI) */ /** Enables Advanced Insights (Entity Risk, GenAI) */
advancedInsights = 'advanced_insights', advancedInsights = 'advanced_insights',
/** /**
@ -65,14 +65,14 @@ export enum AppFeatureSecurityKey {
externalRuleActions = 'external_rule_actions', externalRuleActions = 'external_rule_actions',
} }
export enum AppFeatureCasesKey { export enum ProductFeatureCasesKey {
/** /**
* Enables Cases Connectors * Enables Cases Connectors
*/ */
casesConnectors = 'cases_connectors', casesConnectors = 'cases_connectors',
} }
export enum AppFeatureAssistantKey { export enum ProductFeatureAssistantKey {
/** /**
* Enables Elastic AI Assistant * Enables Elastic AI Assistant
*/ */
@ -80,15 +80,18 @@ export enum AppFeatureAssistantKey {
} }
// Merges the two enums. // Merges the two enums.
export const AppFeatureKey = { export const ProductFeatureKey = {
...AppFeatureSecurityKey, ...ProductFeatureSecurityKey,
...AppFeatureCasesKey, ...ProductFeatureCasesKey,
...AppFeatureAssistantKey, ...ProductFeatureAssistantKey,
}; };
// We need to merge the value and the type and export both to replicate how enum works. // We need to merge the value and the type and export both to replicate how enum works.
export type AppFeatureKeyType = AppFeatureSecurityKey | AppFeatureCasesKey | AppFeatureAssistantKey; export type ProductFeatureKeyType =
| ProductFeatureSecurityKey
| ProductFeatureCasesKey
| ProductFeatureAssistantKey;
export const ALL_APP_FEATURE_KEYS = Object.freeze(Object.values(AppFeatureKey)); export const ALL_PRODUCT_FEATURE_KEYS = Object.freeze(Object.values(ProductFeatureKey));
/** Sub-features IDs for Security */ /** Sub-features IDs for Security */
export enum SecuritySubFeatureId { export enum SecuritySubFeatureId {

View file

@ -7,7 +7,7 @@
import { APP_ID } from './constants'; import { APP_ID } from './constants';
export enum AppFeaturesPrivilegeId { export enum ProductFeaturesPrivilegeId {
endpointExceptions = 'endpoint_exceptions', endpointExceptions = 'endpoint_exceptions',
} }
@ -16,8 +16,8 @@ export enum AppFeaturesPrivilegeId {
* using a different Kibana feature configuration (sub-feature, main feature privilege, etc) * using a different Kibana feature configuration (sub-feature, main feature privilege, etc)
* in each offering type (ess, serverless) * in each offering type (ess, serverless)
*/ */
export const AppFeaturesPrivileges = { export const ProductFeaturesPrivileges = {
[AppFeaturesPrivilegeId.endpointExceptions]: { [ProductFeaturesPrivilegeId.endpointExceptions]: {
all: { all: {
ui: ['showEndpointExceptions', 'crudEndpointExceptions'], ui: ['showEndpointExceptions', 'crudEndpointExceptions'],
api: [`${APP_ID}-showEndpointExceptions`, `${APP_ID}-crudEndpointExceptions`], api: [`${APP_ID}-showEndpointExceptions`, `${APP_ID}-crudEndpointExceptions`],

View file

@ -4,15 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import type { SecuritySubFeatureId } from '../app_features_keys'; import type { SecuritySubFeatureId } from '../product_features_keys';
import type { AppFeatureParams } from '../types'; import type { ProductFeatureParams } from '../types';
import { getSecurityBaseKibanaFeature } from './kibana_features'; import { getSecurityBaseKibanaFeature } from './kibana_features';
import { securitySubFeaturesMap, getSecurityBaseKibanaSubFeatureIds } from './kibana_sub_features'; import { securitySubFeaturesMap, getSecurityBaseKibanaSubFeatureIds } from './kibana_sub_features';
import type { SecurityFeatureParams } from './types'; import type { SecurityFeatureParams } from './types';
export const getSecurityFeature = ( export const getSecurityFeature = (
params: SecurityFeatureParams params: SecurityFeatureParams
): AppFeatureParams<SecuritySubFeatureId> => ({ ): ProductFeatureParams<SecuritySubFeatureId> => ({
baseKibanaFeature: getSecurityBaseKibanaFeature(params), baseKibanaFeature: getSecurityBaseKibanaFeature(params),
baseKibanaSubFeatureIds: getSecurityBaseKibanaSubFeatureIds(params), baseKibanaSubFeatureIds: getSecurityBaseKibanaSubFeatureIds(params),
subFeaturesMap: securitySubFeaturesMap, subFeaturesMap: securitySubFeaturesMap,

View file

@ -8,9 +8,12 @@
import { i18n } from '@kbn/i18n'; import { i18n } from '@kbn/i18n';
import type { SubFeatureConfig } from '@kbn/features-plugin/common'; import type { SubFeatureConfig } from '@kbn/features-plugin/common';
import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants'; import { EXCEPTION_LIST_NAMESPACE_AGNOSTIC } from '@kbn/securitysolution-list-constants';
import { AppFeaturesPrivilegeId, AppFeaturesPrivileges } from '../app_features_privileges'; import {
ProductFeaturesPrivilegeId,
ProductFeaturesPrivileges,
} from '../product_features_privileges';
import { SecuritySubFeatureId } from '../app_features_keys'; import { SecuritySubFeatureId } from '../product_features_keys';
import { APP_ID } from '../constants'; import { APP_ID } from '../constants';
import type { SecurityFeatureParams } from './types'; import type { SecurityFeatureParams } from './types';
@ -583,7 +586,7 @@ const endpointExceptionsSubFeature: SubFeatureConfig = {
all: [], all: [],
read: [], read: [],
}, },
...AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions].all, ...ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions].all,
}, },
{ {
id: 'endpoint_exceptions_read', id: 'endpoint_exceptions_read',
@ -593,7 +596,7 @@ const endpointExceptionsSubFeature: SubFeatureConfig = {
all: [], all: [],
read: [], read: [],
}, },
...AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions].read, ...ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions].read,
}, },
], ],
}, },

View file

@ -5,9 +5,9 @@
* 2.0. * 2.0.
*/ */
import { AppFeatureSecurityKey, SecuritySubFeatureId } from '../app_features_keys'; import { ProductFeatureSecurityKey, SecuritySubFeatureId } from '../product_features_keys';
import { APP_ID } from '../constants'; import { APP_ID } from '../constants';
import type { DefaultSecurityAppFeaturesConfig } from './types'; import type { DefaultSecurityProductFeaturesConfig } from './types';
/** /**
* App features privileges configuration for the Security Solution Kibana Feature app. * App features privileges configuration for the Security Solution Kibana Feature app.
@ -19,8 +19,8 @@ import type { DefaultSecurityAppFeaturesConfig } from './types';
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/ */
export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig = { export const securityDefaultProductFeaturesConfig: DefaultSecurityProductFeaturesConfig = {
[AppFeatureSecurityKey.advancedInsights]: { [ProductFeatureSecurityKey.advancedInsights]: {
privileges: { privileges: {
all: { all: {
ui: ['entity-analytics'], ui: ['entity-analytics'],
@ -32,7 +32,7 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
}, },
}, },
}, },
[AppFeatureSecurityKey.investigationGuide]: { [ProductFeatureSecurityKey.investigationGuide]: {
privileges: { privileges: {
all: { all: {
ui: ['investigation-guide'], ui: ['investigation-guide'],
@ -43,7 +43,7 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
}, },
}, },
[AppFeatureSecurityKey.threatIntelligence]: { [ProductFeatureSecurityKey.threatIntelligence]: {
privileges: { privileges: {
all: { all: {
ui: ['threat-intelligence'], ui: ['threat-intelligence'],
@ -56,18 +56,18 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
}, },
}, },
[AppFeatureSecurityKey.endpointHostManagement]: { [ProductFeatureSecurityKey.endpointHostManagement]: {
subFeatureIds: [SecuritySubFeatureId.endpointList], subFeatureIds: [SecuritySubFeatureId.endpointList],
}, },
[AppFeatureSecurityKey.endpointPolicyManagement]: { [ProductFeatureSecurityKey.endpointPolicyManagement]: {
subFeatureIds: [SecuritySubFeatureId.policyManagement], subFeatureIds: [SecuritySubFeatureId.policyManagement],
}, },
// Adds no additional kibana feature controls // Adds no additional kibana feature controls
[AppFeatureSecurityKey.endpointPolicyProtections]: {}, [ProductFeatureSecurityKey.endpointPolicyProtections]: {},
[AppFeatureSecurityKey.endpointArtifactManagement]: { [ProductFeatureSecurityKey.endpointArtifactManagement]: {
subFeatureIds: [ subFeatureIds: [
SecuritySubFeatureId.trustedApplications, SecuritySubFeatureId.trustedApplications,
SecuritySubFeatureId.blocklist, SecuritySubFeatureId.blocklist,
@ -87,7 +87,7 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
], ],
}, },
[AppFeatureSecurityKey.endpointResponseActions]: { [ProductFeatureSecurityKey.endpointResponseActions]: {
subFeatureIds: [ subFeatureIds: [
SecuritySubFeatureId.hostIsolationExceptions, SecuritySubFeatureId.hostIsolationExceptions,
SecuritySubFeatureId.responseActionsHistory, SecuritySubFeatureId.responseActionsHistory,
@ -105,8 +105,9 @@ export const securityDefaultAppFeaturesConfig: DefaultSecurityAppFeaturesConfig
], ],
}, },
[AppFeatureSecurityKey.osqueryAutomatedResponseActions]: {}, // Product features without RBAC
[AppFeatureSecurityKey.endpointProtectionUpdates]: {}, [ProductFeatureSecurityKey.osqueryAutomatedResponseActions]: {},
[AppFeatureSecurityKey.endpointAgentTamperProtection]: {}, [ProductFeatureSecurityKey.endpointProtectionUpdates]: {},
[AppFeatureSecurityKey.externalRuleActions]: {}, [ProductFeatureSecurityKey.endpointAgentTamperProtection]: {},
[ProductFeatureSecurityKey.externalRuleActions]: {},
}; };

View file

@ -5,16 +5,16 @@
* 2.0. * 2.0.
*/ */
import type { AppFeatureSecurityKey, SecuritySubFeatureId } from '../app_features_keys'; import type { ProductFeatureSecurityKey, SecuritySubFeatureId } from '../product_features_keys';
import type { AppFeatureKibanaConfig } from '../types'; import type { ProductFeatureKibanaConfig } from '../types';
export interface SecurityFeatureParams { export interface SecurityFeatureParams {
experimentalFeatures: Record<string, boolean>; experimentalFeatures: Record<string, boolean>;
savedObjects: string[]; savedObjects: string[];
} }
export type DefaultSecurityAppFeaturesConfig = Omit< export type DefaultSecurityProductFeaturesConfig = Omit<
Record<AppFeatureSecurityKey, AppFeatureKibanaConfig<SecuritySubFeatureId>>, Record<ProductFeatureSecurityKey, ProductFeatureKibanaConfig<SecuritySubFeatureId>>,
AppFeatureSecurityKey.endpointExceptions ProductFeatureSecurityKey.endpointExceptions
// | add not default security app features here // | add not generic security app features here
>; >;

View file

@ -12,48 +12,48 @@ import type {
} from '@kbn/features-plugin/common'; } from '@kbn/features-plugin/common';
import type { RecursivePartial } from '@kbn/utility-types'; import type { RecursivePartial } from '@kbn/utility-types';
import type { import type {
AppFeatureAssistantKey, ProductFeatureAssistantKey,
AppFeatureCasesKey, ProductFeatureCasesKey,
AppFeatureKeyType, ProductFeatureKeyType,
AppFeatureSecurityKey, ProductFeatureSecurityKey,
AssistantSubFeatureId, AssistantSubFeatureId,
CasesSubFeatureId, CasesSubFeatureId,
SecuritySubFeatureId, SecuritySubFeatureId,
} from './app_features_keys'; } from './product_features_keys';
export type { AppFeatureKeyType }; export type { ProductFeatureKeyType };
export type AppFeatureKeys = AppFeatureKeyType[]; export type ProductFeatureKeys = ProductFeatureKeyType[];
// Features types // Features types
export type BaseKibanaFeatureConfig = Omit<KibanaFeatureConfig, 'subFeatures'>; export type BaseKibanaFeatureConfig = Omit<KibanaFeatureConfig, 'subFeatures'>;
export type SubFeaturesPrivileges = RecursivePartial<SubFeaturePrivilegeConfig>; export type SubFeaturesPrivileges = RecursivePartial<SubFeaturePrivilegeConfig>;
export type AppFeatureKibanaConfig<T extends string = string> = export type ProductFeatureKibanaConfig<T extends string = string> =
RecursivePartial<BaseKibanaFeatureConfig> & { RecursivePartial<BaseKibanaFeatureConfig> & {
subFeatureIds?: T[]; subFeatureIds?: T[];
subFeaturesPrivileges?: SubFeaturesPrivileges[]; subFeaturesPrivileges?: SubFeaturesPrivileges[];
}; };
export type AppFeaturesConfig<T extends string = string> = Map< export type ProductFeaturesConfig<T extends string = string> = Map<
AppFeatureKeyType, ProductFeatureKeyType,
AppFeatureKibanaConfig<T> ProductFeatureKibanaConfig<T>
>; >;
export type AppFeaturesSecurityConfig = Map< export type ProductFeaturesSecurityConfig = Map<
AppFeatureSecurityKey, ProductFeatureSecurityKey,
AppFeatureKibanaConfig<SecuritySubFeatureId> ProductFeatureKibanaConfig<SecuritySubFeatureId>
>; >;
export type AppFeaturesCasesConfig = Map< export type ProductFeaturesCasesConfig = Map<
AppFeatureCasesKey, ProductFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId> ProductFeatureKibanaConfig<CasesSubFeatureId>
>; >;
export type AppFeaturesAssistantConfig = Map< export type ProductFeaturesAssistantConfig = Map<
AppFeatureAssistantKey, ProductFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId> ProductFeatureKibanaConfig<AssistantSubFeatureId>
>; >;
export type AppSubFeaturesMap<T extends string = string> = Map<T, SubFeatureConfig>; export type AppSubFeaturesMap<T extends string = string> = Map<T, SubFeatureConfig>;
export interface AppFeatureParams<T extends string = string> { export interface ProductFeatureParams<T extends string = string> {
baseKibanaFeature: BaseKibanaFeatureConfig; baseKibanaFeature: BaseKibanaFeatureConfig;
baseKibanaSubFeatureIds: T[]; baseKibanaSubFeatureIds: T[];
subFeaturesMap: AppSubFeaturesMap<T>; subFeaturesMap: AppSubFeaturesMap<T>;

View file

@ -8,7 +8,7 @@
import type { ENDPOINT_PRIVILEGES, FleetAuthz } from '@kbn/fleet-plugin/common'; import type { ENDPOINT_PRIVILEGES, FleetAuthz } from '@kbn/fleet-plugin/common';
import { omit } from 'lodash'; import { omit } from 'lodash';
import type { AppFeaturesService } from '../../../../server/lib/app_features_service'; import type { ProductFeaturesService } from '../../../../server/lib/product_features_service';
import { RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ } from '../response_actions/constants'; import { RESPONSE_CONSOLE_ACTION_COMMANDS_TO_REQUIRED_AUTHZ } from '../response_actions/constants';
import type { LicenseService } from '../../../license'; import type { LicenseService } from '../../../license';
import type { EndpointAuthz } from '../../types/authz'; import type { EndpointAuthz } from '../../types/authz';
@ -22,19 +22,19 @@ import type { MaybeImmutable } from '../../types';
* level, use `calculateEndpointAuthz()` * level, use `calculateEndpointAuthz()`
* *
* @param fleetAuthz * @param fleetAuthz
* @param appFeatureService * @param productFeatureService
*/ */
function hasAuthFactory(fleetAuthz: FleetAuthz, appFeatureService?: AppFeaturesService) { function hasAuthFactory(fleetAuthz: FleetAuthz, productFeatureService?: ProductFeaturesService) {
return function hasAuth( return function hasAuth(
privilege: keyof typeof ENDPOINT_PRIVILEGES, privilege: keyof typeof ENDPOINT_PRIVILEGES,
{ action }: { action?: string } = {} { action }: { action?: string } = {}
): boolean { ): boolean {
// Product features control // Product features control
if (appFeatureService) { if (productFeatureService) {
// Only server side has to check this, to prevent "superuser" role from being allowed to use product gated APIs. // Only server side has to check this, to prevent "superuser" role from being allowed to use product gated APIs.
// UI side does not need to check this. Capabilities list is correct for superuser. // UI side does not need to check this. Capabilities list is correct for superuser.
const actionToCheck = action ?? appFeatureService.getApiActionName(privilege); const actionToCheck = action ?? productFeatureService.getApiActionName(privilege);
if (!appFeatureService.isActionRegistered(actionToCheck)) { if (!productFeatureService.isActionRegistered(actionToCheck)) {
return false; return false;
} }
} }
@ -58,9 +58,9 @@ export const calculateEndpointAuthz = (
licenseService: LicenseService, licenseService: LicenseService,
fleetAuthz: FleetAuthz, fleetAuthz: FleetAuthz,
userRoles: MaybeImmutable<string[]> = [], userRoles: MaybeImmutable<string[]> = [],
appFeaturesService?: AppFeaturesService // only exists on the server side productFeaturesService?: ProductFeaturesService // only exists on the server side
): EndpointAuthz => { ): EndpointAuthz => {
const hasAuth = hasAuthFactory(fleetAuthz, appFeaturesService); const hasAuth = hasAuthFactory(fleetAuthz, productFeaturesService);
const isPlatinumPlusLicense = licenseService.isPlatinumPlus(); const isPlatinumPlusLicense = licenseService.isPlatinumPlus();
const isEnterpriseLicense = licenseService.isEnterprise(); const isEnterpriseLicense = licenseService.isEnterprise();

View file

@ -9,7 +9,7 @@ import React from 'react';
import { EuiCode, EuiEmptyPrompt } from '@elastic/eui'; import { EuiCode, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import { useIsMounted } from '@kbn/securitysolution-hook-utils'; import { useIsMounted } from '@kbn/securitysolution-hook-utils';
import { AppFeatureKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import { useUpsellingComponent } from '../../../common/hooks/use_upselling'; import { useUpsellingComponent } from '../../../common/hooks/use_upselling';
import { ResponseActionFormField } from './osquery_response_action_form_field'; import { ResponseActionFormField } from './osquery_response_action_form_field';
import type { ArrayItem } from '../../../shared_imports'; import type { ArrayItem } from '../../../shared_imports';
@ -28,7 +28,9 @@ export const OsqueryResponseAction = React.memo((props: OsqueryResponseActionPro
const isMounted = useIsMounted(); const isMounted = useIsMounted();
// serverless component that is returned when users do not have Endpoint.Complete tier // serverless component that is returned when users do not have Endpoint.Complete tier
const UpsellingComponent = useUpsellingComponent(AppFeatureKey.osqueryAutomatedResponseActions); const UpsellingComponent = useUpsellingComponent(
ProductFeatureKey.osqueryAutomatedResponseActions
);
if (osquery) { if (osquery) {
const { disabled, permissionDenied } = osquery.fetchInstallationStatus(); const { disabled, permissionDenied } = osquery.fetchInstallationStatus();

View file

@ -50,7 +50,7 @@ import { calculateEndpointAuthz } from '../../common/endpoint/service/authz';
import type { FeatureUsageService } from './services/feature_usage/service'; import type { FeatureUsageService } from './services/feature_usage/service';
import type { ExperimentalFeatures } from '../../common/experimental_features'; import type { ExperimentalFeatures } from '../../common/experimental_features';
import type { ActionCreateService } from './services/actions/create/types'; import type { ActionCreateService } from './services/actions/create/types';
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service';
export interface EndpointAppContextServiceSetupContract { export interface EndpointAppContextServiceSetupContract {
securitySolutionRequestContextFactory: IRequestContextFactory; securitySolutionRequestContextFactory: IRequestContextFactory;
@ -79,7 +79,7 @@ export interface EndpointAppContextServiceStartContract {
messageSigningService: MessageSigningServiceInterface | undefined; messageSigningService: MessageSigningServiceInterface | undefined;
actionCreateService: ActionCreateService | undefined; actionCreateService: ActionCreateService | undefined;
esClient: ElasticsearchClient; esClient: ElasticsearchClient;
appFeaturesService: AppFeaturesService; productFeaturesService: ProductFeaturesService;
savedObjectsClient: SavedObjectsClientContract; savedObjectsClient: SavedObjectsClientContract;
} }
@ -117,17 +117,17 @@ export class EndpointAppContextService {
featureUsageService, featureUsageService,
endpointMetadataService, endpointMetadataService,
esClient, esClient,
appFeaturesService, productFeaturesService,
savedObjectsClient, savedObjectsClient,
} = dependencies; } = dependencies;
registerIngestCallback( registerIngestCallback(
'agentPolicyCreate', 'agentPolicyCreate',
getAgentPolicyCreateCallback(logger, appFeaturesService) getAgentPolicyCreateCallback(logger, productFeaturesService)
); );
registerIngestCallback( registerIngestCallback(
'agentPolicyUpdate', 'agentPolicyUpdate',
getAgentPolicyUpdateCallback(logger, appFeaturesService) getAgentPolicyUpdateCallback(logger, productFeaturesService)
); );
registerIngestCallback( registerIngestCallback(
@ -140,7 +140,7 @@ export class EndpointAppContextService {
licenseService, licenseService,
exceptionListsClient, exceptionListsClient,
this.setupDependencies.cloud, this.setupDependencies.cloud,
appFeaturesService productFeaturesService
) )
); );
@ -158,7 +158,7 @@ export class EndpointAppContextService {
endpointMetadataService, endpointMetadataService,
this.setupDependencies.cloud, this.setupDependencies.cloud,
esClient, esClient,
appFeaturesService productFeaturesService
) )
); );
@ -194,7 +194,7 @@ export class EndpointAppContextService {
} }
public async getEndpointAuthz(request: KibanaRequest): Promise<EndpointAuthz> { public async getEndpointAuthz(request: KibanaRequest): Promise<EndpointAuthz> {
if (!this.startDependencies?.appFeaturesService) { if (!this.startDependencies?.productFeaturesService) {
throw new EndpointAppContentServicesNotStartedError(); throw new EndpointAppContentServicesNotStartedError();
} }
const fleetAuthz = await this.getFleetAuthzService().fromRequest(request); const fleetAuthz = await this.getFleetAuthzService().fromRequest(request);
@ -203,7 +203,7 @@ export class EndpointAppContextService {
this.getLicenseService(), this.getLicenseService(),
fleetAuthz, fleetAuthz,
userRoles, userRoles,
this.startDependencies.appFeaturesService this.startDependencies.productFeaturesService
); );
} }

View file

@ -9,25 +9,25 @@ import { createMockEndpointAppContextServiceStartContract } from '../mocks';
import type { Logger } from '@kbn/logging'; import type { Logger } from '@kbn/logging';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
import { createAppFeaturesServiceMock } from '../../lib/app_features_service/mocks'; import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks';
import { turnOffAgentPolicyFeatures } from './turn_off_agent_policy_features'; import { turnOffAgentPolicyFeatures } from './turn_off_agent_policy_features';
describe('Turn Off Agent Policy Features Migration', () => { describe('Turn Off Agent Policy Features Migration', () => {
let fleetServices: EndpointInternalFleetServicesInterface; let fleetServices: EndpointInternalFleetServicesInterface;
let appFeatureService: AppFeaturesService; let productFeatureService: ProductFeaturesService;
let logger: Logger; let logger: Logger;
const callTurnOffAgentPolicyFeatures = () => const callTurnOffAgentPolicyFeatures = () =>
turnOffAgentPolicyFeatures(fleetServices, appFeatureService, logger); turnOffAgentPolicyFeatures(fleetServices, productFeatureService, logger);
beforeEach(() => { beforeEach(() => {
const endpointContextStartContract = createMockEndpointAppContextServiceStartContract(); const endpointContextStartContract = createMockEndpointAppContextServiceStartContract();
({ logger } = endpointContextStartContract); ({ logger } = endpointContextStartContract);
appFeatureService = endpointContextStartContract.appFeaturesService; productFeatureService = endpointContextStartContract.productFeaturesService;
fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser(); fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser();
}); });
@ -46,8 +46,8 @@ describe('Turn Off Agent Policy Features Migration', () => {
describe('and `agentTamperProtection` is disabled', () => { describe('and `agentTamperProtection` is disabled', () => {
beforeEach(() => { beforeEach(() => {
appFeatureService = createAppFeaturesServiceMock( productFeatureService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_agent_tamper_protection') ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_agent_tamper_protection')
); );
}); });

View file

@ -6,27 +6,27 @@
*/ */
import type { Logger } from '@kbn/core/server'; import type { Logger } from '@kbn/core/server';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
export const turnOffAgentPolicyFeatures = async ( export const turnOffAgentPolicyFeatures = async (
fleetServices: EndpointInternalFleetServicesInterface, fleetServices: EndpointInternalFleetServicesInterface,
appFeaturesService: AppFeaturesService, productFeaturesService: ProductFeaturesService,
logger: Logger logger: Logger
): Promise<void> => { ): Promise<void> => {
const log = logger.get('endpoint', 'agentPolicyFeatures'); const log = logger.get('endpoint', 'agentPolicyFeatures');
if (appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection)) { if (productFeaturesService.isEnabled(ProductFeatureSecurityKey.endpointAgentTamperProtection)) {
log.info( log.info(
`App feature [${AppFeatureSecurityKey.endpointAgentTamperProtection}] is enabled. Nothing to do!` `App feature [${ProductFeatureSecurityKey.endpointAgentTamperProtection}] is enabled. Nothing to do!`
); );
return; return;
} }
log.info( log.info(
`App feature [${AppFeatureSecurityKey.endpointAgentTamperProtection}] is disabled. Checking fleet agent policies for compliance` `App feature [${ProductFeatureSecurityKey.endpointAgentTamperProtection}] is disabled. Checking fleet agent policies for compliance`
); );
const { agentPolicy: agentPolicyService, internalSoClient } = fleetServices; const { agentPolicy: agentPolicyService, internalSoClient } = fleetServices;

View file

@ -10,24 +10,24 @@ import type { Logger } from '@kbn/logging';
import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server'; import type { ElasticsearchClient } from '@kbn/core-elasticsearch-server';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { turnOffPolicyProtectionsIfNotSupported } from './turn_off_policy_protections'; import { turnOffPolicyProtectionsIfNotSupported } from './turn_off_policy_protections';
import { FleetPackagePolicyGenerator } from '../../../common/endpoint/data_generators/fleet_package_policy_generator'; import { FleetPackagePolicyGenerator } from '../../../common/endpoint/data_generators/fleet_package_policy_generator';
import type { PolicyData } from '../../../common/endpoint/types'; import type { PolicyData } from '../../../common/endpoint/types';
import type { PackagePolicyClient } from '@kbn/fleet-plugin/server'; import type { PackagePolicyClient } from '@kbn/fleet-plugin/server';
import type { PromiseResolvedValue } from '../../../common/endpoint/types/utility_types'; import type { PromiseResolvedValue } from '../../../common/endpoint/types/utility_types';
import { ensureOnlyEventCollectionIsAllowed } from '../../../common/endpoint/models/policy_config_helpers'; import { ensureOnlyEventCollectionIsAllowed } from '../../../common/endpoint/models/policy_config_helpers';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
import { createAppFeaturesServiceMock } from '../../lib/app_features_service/mocks'; import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks';
describe('Turn Off Policy Protections Migration', () => { describe('Turn Off Policy Protections Migration', () => {
let esClient: ElasticsearchClient; let esClient: ElasticsearchClient;
let fleetServices: EndpointInternalFleetServicesInterface; let fleetServices: EndpointInternalFleetServicesInterface;
let appFeatureService: AppFeaturesService; let productFeatureService: ProductFeaturesService;
let logger: Logger; let logger: Logger;
const callTurnOffPolicyProtections = () => const callTurnOffPolicyProtections = () =>
turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, appFeatureService, logger); turnOffPolicyProtectionsIfNotSupported(esClient, fleetServices, productFeatureService, logger);
const generatePolicyMock = ( const generatePolicyMock = (
policyGenerator: FleetPackagePolicyGenerator, policyGenerator: FleetPackagePolicyGenerator,
@ -60,7 +60,7 @@ describe('Turn Off Policy Protections Migration', () => {
({ esClient, logger } = endpointContextStartContract); ({ esClient, logger } = endpointContextStartContract);
appFeatureService = endpointContextStartContract.appFeaturesService; productFeatureService = endpointContextStartContract.productFeaturesService;
fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser(); fleetServices = endpointContextStartContract.endpointFleetServicesFactory.asInternalUser();
}); });
@ -89,8 +89,8 @@ describe('Turn Off Policy Protections Migration', () => {
policyGenerator = new FleetPackagePolicyGenerator('seed'); policyGenerator = new FleetPackagePolicyGenerator('seed');
const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock; const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock;
appFeatureService = createAppFeaturesServiceMock( productFeatureService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates') ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates')
); );
page1Items = [ page1Items = [
@ -160,8 +160,8 @@ describe('Turn Off Policy Protections Migration', () => {
policyGenerator = new FleetPackagePolicyGenerator('seed'); policyGenerator = new FleetPackagePolicyGenerator('seed');
const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock; const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock;
appFeatureService = createAppFeaturesServiceMock( productFeatureService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
); );
page1Items = [ page1Items = [
@ -253,8 +253,8 @@ describe('Turn Off Policy Protections Migration', () => {
policyGenerator = new FleetPackagePolicyGenerator('seed'); policyGenerator = new FleetPackagePolicyGenerator('seed');
const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock; const packagePolicyListSrv = fleetServices.packagePolicy.list as jest.Mock;
appFeatureService = createAppFeaturesServiceMock( productFeatureService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter( ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== 'endpoint_policy_protections' && key !== 'endpoint_protection_updates' (key) => key !== 'endpoint_policy_protections' && key !== 'endpoint_protection_updates'
) )
); );

View file

@ -8,7 +8,7 @@
import type { ElasticsearchClient, Logger } from '@kbn/core/server'; import type { ElasticsearchClient, Logger } from '@kbn/core/server';
import type { UpdatePackagePolicy } from '@kbn/fleet-plugin/common'; import type { UpdatePackagePolicy } from '@kbn/fleet-plugin/common';
import type { AuthenticatedUser } from '@kbn/security-plugin/common'; import type { AuthenticatedUser } from '@kbn/security-plugin/common';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { import {
ensureOnlyEventCollectionIsAllowed, ensureOnlyEventCollectionIsAllowed,
isPolicySetToEventCollectionOnly, isPolicySetToEventCollectionOnly,
@ -16,33 +16,33 @@ import {
import type { PolicyData } from '../../../common/endpoint/types'; import type { PolicyData } from '../../../common/endpoint/types';
import type { EndpointInternalFleetServicesInterface } from '../services/fleet'; import type { EndpointInternalFleetServicesInterface } from '../services/fleet';
import { getPolicyDataForUpdate } from '../../../common/endpoint/service/policy'; import { getPolicyDataForUpdate } from '../../../common/endpoint/service/policy';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
export const turnOffPolicyProtectionsIfNotSupported = async ( export const turnOffPolicyProtectionsIfNotSupported = async (
esClient: ElasticsearchClient, esClient: ElasticsearchClient,
fleetServices: EndpointInternalFleetServicesInterface, fleetServices: EndpointInternalFleetServicesInterface,
appFeaturesService: AppFeaturesService, productFeaturesService: ProductFeaturesService,
logger: Logger logger: Logger
): Promise<void> => { ): Promise<void> => {
const log = logger.get('endpoint', 'policyProtections'); const log = logger.get('endpoint', 'policyProtections');
const isProtectionUpdatesFeatureEnabled = appFeaturesService.isEnabled( const isProtectionUpdatesFeatureEnabled = productFeaturesService.isEnabled(
AppFeatureSecurityKey.endpointProtectionUpdates ProductFeatureSecurityKey.endpointProtectionUpdates
); );
const isPolicyProtectionsEnabled = appFeaturesService.isEnabled( const isPolicyProtectionsEnabled = productFeaturesService.isEnabled(
AppFeatureSecurityKey.endpointPolicyProtections ProductFeatureSecurityKey.endpointPolicyProtections
); );
if (isPolicyProtectionsEnabled) { if (isPolicyProtectionsEnabled) {
log.info( log.info(
`App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is enabled. Nothing to do!` `App feature [${ProductFeatureSecurityKey.endpointPolicyProtections}] is enabled. Nothing to do!`
); );
} }
if (isProtectionUpdatesFeatureEnabled) { if (isProtectionUpdatesFeatureEnabled) {
log.info( log.info(
`App feature [${AppFeatureSecurityKey.endpointProtectionUpdates}] is enabled. Nothing to do!` `App feature [${ProductFeatureSecurityKey.endpointProtectionUpdates}] is enabled. Nothing to do!`
); );
} }
@ -52,13 +52,13 @@ export const turnOffPolicyProtectionsIfNotSupported = async (
if (!isPolicyProtectionsEnabled) { if (!isPolicyProtectionsEnabled) {
log.info( log.info(
`App feature [${AppFeatureSecurityKey.endpointPolicyProtections}] is disabled. Checking endpoint integration policies for compliance` `App feature [${ProductFeatureSecurityKey.endpointPolicyProtections}] is disabled. Checking endpoint integration policies for compliance`
); );
} }
if (!isProtectionUpdatesFeatureEnabled) { if (!isProtectionUpdatesFeatureEnabled) {
log.info( log.info(
`App feature [${AppFeatureSecurityKey.endpointProtectionUpdates}] is disabled. Checking endpoint integration policies for compliance` `App feature [${ProductFeatureSecurityKey.endpointProtectionUpdates}] is disabled. Checking endpoint integration policies for compliance`
); );
} }

View file

@ -71,7 +71,7 @@ import type { EndpointAuthz } from '../../common/endpoint/types/authz';
import { EndpointFleetServicesFactory } from './services/fleet'; import { EndpointFleetServicesFactory } from './services/fleet';
import { createLicenseServiceMock } from '../../common/license/mocks'; import { createLicenseServiceMock } from '../../common/license/mocks';
import { createFeatureUsageServiceMock } from './services/feature_usage/mocks'; import { createFeatureUsageServiceMock } from './services/feature_usage/mocks';
import { createAppFeaturesServiceMock } from '../lib/app_features_service/mocks'; import { createProductFeaturesServiceMock } from '../lib/product_features_service/mocks';
/** /**
* Creates a mocked EndpointAppContext. * Creates a mocked EndpointAppContext.
@ -170,7 +170,7 @@ export const createMockEndpointAppContextServiceStartContract =
savedObjectsStart savedObjectsStart
); );
const experimentalFeatures = config.experimentalFeatures; const experimentalFeatures = config.experimentalFeatures;
const appFeaturesService = createAppFeaturesServiceMock( const productFeaturesService = createProductFeaturesServiceMock(
undefined, undefined,
experimentalFeatures, experimentalFeatures,
undefined, undefined,
@ -224,7 +224,7 @@ export const createMockEndpointAppContextServiceStartContract =
actionCreateService: undefined, actionCreateService: undefined,
createFleetActionsClient: jest.fn((_) => fleetActionsClientMock), createFleetActionsClient: jest.fn((_) => fleetActionsClientMock),
esClient: elasticsearchClientMock.createElasticsearchClient(), esClient: elasticsearchClientMock.createElasticsearchClient(),
appFeaturesService, productFeaturesService,
savedObjectsClient: savedObjectsClientMock.create(), savedObjectsClient: savedObjectsClientMock.create(),
}; };
}; };

View file

@ -16,7 +16,7 @@ import { createPackagePolicyServiceMock } from '@kbn/fleet-plugin/server/mocks';
import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import { listMock } from '@kbn/lists-plugin/server/mocks'; import { listMock } from '@kbn/lists-plugin/server/mocks';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import type { AppFeatureKeys } from '@kbn/security-solution-features'; import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import { import {
createPackagePolicyWithInitialManifestMock, createPackagePolicyWithInitialManifestMock,
createPackagePolicyWithManifestMock, createPackagePolicyWithManifestMock,
@ -28,8 +28,8 @@ import { createEndpointArtifactClientMock, getManifestClientMock } from '../mock
import type { ManifestManagerContext } from './manifest_manager'; import type { ManifestManagerContext } from './manifest_manager';
import { ManifestManager } from './manifest_manager'; import { ManifestManager } from './manifest_manager';
import { parseExperimentalConfigValue } from '../../../../../common/experimental_features'; import { parseExperimentalConfigValue } from '../../../../../common/experimental_features';
import { createAppFeaturesServiceMock } from '../../../../lib/app_features_service/mocks'; import { createProductFeaturesServiceMock } from '../../../../lib/product_features_service/mocks';
import type { AppFeaturesService } from '../../../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../../../lib/product_features_service/product_features_service';
export const createExceptionListResponse = (data: ExceptionListItemSchema[], total?: number) => ({ export const createExceptionListResponse = (data: ExceptionListItemSchema[], total?: number) => ({
data, data,
@ -71,28 +71,28 @@ export interface ManifestManagerMockOptions {
exceptionListClient: ExceptionListClient; exceptionListClient: ExceptionListClient;
packagePolicyService: jest.Mocked<PackagePolicyClient>; packagePolicyService: jest.Mocked<PackagePolicyClient>;
savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>; savedObjectsClient: ReturnType<typeof savedObjectsClientMock.create>;
appFeaturesService: AppFeaturesService; productFeaturesService: ProductFeaturesService;
} }
export const buildManifestManagerMockOptions = ( export const buildManifestManagerMockOptions = (
opts: Partial<ManifestManagerMockOptions>, opts: Partial<ManifestManagerMockOptions>,
customAppFeatures?: AppFeatureKeys customProductFeatures?: ProductFeatureKeys
): ManifestManagerMockOptions => { ): ManifestManagerMockOptions => {
const savedObjectMock = savedObjectsClientMock.create(); const savedObjectMock = savedObjectsClientMock.create();
return { return {
exceptionListClient: listMock.getExceptionListClient(savedObjectMock), exceptionListClient: listMock.getExceptionListClient(savedObjectMock),
packagePolicyService: createPackagePolicyServiceMock(), packagePolicyService: createPackagePolicyServiceMock(),
savedObjectsClient: savedObjectMock, savedObjectsClient: savedObjectMock,
appFeaturesService: createAppFeaturesServiceMock(customAppFeatures), productFeaturesService: createProductFeaturesServiceMock(customProductFeatures),
...opts, ...opts,
}; };
}; };
export const buildManifestManagerContextMock = ( export const buildManifestManagerContextMock = (
opts: Partial<ManifestManagerMockOptions>, opts: Partial<ManifestManagerMockOptions>,
customAppFeatures?: AppFeatureKeys customProductFeatures?: ProductFeatureKeys
): ManifestManagerContext => { ): ManifestManagerContext => {
const fullOpts = buildManifestManagerMockOptions(opts, customAppFeatures); const fullOpts = buildManifestManagerMockOptions(opts, customProductFeatures);
return { return {
...fullOpts, ...fullOpts,

View file

@ -37,7 +37,7 @@ import type { EndpointArtifactClientInterface } from '../artifact_client';
import { InvalidInternalManifestError } from '../errors'; import { InvalidInternalManifestError } from '../errors';
import { EndpointError } from '../../../../../common/endpoint/errors'; import { EndpointError } from '../../../../../common/endpoint/errors';
import type { Artifact } from '@kbn/fleet-plugin/server'; import type { Artifact } from '@kbn/fleet-plugin/server';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types/src/response/exception_list_item_schema'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types/src/response/exception_list_item_schema';
import { import {
createFetchAllArtifactsIterableMock, createFetchAllArtifactsIterableMock,
@ -775,7 +775,7 @@ describe('ManifestManager', () => {
tags: ['policy:all'], tags: ['policy:all'],
}); });
const context = buildManifestManagerContextMock({}, [ const context = buildManifestManagerContextMock({}, [
AppFeatureSecurityKey.endpointArtifactManagement, ProductFeatureSecurityKey.endpointArtifactManagement,
]); ]);
const manifestManager = new ManifestManager(context); const manifestManager = new ManifestManager(context);
@ -859,8 +859,8 @@ describe('ManifestManager', () => {
tags: ['policy:all'], tags: ['policy:all'],
}); });
const context = buildManifestManagerContextMock({}, [ const context = buildManifestManagerContextMock({}, [
AppFeatureSecurityKey.endpointArtifactManagement, ProductFeatureSecurityKey.endpointArtifactManagement,
AppFeatureSecurityKey.endpointResponseActions, ProductFeatureSecurityKey.endpointResponseActions,
]); ]);
const manifestManager = new ManifestManager(context); const manifestManager = new ManifestManager(context);

View file

@ -14,10 +14,10 @@ import type { PackagePolicy } from '@kbn/fleet-plugin/common';
import type { Artifact, PackagePolicyClient } from '@kbn/fleet-plugin/server'; import type { Artifact, PackagePolicyClient } from '@kbn/fleet-plugin/server';
import type { ExceptionListClient } from '@kbn/lists-plugin/server'; import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types'; import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { AppFeatureKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import { stringify } from '../../../utils/stringify'; import { stringify } from '../../../utils/stringify';
import { QueueProcessor } from '../../../utils/queue_processor'; import { QueueProcessor } from '../../../utils/queue_processor';
import type { AppFeaturesService } from '../../../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../../../lib/product_features_service/product_features_service';
import type { ExperimentalFeatures } from '../../../../../common'; import type { ExperimentalFeatures } from '../../../../../common';
import type { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common'; import type { ManifestSchemaVersion } from '../../../../../common/endpoint/schema/common';
import { import {
@ -81,7 +81,7 @@ export interface ManifestManagerContext {
experimentalFeatures: ExperimentalFeatures; experimentalFeatures: ExperimentalFeatures;
packagerTaskPackagePolicyUpdateBatchSize: number; packagerTaskPackagePolicyUpdateBatchSize: number;
esClient: ElasticsearchClient; esClient: ElasticsearchClient;
appFeaturesService: AppFeaturesService; productFeaturesService: ProductFeaturesService;
} }
const getArtifactIds = (manifest: ManifestSchema) => const getArtifactIds = (manifest: ManifestSchema) =>
@ -103,7 +103,7 @@ export class ManifestManager {
protected cachedExceptionsListsByOs: Map<string, ExceptionListItemSchema[]>; protected cachedExceptionsListsByOs: Map<string, ExceptionListItemSchema[]>;
protected packagerTaskPackagePolicyUpdateBatchSize: number; protected packagerTaskPackagePolicyUpdateBatchSize: number;
protected esClient: ElasticsearchClient; protected esClient: ElasticsearchClient;
protected appFeaturesService: AppFeaturesService; protected productFeaturesService: ProductFeaturesService;
constructor(context: ManifestManagerContext) { constructor(context: ManifestManagerContext) {
this.artifactClient = context.artifactClient; this.artifactClient = context.artifactClient;
@ -117,7 +117,7 @@ export class ManifestManager {
this.packagerTaskPackagePolicyUpdateBatchSize = this.packagerTaskPackagePolicyUpdateBatchSize =
context.packagerTaskPackagePolicyUpdateBatchSize; context.packagerTaskPackagePolicyUpdateBatchSize;
this.esClient = context.esClient; this.esClient = context.esClient;
this.appFeaturesService = context.appFeaturesService; this.productFeaturesService = context.productFeaturesService;
} }
/** /**
@ -151,9 +151,9 @@ export class ManifestManager {
let itemsByListId: ExceptionListItemSchema[] = []; let itemsByListId: ExceptionListItemSchema[] = [];
if ( if (
(listId === ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id && (listId === ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id &&
this.appFeaturesService.isEnabled(AppFeatureKey.endpointResponseActions)) || this.productFeaturesService.isEnabled(ProductFeatureKey.endpointResponseActions)) ||
(listId !== ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id && (listId !== ENDPOINT_ARTIFACT_LISTS.hostIsolationExceptions.id &&
this.appFeaturesService.isEnabled(AppFeatureKey.endpointArtifactManagement)) this.productFeaturesService.isEnabled(ProductFeatureKey.endpointArtifactManagement))
) { ) {
itemsByListId = await getAllItemsFromEndpointExceptionList({ itemsByListId = await getAllItemsFromEndpointExceptionList({
elClient, elClient,

View file

@ -32,7 +32,10 @@ import {
getPackagePolicyUpdateCallback, getPackagePolicyUpdateCallback,
} from './fleet_integration'; } from './fleet_integration';
import type { KibanaRequest, Logger } from '@kbn/core/server'; import type { KibanaRequest, Logger } from '@kbn/core/server';
import { ALL_APP_FEATURE_KEYS, AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import {
ALL_PRODUCT_FEATURE_KEYS,
ProductFeatureSecurityKey,
} from '@kbn/security-solution-features/keys';
import { requestContextMock } from '../lib/detection_engine/routes/__mocks__'; import { requestContextMock } from '../lib/detection_engine/routes/__mocks__';
import { requestContextFactoryMock } from '../request_context_factory.mock'; import { requestContextFactoryMock } from '../request_context_factory.mock';
import type { EndpointAppContextServiceStartContract } from '../endpoint/endpoint_app_context_services'; import type { EndpointAppContextServiceStartContract } from '../endpoint/endpoint_app_context_services';
@ -60,8 +63,8 @@ import { createMockPolicyData } from '../endpoint/services/feature_usage/mocks';
import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants'; import { ALL_ENDPOINT_ARTIFACT_LIST_IDS } from '../../common/endpoint/service/artifacts/constants';
import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants'; import { ENDPOINT_EVENT_FILTERS_LIST_ID } from '@kbn/securitysolution-list-constants';
import { disableProtections } from '../../common/endpoint/models/policy_config_helpers'; import { disableProtections } from '../../common/endpoint/models/policy_config_helpers';
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service';
import { createAppFeaturesServiceMock } from '../lib/app_features_service/mocks'; import { createProductFeaturesServiceMock } from '../lib/product_features_service/mocks';
import * as moment from 'moment'; import * as moment from 'moment';
import type { PostAgentPolicyCreateCallback } from '@kbn/fleet-plugin/server/types'; import type { PostAgentPolicyCreateCallback } from '@kbn/fleet-plugin/server/types';
@ -87,7 +90,7 @@ describe('ingest_integration tests ', () => {
}); });
const generator = new EndpointDocGenerator(); const generator = new EndpointDocGenerator();
const cloudService = cloudMock.createSetup(); const cloudService = cloudMock.createSetup();
let appFeaturesService: AppFeaturesService; let productFeaturesService: ProductFeaturesService;
beforeEach(() => { beforeEach(() => {
endpointAppContextMock = createMockEndpointAppContextServiceStartContract(); endpointAppContextMock = createMockEndpointAppContextServiceStartContract();
@ -96,7 +99,7 @@ describe('ingest_integration tests ', () => {
licenseEmitter = new Subject(); licenseEmitter = new Subject();
licenseService = new LicenseService(); licenseService = new LicenseService();
licenseService.start(licenseEmitter); licenseService.start(licenseEmitter);
appFeaturesService = endpointAppContextMock.appFeaturesService; productFeaturesService = endpointAppContextMock.productFeaturesService;
jest jest
.spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy') .spyOn(endpointAppContextMock.endpointMetadataService, 'getFleetEndpointPackagePolicy')
@ -153,7 +156,7 @@ describe('ingest_integration tests ', () => {
licenseService, licenseService,
exceptionListClient, exceptionListClient,
cloudService, cloudService,
appFeaturesService productFeaturesService
); );
return callback( return callback(
@ -389,15 +392,15 @@ describe('ingest_integration tests ', () => {
}); });
describe('agent policy update callback', () => { describe('agent policy update callback', () => {
it('AppFeature disabled - returns an error if higher tier features are turned on in the policy', async () => { it('ProductFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test'); const logger = loggingSystemMock.create().get('ingest_integration.test');
appFeaturesService = createAppFeaturesServiceMock( productFeaturesService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter( ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection (key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
) )
); );
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService); const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy(); const policyConfig = generator.generateAgentPolicy();
policyConfig.is_protected = true; policyConfig.is_protected = true;
@ -406,15 +409,15 @@ describe('ingest_integration tests ', () => {
'Agent Tamper Protection is not allowed in current environment' 'Agent Tamper Protection is not allowed in current environment'
); );
}); });
it('AppFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => { it('ProductFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test'); const logger = loggingSystemMock.create().get('ingest_integration.test');
appFeaturesService = createAppFeaturesServiceMock( productFeaturesService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter( ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection (key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
) )
); );
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService); const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy(); const policyConfig = generator.generateAgentPolicy();
@ -422,10 +425,10 @@ describe('ingest_integration tests ', () => {
expect(updatedPolicyConfig).toEqual(policyConfig); expect(updatedPolicyConfig).toEqual(policyConfig);
}); });
it('AppFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => { it('ProductFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test'); const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService); const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy(); const policyConfig = generator.generateAgentPolicy();
policyConfig.is_protected = true; policyConfig.is_protected = true;
@ -434,10 +437,10 @@ describe('ingest_integration tests ', () => {
expect(updatedPolicyConfig).toEqual(policyConfig); expect(updatedPolicyConfig).toEqual(policyConfig);
}); });
it('AppFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => { it('ProductFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
const logger = loggingSystemMock.create().get('ingest_integration.test'); const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getAgentPolicyUpdateCallback(logger, appFeaturesService); const callback = getAgentPolicyUpdateCallback(logger, productFeaturesService);
const policyConfig = generator.generateAgentPolicy(); const policyConfig = generator.generateAgentPolicy();
const updatedPolicyConfig = await callback(policyConfig); const updatedPolicyConfig = await callback(policyConfig);
@ -453,17 +456,17 @@ describe('ingest_integration tests ', () => {
beforeEach(() => { beforeEach(() => {
logger = loggingSystemMock.create().get('ingest_integration.test'); logger = loggingSystemMock.create().get('ingest_integration.test');
callback = getAgentPolicyCreateCallback(logger, appFeaturesService); callback = getAgentPolicyCreateCallback(logger, productFeaturesService);
policyConfig = generator.generateAgentPolicy(); policyConfig = generator.generateAgentPolicy();
}); });
it('AppFeature disabled - returns an error if higher tier features are turned on in the policy', async () => { it('ProductFeature disabled - returns an error if higher tier features are turned on in the policy', async () => {
appFeaturesService = createAppFeaturesServiceMock( productFeaturesService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter( ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection (key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
) )
); );
callback = getAgentPolicyCreateCallback(logger, appFeaturesService); callback = getAgentPolicyCreateCallback(logger, productFeaturesService);
policyConfig.is_protected = true; policyConfig.is_protected = true;
await expect(() => callback(policyConfig)).rejects.toThrow( await expect(() => callback(policyConfig)).rejects.toThrow(
@ -471,26 +474,26 @@ describe('ingest_integration tests ', () => {
); );
}); });
it('AppFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => { it('ProductFeature disabled - returns agent policy if higher tier features are turned off in the policy', async () => {
appFeaturesService = createAppFeaturesServiceMock( productFeaturesService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter( ALL_PRODUCT_FEATURE_KEYS.filter(
(key) => key !== AppFeatureSecurityKey.endpointAgentTamperProtection (key) => key !== ProductFeatureSecurityKey.endpointAgentTamperProtection
) )
); );
callback = getAgentPolicyCreateCallback(logger, appFeaturesService); callback = getAgentPolicyCreateCallback(logger, productFeaturesService);
const updatedPolicyConfig = await callback(policyConfig); const updatedPolicyConfig = await callback(policyConfig);
expect(updatedPolicyConfig).toEqual(policyConfig); expect(updatedPolicyConfig).toEqual(policyConfig);
}); });
it('AppFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => { it('ProductFeature enabled - returns agent policy if higher tier features are turned on in the policy', async () => {
policyConfig.is_protected = true; policyConfig.is_protected = true;
const updatedPolicyConfig = await callback(policyConfig); const updatedPolicyConfig = await callback(policyConfig);
expect(updatedPolicyConfig).toEqual(policyConfig); expect(updatedPolicyConfig).toEqual(policyConfig);
}); });
it('AppFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => { it('ProductFeature enabled - returns agent policy if higher tier features are turned off in the policy', async () => {
const updatedPolicyConfig = await callback(policyConfig); const updatedPolicyConfig = await callback(policyConfig);
expect(updatedPolicyConfig).toEqual(policyConfig); expect(updatedPolicyConfig).toEqual(policyConfig);
@ -514,7 +517,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy; policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
@ -533,7 +536,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy; policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
@ -558,9 +561,9 @@ describe('ingest_integration tests ', () => {
const validDateYesterday = moment.utc().subtract(1, 'day'); const validDateYesterday = moment.utc().subtract(1, 'day');
it('should throw if endpointProtectionUpdates appFeature is disabled and user modifies global_manifest_version', () => { it('should throw if endpointProtectionUpdates productFeature is disabled and user modifies global_manifest_version', () => {
appFeaturesService = createAppFeaturesServiceMock( productFeaturesService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates') ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_protection_updates')
); );
const callback = getPackagePolicyUpdateCallback( const callback = getPackagePolicyUpdateCallback(
endpointAppContextMock.logger, endpointAppContextMock.logger,
@ -569,7 +572,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value.global_manifest_version = '2023-01-01'; policyConfig.inputs[0]!.config!.policy.value.global_manifest_version = '2023-01-01';
@ -624,7 +627,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = { policyConfig.inputs[0]!.config!.policy.value = {
@ -684,7 +687,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = { policyConfig.inputs[0]!.config!.policy.value = {
@ -722,7 +725,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy; policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
@ -736,9 +739,9 @@ describe('ingest_integration tests ', () => {
expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy); expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy);
}); });
it('should turn off protections if endpointPolicyProtections appFeature is disabled', async () => { it('should turn off protections if endpointPolicyProtections productFeature is disabled', async () => {
appFeaturesService = createAppFeaturesServiceMock( productFeaturesService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
); );
const callback = getPackagePolicyUpdateCallback( const callback = getPackagePolicyUpdateCallback(
endpointAppContextMock.logger, endpointAppContextMock.logger,
@ -747,7 +750,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const updatedPolicy = await callback( const updatedPolicy = await callback(
@ -826,7 +829,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
@ -863,7 +866,7 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.endpointMetadataService, endpointAppContextMock.endpointMetadataService,
cloudService, cloudService,
esClient, esClient,
appFeaturesService productFeaturesService
); );
const policyConfig = generator.generatePolicyPackagePolicy(); const policyConfig = generator.generatePolicyPackagePolicy();
// values should be updated // values should be updated

View file

@ -24,12 +24,12 @@ import type {
} from '@kbn/fleet-plugin/common'; } from '@kbn/fleet-plugin/common';
import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server';
import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type { import type {
PostAgentPolicyCreateCallback, PostAgentPolicyCreateCallback,
PostAgentPolicyUpdateCallback, PostAgentPolicyUpdateCallback,
} from '@kbn/fleet-plugin/server/types'; } from '@kbn/fleet-plugin/server/types';
import { validatePolicyAgainstAppFeatures } from './handlers/validate_policy_against_app_features'; import { validatePolicyAgainstProductFeatures } from './handlers/validate_policy_against_product_features';
import { validateEndpointPackagePolicy } from './handlers/validate_endpoint_package_policy'; import { validateEndpointPackagePolicy } from './handlers/validate_endpoint_package_policy';
import { import {
isPolicySetToEventCollectionOnly, isPolicySetToEventCollectionOnly,
@ -51,7 +51,7 @@ import { notifyProtectionFeatureUsage } from './notify_protection_feature_usage'
import type { AnyPolicyCreateConfig } from './types'; import type { AnyPolicyCreateConfig } from './types';
import { ENDPOINT_INTEGRATION_CONFIG_KEY } from './constants'; import { ENDPOINT_INTEGRATION_CONFIG_KEY } from './constants';
import { createEventFilters } from './handlers/create_event_filters'; import { createEventFilters } from './handlers/create_event_filters';
import type { AppFeaturesService } from '../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../lib/product_features_service/product_features_service';
import { removeProtectionUpdatesNote } from './handlers/remove_protection_updates_note'; import { removeProtectionUpdatesNote } from './handlers/remove_protection_updates_note';
const isEndpointPackagePolicy = <T extends { package?: { name: string } }>( const isEndpointPackagePolicy = <T extends { package?: { name: string } }>(
@ -90,7 +90,7 @@ export const getPackagePolicyCreateCallback = (
licenseService: LicenseService, licenseService: LicenseService,
exceptionsClient: ExceptionListClient | undefined, exceptionsClient: ExceptionListClient | undefined,
cloud: CloudSetup, cloud: CloudSetup,
appFeatures: AppFeaturesService productFeatures: ProductFeaturesService
): PostPackagePolicyCreateCallback => { ): PostPackagePolicyCreateCallback => {
return async ( return async (
newPackagePolicy, newPackagePolicy,
@ -111,7 +111,7 @@ export const getPackagePolicyCreateCallback = (
} }
if (newPackagePolicy?.inputs) { if (newPackagePolicy?.inputs) {
validatePolicyAgainstAppFeatures(newPackagePolicy.inputs, appFeatures); validatePolicyAgainstProductFeatures(newPackagePolicy.inputs, productFeatures);
validateEndpointPackagePolicy(newPackagePolicy.inputs); validateEndpointPackagePolicy(newPackagePolicy.inputs);
} }
// Optional endpoint integration configuration // Optional endpoint integration configuration
@ -163,7 +163,7 @@ export const getPackagePolicyCreateCallback = (
endpointIntegrationConfig, endpointIntegrationConfig,
cloud, cloud,
esClientInfo, esClientInfo,
appFeatures productFeatures
); );
return { return {
@ -199,7 +199,7 @@ export const getPackagePolicyUpdateCallback = (
endpointMetadataService: EndpointMetadataService, endpointMetadataService: EndpointMetadataService,
cloud: CloudSetup, cloud: CloudSetup,
esClient: ElasticsearchClient, esClient: ElasticsearchClient,
appFeatures: AppFeaturesService productFeatures: ProductFeaturesService
): PutPackagePolicyUpdateCallback => { ): PutPackagePolicyUpdateCallback => {
return async (newPackagePolicy: NewPackagePolicy): Promise<UpdatePackagePolicy> => { return async (newPackagePolicy: NewPackagePolicy): Promise<UpdatePackagePolicy> => {
if (!isEndpointPackagePolicy(newPackagePolicy)) { if (!isEndpointPackagePolicy(newPackagePolicy)) {
@ -218,7 +218,7 @@ export const getPackagePolicyUpdateCallback = (
); );
// Validate that Endpoint Security policy uses only enabled App Features // Validate that Endpoint Security policy uses only enabled App Features
validatePolicyAgainstAppFeatures(endpointIntegrationData.inputs, appFeatures); validatePolicyAgainstProductFeatures(endpointIntegrationData.inputs, productFeatures);
validateEndpointPackagePolicy(endpointIntegrationData.inputs); validateEndpointPackagePolicy(endpointIntegrationData.inputs);
@ -258,11 +258,11 @@ export const getPackagePolicyUpdateCallback = (
// If no Policy Protection allowed (ex. serverless) // If no Policy Protection allowed (ex. serverless)
const eventsOnlyPolicy = isPolicySetToEventCollectionOnly(newEndpointPackagePolicy); const eventsOnlyPolicy = isPolicySetToEventCollectionOnly(newEndpointPackagePolicy);
if ( if (
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections) && !productFeatures.isEnabled(ProductFeatureSecurityKey.endpointPolicyProtections) &&
!eventsOnlyPolicy.isOnlyCollectingEvents !eventsOnlyPolicy.isOnlyCollectingEvents
) { ) {
logger.warn( logger.warn(
`Endpoint integration policy [${endpointIntegrationData.id}][${endpointIntegrationData.name}] adjusted due to [endpointPolicyProtections] appFeature not being enabled. Trigger [${eventsOnlyPolicy.message}]` `Endpoint integration policy [${endpointIntegrationData.id}][${endpointIntegrationData.name}] adjusted due to [endpointPolicyProtections] productFeature not being enabled. Trigger [${eventsOnlyPolicy.message}]`
); );
endpointIntegrationData.inputs[0].config.policy.value = endpointIntegrationData.inputs[0].config.policy.value =
@ -317,12 +317,12 @@ const throwAgentTamperProtectionUnavailableError = (
export const getAgentPolicyCreateCallback = ( export const getAgentPolicyCreateCallback = (
logger: Logger, logger: Logger,
appFeatures: AppFeaturesService productFeatures: ProductFeaturesService
): PostAgentPolicyCreateCallback => { ): PostAgentPolicyCreateCallback => {
return async (agentPolicy: NewAgentPolicy): Promise<NewAgentPolicy> => { return async (agentPolicy: NewAgentPolicy): Promise<NewAgentPolicy> => {
if ( if (
agentPolicy.is_protected && agentPolicy.is_protected &&
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection) !productFeatures.isEnabled(ProductFeatureSecurityKey.endpointAgentTamperProtection)
) { ) {
throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id); throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id);
} }
@ -332,12 +332,12 @@ export const getAgentPolicyCreateCallback = (
export const getAgentPolicyUpdateCallback = ( export const getAgentPolicyUpdateCallback = (
logger: Logger, logger: Logger,
appFeatures: AppFeaturesService productFeatures: ProductFeaturesService
): PostAgentPolicyUpdateCallback => { ): PostAgentPolicyUpdateCallback => {
return async (agentPolicy: Partial<AgentPolicy>): Promise<Partial<AgentPolicy>> => { return async (agentPolicy: Partial<AgentPolicy>): Promise<Partial<AgentPolicy>> => {
if ( if (
agentPolicy.is_protected && agentPolicy.is_protected &&
!appFeatures.isEnabled(AppFeatureSecurityKey.endpointAgentTamperProtection) !productFeatures.isEnabled(ProductFeatureSecurityKey.endpointAgentTamperProtection)
) { ) {
throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id); throwAgentTamperProtectionUnavailableError(logger, agentPolicy.name, agentPolicy.id);
} }

View file

@ -8,7 +8,7 @@ import { Subject } from 'rxjs';
import type { ILicense } from '@kbn/licensing-plugin/common/types'; import type { ILicense } from '@kbn/licensing-plugin/common/types';
import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock'; import { licenseMock } from '@kbn/licensing-plugin/common/licensing.mock';
import { cloudMock } from '@kbn/cloud-plugin/server/mocks'; import { cloudMock } from '@kbn/cloud-plugin/server/mocks';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { LicenseService } from '../../../common/license'; import { LicenseService } from '../../../common/license';
import { createDefaultPolicy } from './create_default_policy'; import { createDefaultPolicy } from './create_default_policy';
import { ProtectionModes } from '../../../common/endpoint/types'; import { ProtectionModes } from '../../../common/endpoint/types';
@ -20,8 +20,8 @@ import type {
PolicyCreateCloudConfig, PolicyCreateCloudConfig,
PolicyCreateEndpointConfig, PolicyCreateEndpointConfig,
} from '../types'; } from '../types';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
import { createAppFeaturesServiceMock } from '../../lib/app_features_service/mocks'; import { createProductFeaturesServiceMock } from '../../lib/product_features_service/mocks';
describe('Create Default Policy tests ', () => { describe('Create Default Policy tests ', () => {
const cloud = cloudMock.createSetup(); const cloud = cloudMock.createSetup();
@ -31,7 +31,7 @@ describe('Create Default Policy tests ', () => {
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold', uid: '' } }); const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold', uid: '' } });
let licenseEmitter: Subject<ILicense>; let licenseEmitter: Subject<ILicense>;
let licenseService: LicenseService; let licenseService: LicenseService;
let appFeaturesService: AppFeaturesService; let productFeaturesService: ProductFeaturesService;
const createDefaultPolicyCallback = async ( const createDefaultPolicyCallback = async (
config: AnyPolicyCreateConfig | undefined config: AnyPolicyCreateConfig | undefined
@ -39,7 +39,7 @@ describe('Create Default Policy tests ', () => {
const esClientInfo = await elasticsearchServiceMock.createClusterClient().asInternalUser.info(); const esClientInfo = await elasticsearchServiceMock.createClusterClient().asInternalUser.info();
esClientInfo.cluster_name = ''; esClientInfo.cluster_name = '';
esClientInfo.cluster_uuid = ''; esClientInfo.cluster_uuid = '';
return createDefaultPolicy(licenseService, config, cloud, esClientInfo, appFeaturesService); return createDefaultPolicy(licenseService, config, cloud, esClientInfo, productFeaturesService);
}; };
beforeEach(() => { beforeEach(() => {
@ -47,7 +47,7 @@ describe('Create Default Policy tests ', () => {
licenseService = new LicenseService(); licenseService = new LicenseService();
licenseService.start(licenseEmitter); licenseService.start(licenseEmitter);
licenseEmitter.next(Platinum); // set license level to platinum licenseEmitter.next(Platinum); // set license level to platinum
appFeaturesService = createAppFeaturesServiceMock(); productFeaturesService = createProductFeaturesServiceMock();
}); });
describe('When no config is set', () => { describe('When no config is set', () => {
@ -210,9 +210,9 @@ describe('Create Default Policy tests ', () => {
expect(policy).toMatchObject(defaultPolicy); expect(policy).toMatchObject(defaultPolicy);
}); });
it('should set policy to event collection only if endpointPolicyProtections appFeature is disabled', async () => { it('should set policy to event collection only if endpointPolicyProtections productFeature is disabled', async () => {
appFeaturesService = createAppFeaturesServiceMock( productFeaturesService = createProductFeaturesServiceMock(
ALL_APP_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections') ALL_PRODUCT_FEATURE_KEYS.filter((key) => key !== 'endpoint_policy_protections')
); );
await expect( await expect(

View file

@ -7,7 +7,7 @@
import type { CloudSetup } from '@kbn/cloud-plugin/server'; import type { CloudSetup } from '@kbn/cloud-plugin/server';
import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types'; import type { InfoResponse } from '@elastic/elasticsearch/lib/api/types';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { import {
policyFactory as policyConfigFactory, policyFactory as policyConfigFactory,
policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures,
@ -25,7 +25,7 @@ import {
disableProtections, disableProtections,
ensureOnlyEventCollectionIsAllowed, ensureOnlyEventCollectionIsAllowed,
} from '../../../common/endpoint/models/policy_config_helpers'; } from '../../../common/endpoint/models/policy_config_helpers';
import type { AppFeaturesService } from '../../lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from '../../lib/product_features_service/product_features_service';
/** /**
* Create the default endpoint policy based on the current license and configuration type * Create the default endpoint policy based on the current license and configuration type
@ -35,7 +35,7 @@ export const createDefaultPolicy = (
config: AnyPolicyCreateConfig | undefined, config: AnyPolicyCreateConfig | undefined,
cloud: CloudSetup, cloud: CloudSetup,
esClientInfo: InfoResponse, esClientInfo: InfoResponse,
appFeatures: AppFeaturesService productFeatures: ProductFeaturesService
): PolicyConfig => { ): PolicyConfig => {
// Pass license and cloud information to use in Policy creation // Pass license and cloud information to use in Policy creation
const factoryPolicy = policyConfigFactory( const factoryPolicy = policyConfigFactory(
@ -57,7 +57,7 @@ export const createDefaultPolicy = (
} }
// If no Policy Protection allowed (ex. serverless) // If no Policy Protection allowed (ex. serverless)
if (!appFeatures.isEnabled(AppFeatureSecurityKey.endpointPolicyProtections)) { if (!productFeatures.isEnabled(ProductFeatureSecurityKey.endpointPolicyProtections)) {
defaultPolicyPerType = ensureOnlyEventCollectionIsAllowed(defaultPolicyPerType); defaultPolicyPerType = ensureOnlyEventCollectionIsAllowed(defaultPolicyPerType);
} }

View file

@ -1,31 +0,0 @@
/*
* 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 type { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/src/app_features_keys';
import type { AppFeaturesService } from '../../lib/app_features_service';
export const validatePolicyAgainstAppFeatures = (
inputs: NewPackagePolicyInput[],
appFeaturesService: AppFeaturesService
): void => {
const input = inputs.find((i) => i.type === 'endpoint');
if (input?.config?.policy?.value?.global_manifest_version) {
const globalManifestVersion = input.config.policy.value.global_manifest_version;
if (
globalManifestVersion !== 'latest' &&
!appFeaturesService.isEnabled(AppFeatureSecurityKey.endpointProtectionUpdates)
) {
const appFeatureError: Error & { statusCode?: number; apiPassThrough?: boolean } = new Error(
'To modify protection updates, you must add at least Endpoint Complete to your project.'
);
appFeatureError.statusCode = 403;
appFeatureError.apiPassThrough = true;
throw appFeatureError;
}
}
};

View file

@ -0,0 +1,32 @@
/*
* 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 type { NewPackagePolicyInput } from '@kbn/fleet-plugin/common';
import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import type { ProductFeaturesService } from '../../lib/product_features_service';
export const validatePolicyAgainstProductFeatures = (
inputs: NewPackagePolicyInput[],
productFeaturesService: ProductFeaturesService
): void => {
const input = inputs.find((i) => i.type === 'endpoint');
if (input?.config?.policy?.value?.global_manifest_version) {
const globalManifestVersion = input.config.policy.value.global_manifest_version;
if (
globalManifestVersion !== 'latest' &&
!productFeaturesService.isEnabled(ProductFeatureSecurityKey.endpointProtectionUpdates)
) {
const productFeatureError: Error & { statusCode?: number; apiPassThrough?: boolean } =
new Error(
'To modify protection updates, you must add at least Endpoint Complete to your project.'
);
productFeatureError.statusCode = 403;
productFeatureError.apiPassThrough = true;
throw productFeatureError;
}
}
};

View file

@ -1,245 +0,0 @@
/*
* 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 { AppFeaturesService } from './app_features_service';
import { AppFeatures } from './app_features';
import type { AppFeaturesConfig, BaseKibanaFeatureConfig } from '@kbn/security-solution-features';
import { loggerMock } from '@kbn/logging-mocks';
import type { ExperimentalFeatures } from '../../../common';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { AppFeaturesConfigurator } from './types';
import type {
AssistantSubFeatureId,
CasesSubFeatureId,
SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys';
import { AppFeatureKey } from '@kbn/security-solution-features/keys';
import { httpServiceMock } from '@kbn/core-http-server-mocks';
import type {
KibanaRequest,
LifecycleResponseFactory,
OnPostAuthHandler,
} from '@kbn/core-http-server';
jest.mock('./app_features');
const MockedAppFeatures = AppFeatures as unknown as jest.MockedClass<typeof AppFeatures>;
const appFeature = {
subFeaturesMap: new Map(),
baseKibanaFeature: {} as BaseKibanaFeatureConfig,
baseKibanaSubFeatureIds: [],
};
const mockGetFeature = jest.fn().mockReturnValue(appFeature);
jest.mock('@kbn/security-solution-features/app_features', () => ({
getAssistantFeature: () => mockGetFeature(),
getCasesFeature: () => mockGetFeature(),
getSecurityFeature: () => mockGetFeature(),
}));
describe('AppFeaturesService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should create AppFeatureService instance', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
new AppFeaturesService(loggerMock.create(), experimentalFeatures);
expect(mockGetFeature).toHaveBeenCalledTimes(3);
expect(MockedAppFeatures).toHaveBeenCalledTimes(3);
});
it('should init all AppFeatures when initialized', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
const featuresSetup = featuresPluginMock.createSetup();
appFeaturesService.init(featuresSetup);
expect(MockedAppFeatures.mock.instances[0].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedAppFeatures.mock.instances[1].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedAppFeatures.mock.instances[2].init).toHaveBeenCalledWith(featuresSetup);
});
it('should configure AppFeatures', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
const featuresSetup = featuresPluginMock.createSetup();
appFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map() as AppFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map() as AppFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map() as AppFeaturesConfig<AssistantSubFeatureId>;
const configurator: AppFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
appFeaturesService.setAppFeaturesConfigurator(configurator);
expect(configurator.security).toHaveBeenCalled();
expect(configurator.cases).toHaveBeenCalled();
expect(configurator.securityAssistant).toHaveBeenCalled();
expect(MockedAppFeatures.mock.instances[0].setConfig).toHaveBeenCalledWith(mockSecurityConfig);
expect(MockedAppFeatures.mock.instances[1].setConfig).toHaveBeenCalledWith(mockCasesConfig);
expect(MockedAppFeatures.mock.instances[2].setConfig).toHaveBeenCalledWith(mockAssistantConfig);
});
it('should return isEnabled for enabled features', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
const featuresSetup = featuresPluginMock.createSetup();
appFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map([
[AppFeatureKey.advancedInsights, {}],
[AppFeatureKey.endpointExceptions, {}],
]) as AppFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map([
[AppFeatureKey.casesConnectors, {}],
]) as AppFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map([
[AppFeatureKey.assistant, {}],
]) as AppFeaturesConfig<AssistantSubFeatureId>;
const configurator: AppFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
appFeaturesService.setAppFeaturesConfigurator(configurator);
expect(appFeaturesService.isEnabled(AppFeatureKey.advancedInsights)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.endpointExceptions)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.casesConnectors)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.assistant)).toEqual(true);
expect(appFeaturesService.isEnabled(AppFeatureKey.externalRuleActions)).toEqual(false);
});
it('should call isApiPrivilegeEnabled for api actions', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.isApiPrivilegeEnabled('writeEndpointExceptions');
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedAppFeatures.mock.instances[1].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedAppFeatures.mock.instances[2].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
});
describe('registerApiAccessControl', () => {
const mockHttpSetup = httpServiceMock.createSetupContract();
let lastRegisteredFn: OnPostAuthHandler;
mockHttpSetup.registerOnPostAuth.mockImplementation((fn) => {
lastRegisteredFn = fn;
});
const getReq = (tags: string[] = []) =>
({
route: { options: { tags } },
url: { pathname: '', search: '' },
} as unknown as KibanaRequest);
const res = { notFound: jest.fn() } as unknown as LifecycleResponseFactory;
const toolkit = { next: jest.fn() };
beforeEach(() => {
jest.clearAllMocks();
});
it('should register api authorization http route interceptor', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
expect(mockHttpSetup.registerOnPostAuth).toHaveBeenCalledTimes(1);
});
it('should authorize when no tag matches', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
lastRegisteredFn(getReq(['access:something', 'access:securitySolution']), res, toolkit);
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).not.toHaveBeenCalled();
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when tag matches and return not found when not action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedAppFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
false
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when tag matches and continue when action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedAppFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
true
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedAppFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when appFeature tag when it matches and return not found when not enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
appFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(false);
lastRegisteredFn(getReq(['securitySolutionAppFeature:foo']), res, toolkit);
expect(appFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when appFeature tag when it matches and continue when enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const appFeaturesService = new AppFeaturesService(loggerMock.create(), experimentalFeatures);
appFeaturesService.registerApiAccessControl(mockHttpSetup);
appFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(true);
lastRegisteredFn(getReq(['securitySolutionAppFeature:foo']), res, toolkit);
expect(appFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -4,4 +4,4 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
export { AppFeaturesService } from './app_features_service'; export { ProductFeaturesService } from './product_features_service';

View file

@ -10,12 +10,12 @@ import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-p
import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks'; import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { AppFeatureKeys } from '@kbn/security-solution-features'; import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import { allowedExperimentalValues, type ExperimentalFeatures } from '../../../common'; import { allowedExperimentalValues, type ExperimentalFeatures } from '../../../common';
import { AppFeaturesService } from './app_features_service'; import { ProductFeaturesService } from './product_features_service';
jest.mock('@kbn/security-solution-features/app_features', () => ({ jest.mock('@kbn/security-solution-features/product_features', () => ({
getSecurityFeature: jest.fn(() => ({ getSecurityFeature: jest.fn(() => ({
baseKibanaFeature: {}, baseKibanaFeature: {},
baseKibanaSubFeatureIds: [], baseKibanaSubFeatureIds: [],
@ -33,19 +33,19 @@ jest.mock('@kbn/security-solution-features/app_features', () => ({
})), })),
})); }));
export const createAppFeaturesServiceMock = ( export const createProductFeaturesServiceMock = (
/** What features keys should be enabled. Default is all */ /** What features keys should be enabled. Default is all */
enabledFeatureKeys: AppFeatureKeys = [...ALL_APP_FEATURE_KEYS], enabledFeatureKeys: ProductFeatureKeys = [...ALL_PRODUCT_FEATURE_KEYS],
experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues }, experimentalFeatures: ExperimentalFeatures = { ...allowedExperimentalValues },
featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(), featuresPluginSetupContract: FeaturesPluginSetup = featuresPluginMock.createSetup(),
logger: Logger = loggingSystemMock.create().get('appFeatureMock') logger: Logger = loggingSystemMock.create().get('productFeatureMock')
) => { ) => {
const appFeaturesService = new AppFeaturesService(logger, experimentalFeatures); const productFeaturesService = new ProductFeaturesService(logger, experimentalFeatures);
appFeaturesService.init(featuresPluginSetupContract); productFeaturesService.init(featuresPluginSetupContract);
if (enabledFeatureKeys) { if (enabledFeatureKeys) {
appFeaturesService.setAppFeaturesConfigurator({ productFeaturesService.setProductFeaturesConfigurator({
security: jest.fn().mockReturnValue( security: jest.fn().mockReturnValue(
new Map( new Map(
enabledFeatureKeys.map((key) => [ enabledFeatureKeys.map((key) => [
@ -106,5 +106,5 @@ export const createAppFeaturesServiceMock = (
}); });
} }
return appFeaturesService; return productFeaturesService;
}; };

View file

@ -7,10 +7,10 @@
import type { PluginSetupContract } from '@kbn/features-plugin/server'; import type { PluginSetupContract } from '@kbn/features-plugin/server';
import { loggingSystemMock } from '@kbn/core-logging-server-mocks'; import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
import { AppFeatures } from './app_features'; import { ProductFeatures } from './product_features';
import type { import type {
AppFeatureKeyType, ProductFeatureKeyType,
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
BaseKibanaFeatureConfig, BaseKibanaFeatureConfig,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import type { SubFeatureConfig } from '@kbn/features-plugin/common'; import type { SubFeatureConfig } from '@kbn/features-plugin/common';
@ -102,11 +102,11 @@ const subFeaturesMap = new Map([
]); ]);
// app features configs // app features configs
const testSubFeaturePrivilegeConfig: AppFeatureKibanaConfig = { const testSubFeaturePrivilegeConfig: ProductFeatureKibanaConfig = {
subFeatureIds: [SUB_FEATURE.name], subFeatureIds: [SUB_FEATURE.name],
}; };
const testFeaturePrivilegeConfig: AppFeatureKibanaConfig = { const testFeaturePrivilegeConfig: ProductFeatureKibanaConfig = {
privileges: { privileges: {
all: { all: {
ui: ['test-action'], ui: ['test-action'],
@ -144,7 +144,7 @@ const expectedBaseWithTestConfigPrivileges = {
}, },
}; };
describe('AppFeatures', () => { describe('ProductFeatures', () => {
describe('setConfig', () => { describe('setConfig', () => {
it('should register base kibana features', () => { it('should register base kibana features', () => {
const featuresSetup = { const featuresSetup = {
@ -152,14 +152,14 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(), getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[] []
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig(new Map()); productFeatures.setConfig(new Map());
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({ expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
...baseKibanaFeature, ...baseKibanaFeature,
@ -173,15 +173,15 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(), getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[] []
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig( productFeatures.setConfig(
new Map([['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig]]) new Map([['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig]])
); );
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({ expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
@ -197,17 +197,17 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(), getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[] []
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig( productFeatures.setConfig(
new Map([ new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig], ['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig], ['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
]) ])
); );
@ -224,17 +224,17 @@ describe('AppFeatures', () => {
getKibanaFeatures: jest.fn(), getKibanaFeatures: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[SUB_FEATURE_2.name] [SUB_FEATURE_2.name]
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig( productFeatures.setConfig(
new Map([ new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig], ['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig], ['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
]) ])
); );
@ -252,25 +252,25 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(), registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[] []
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig(new Map()); productFeatures.setConfig(new Map());
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true); expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true); expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(false); expect(productFeatures.isActionRegistered('api:test-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(false); expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(false);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false); expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false); expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false); expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false); expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
}); });
it('should register config privilege actions', () => { it('should register config privilege actions', () => {
@ -278,27 +278,27 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(), registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[] []
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig( productFeatures.setConfig(
new Map([['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig]]) new Map([['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig]])
); );
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true); expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true); expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(true); expect(productFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(true); expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false); expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false); expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(false);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false); expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false); expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
}); });
it('should register config sub-feature privilege actions', () => { it('should register config sub-feature privilege actions', () => {
@ -306,30 +306,30 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(), registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[] []
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig( productFeatures.setConfig(
new Map([ new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig], ['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig], ['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
]) ])
); );
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true); expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true); expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(true); expect(productFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(true); expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true); expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true); expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false); expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(false);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false); expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(false);
}); });
it('should register default and config sub-feature privilege actions', () => { it('should register default and config sub-feature privilege actions', () => {
@ -337,30 +337,30 @@ describe('AppFeatures', () => {
registerKibanaFeature: jest.fn(), registerKibanaFeature: jest.fn(),
} as unknown as PluginSetupContract; } as unknown as PluginSetupContract;
const appFeatures = new AppFeatures( const productFeatures = new ProductFeatures(
loggingSystemMock.create().get('mock'), loggingSystemMock.create().get('mock'),
subFeaturesMap, subFeaturesMap,
baseKibanaFeature, baseKibanaFeature,
[SUB_FEATURE_2.name] [SUB_FEATURE_2.name]
); );
appFeatures.init(featuresSetup); productFeatures.init(featuresSetup);
appFeatures.setConfig( productFeatures.setConfig(
new Map([ new Map([
['test-feature' as AppFeatureKeyType, testFeaturePrivilegeConfig], ['test-feature' as ProductFeatureKeyType, testFeaturePrivilegeConfig],
['test-sub-feature' as AppFeatureKeyType, testSubFeaturePrivilegeConfig], ['test-sub-feature' as ProductFeatureKeyType, testSubFeaturePrivilegeConfig],
]) ])
); );
expect(appFeatures.isActionRegistered('api:api-read')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-read')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:read')).toEqual(true); expect(productFeatures.isActionRegistered('ui:read')).toEqual(true);
expect(appFeatures.isActionRegistered('api:api-write')).toEqual(true); expect(productFeatures.isActionRegistered('api:api-write')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:write')).toEqual(true); expect(productFeatures.isActionRegistered('ui:write')).toEqual(true);
expect(appFeatures.isActionRegistered('api:test-action')).toEqual(true); expect(productFeatures.isActionRegistered('api:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:test-action')).toEqual(true); expect(productFeatures.isActionRegistered('ui:test-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true); expect(productFeatures.isActionRegistered('api:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true); expect(productFeatures.isActionRegistered('ui:subFeature1-action')).toEqual(true);
expect(appFeatures.isActionRegistered('api:subFeature2-action')).toEqual(true); expect(productFeatures.isActionRegistered('api:subFeature2-action')).toEqual(true);
expect(appFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(true); expect(productFeatures.isActionRegistered('ui:subFeature2-action')).toEqual(true);
}); });
}); });
}); });

View file

@ -12,14 +12,14 @@ import type {
PluginSetupContract as FeaturesPluginSetup, PluginSetupContract as FeaturesPluginSetup,
} from '@kbn/features-plugin/server'; } from '@kbn/features-plugin/server';
import type { import type {
AppFeaturesConfig, ProductFeaturesConfig,
AppSubFeaturesMap, AppSubFeaturesMap,
BaseKibanaFeatureConfig, BaseKibanaFeatureConfig,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import { AppFeaturesConfigMerger } from './app_features_config_merger'; import { ProductFeaturesConfigMerger } from './product_features_config_merger';
export class AppFeatures<T extends string = string, S extends string = string> { export class ProductFeatures<T extends string = string, S extends string = string> {
private featureConfigMerger: AppFeaturesConfigMerger; private featureConfigMerger: ProductFeaturesConfigMerger;
private featuresSetup?: FeaturesPluginSetup; private featuresSetup?: FeaturesPluginSetup;
private readonly registeredActions: Set<string>; private readonly registeredActions: Set<string>;
@ -29,7 +29,7 @@ export class AppFeatures<T extends string = string, S extends string = string> {
private readonly baseKibanaFeature: BaseKibanaFeatureConfig, private readonly baseKibanaFeature: BaseKibanaFeatureConfig,
private readonly baseKibanaSubFeatureIds: T[] private readonly baseKibanaSubFeatureIds: T[]
) { ) {
this.featureConfigMerger = new AppFeaturesConfigMerger(this.logger, subFeaturesMap); this.featureConfigMerger = new ProductFeaturesConfigMerger(this.logger, subFeaturesMap);
this.registeredActions = new Set(); this.registeredActions = new Set();
} }
@ -37,22 +37,22 @@ export class AppFeatures<T extends string = string, S extends string = string> {
this.featuresSetup = featuresSetup; this.featuresSetup = featuresSetup;
} }
public setConfig(appFeatureConfig: AppFeaturesConfig<S>) { public setConfig(productFeatureConfig: ProductFeaturesConfig<S>) {
if (this.featuresSetup == null) { if (this.featuresSetup == null) {
throw new Error( throw new Error(
'Cannot sync kibana features as featuresSetup is not present. Did you call init?' 'Cannot sync kibana features as featuresSetup is not present. Did you call init?'
); );
} }
const completeAppFeatureConfig = this.featureConfigMerger.mergeAppFeatureConfigs( const completeProductFeatureConfig = this.featureConfigMerger.mergeProductFeatureConfigs(
this.baseKibanaFeature, this.baseKibanaFeature,
this.baseKibanaSubFeatureIds, this.baseKibanaSubFeatureIds,
Array.from(appFeatureConfig.values()) Array.from(productFeatureConfig.values())
); );
this.logger.debug(JSON.stringify(completeAppFeatureConfig)); this.logger.debug(JSON.stringify(completeProductFeatureConfig));
this.featuresSetup.registerKibanaFeature(completeAppFeatureConfig); this.featuresSetup.registerKibanaFeature(completeProductFeatureConfig);
this.addRegisteredActions(completeAppFeatureConfig); this.addRegisteredActions(completeProductFeatureConfig);
} }
private addRegisteredActions(config: KibanaFeatureConfig) { private addRegisteredActions(config: KibanaFeatureConfig) {

View file

@ -6,9 +6,9 @@
*/ */
import { loggingSystemMock } from '@kbn/core/server/mocks'; import { loggingSystemMock } from '@kbn/core/server/mocks';
import { AppFeaturesConfigMerger } from './app_features_config_merger'; import { ProductFeaturesConfigMerger } from './product_features_config_merger';
import type { Logger } from '@kbn/core/server'; import type { Logger } from '@kbn/core/server';
import type { AppFeatureKibanaConfig } from '@kbn/security-solution-features'; import type { ProductFeatureKibanaConfig } from '@kbn/security-solution-features';
import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common'; import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common';
const category = { const category = {
@ -138,8 +138,8 @@ export const subFeaturesMap = Object.freeze(
const mockLogger = loggingSystemMock.create().get() as jest.Mocked<Logger>; const mockLogger = loggingSystemMock.create().get() as jest.Mocked<Logger>;
describe('AppFeaturesConfigMerger', () => { describe('ProductFeaturesConfigMerger', () => {
const merger = new AppFeaturesConfigMerger(mockLogger, subFeaturesMap); const merger = new ProductFeaturesConfigMerger(mockLogger, subFeaturesMap);
beforeEach(() => { beforeEach(() => {
jest.clearAllMocks(); jest.clearAllMocks();
@ -147,7 +147,7 @@ describe('AppFeaturesConfigMerger', () => {
describe('main privileges', () => { describe('main privileges', () => {
it('should merge enabled main privileges into base config', () => { it('should merge enabled main privileges into base config', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [ const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{ {
privileges: { privileges: {
all: { all: {
@ -179,10 +179,10 @@ describe('AppFeaturesConfigMerger', () => {
}, },
]; ];
const merged = merger.mergeAppFeatureConfigs( const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature, baseKibanaFeature,
[], [],
enabledAppFeaturesConfigs enabledProductFeaturesConfigs
); );
expect(merged).toEqual({ expect(merged).toEqual({
@ -227,21 +227,25 @@ describe('AppFeaturesConfigMerger', () => {
it('adds base subFeatures in the correct order', () => { it('adds base subFeatures in the correct order', () => {
const baseKibanaSubFeatureIds = ['subFeature2', 'subFeature3', 'subFeature1']; const baseKibanaSubFeatureIds = ['subFeature2', 'subFeature3', 'subFeature1'];
const merged = merger.mergeAppFeatureConfigs(baseKibanaFeature, baseKibanaSubFeatureIds, []); const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature,
baseKibanaSubFeatureIds,
[]
);
expect(merged.subFeatures).toEqual([subFeature1, subFeature2, subFeature3]); expect(merged.subFeatures).toEqual([subFeature1, subFeature2, subFeature3]);
}); });
it('should merge enabled subFeatures into base config in the correct order', () => { it('should merge enabled subFeatures into base config in the correct order', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [ const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{ {
subFeatureIds: ['subFeature3', 'subFeature1'], subFeatureIds: ['subFeature3', 'subFeature1'],
}, },
]; ];
const merged = merger.mergeAppFeatureConfigs( const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature, baseKibanaFeature,
['subFeature2'], ['subFeature2'],
enabledAppFeaturesConfigs enabledProductFeaturesConfigs
); );
expect(merged).toEqual({ expect(merged).toEqual({
@ -253,7 +257,7 @@ describe('AppFeaturesConfigMerger', () => {
describe('subFeaturePrivileges', () => { describe('subFeaturePrivileges', () => {
it('should merge enabled subFeatures with extra subFeaturePrivileges into base config in the correct order', () => { it('should merge enabled subFeatures with extra subFeaturePrivileges into base config in the correct order', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [ const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{ {
subFeaturesPrivileges: [ subFeaturesPrivileges: [
{ {
@ -273,10 +277,10 @@ describe('AppFeaturesConfigMerger', () => {
}, },
]; ];
const merged = merger.mergeAppFeatureConfigs( const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature, baseKibanaFeature,
['subFeature2'], ['subFeature2'],
enabledAppFeaturesConfigs enabledProductFeaturesConfigs
); );
expect(merged).toEqual({ expect(merged).toEqual({
...baseKibanaFeature, ...baseKibanaFeature,
@ -323,7 +327,7 @@ describe('AppFeaturesConfigMerger', () => {
it('should warn if there are subFeaturesPrivileges for a subFeature id that is not found', () => { it('should warn if there are subFeaturesPrivileges for a subFeature id that is not found', () => {
const subFeaturesPrivilegesId = 'sub-feature-1_all'; const subFeaturesPrivilegesId = 'sub-feature-1_all';
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [ const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{ {
subFeaturesPrivileges: [ subFeaturesPrivileges: [
{ {
@ -335,10 +339,10 @@ describe('AppFeaturesConfigMerger', () => {
}, },
]; ];
const merged = merger.mergeAppFeatureConfigs( const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature, baseKibanaFeature,
['subFeature2', 'subFeature3'], ['subFeature2', 'subFeature3'],
enabledAppFeaturesConfigs enabledProductFeaturesConfigs
); );
expect(mockLogger.warn).toHaveBeenCalledWith( expect(mockLogger.warn).toHaveBeenCalledWith(
@ -349,7 +353,7 @@ describe('AppFeaturesConfigMerger', () => {
}); });
it('should merge everything at the same time', () => { it('should merge everything at the same time', () => {
const enabledAppFeaturesConfigs: AppFeatureKibanaConfig[] = [ const enabledProductFeaturesConfigs: ProductFeatureKibanaConfig[] = [
{ {
privileges: { privileges: {
all: { all: {
@ -395,10 +399,10 @@ describe('AppFeaturesConfigMerger', () => {
]; ];
const baseKibanaSubFeatureIds = ['subFeature2']; const baseKibanaSubFeatureIds = ['subFeature2'];
const merged = merger.mergeAppFeatureConfigs( const merged = merger.mergeProductFeatureConfigs(
baseKibanaFeature, baseKibanaFeature,
baseKibanaSubFeatureIds, baseKibanaSubFeatureIds,
enabledAppFeaturesConfigs enabledProductFeaturesConfigs
); );
expect(merged).toEqual({ expect(merged).toEqual({

View file

@ -9,28 +9,28 @@ import { cloneDeep, isArray, mergeWith, uniq } from 'lodash';
import type { Logger } from '@kbn/core/server'; import type { Logger } from '@kbn/core/server';
import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common'; import type { KibanaFeatureConfig, SubFeatureConfig } from '@kbn/features-plugin/common';
import type { import type {
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
BaseKibanaFeatureConfig, BaseKibanaFeatureConfig,
SubFeaturesPrivileges, SubFeaturesPrivileges,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
export class AppFeaturesConfigMerger<T extends string = string> { export class ProductFeaturesConfigMerger<T extends string = string> {
constructor( constructor(
private readonly logger: Logger, private readonly logger: Logger,
private readonly subFeaturesMap: Map<T, SubFeatureConfig> private readonly subFeaturesMap: Map<T, SubFeatureConfig>
) {} ) {}
/** /**
* Merges `appFeaturesConfigs` into `kibanaFeatureConfig`. * Merges `productFeaturesConfigs` into `kibanaFeatureConfig`.
* @param kibanaFeatureConfig the KibanaFeatureConfig to merge into * @param kibanaFeatureConfig the KibanaFeatureConfig to merge into
* @param kibanaSubFeatureIds * @param kibanaSubFeatureIds
* @param appFeaturesConfigs the AppFeatureKibanaConfig to merge * @param productFeaturesConfigs the ProductFeatureKibanaConfig to merge
* @returns mergedKibanaFeatureConfig the merged KibanaFeatureConfig * @returns mergedKibanaFeatureConfig the merged KibanaFeatureConfig
* */ * */
public mergeAppFeatureConfigs( public mergeProductFeatureConfigs(
kibanaFeatureConfig: BaseKibanaFeatureConfig, kibanaFeatureConfig: BaseKibanaFeatureConfig,
kibanaSubFeatureIds: T[], kibanaSubFeatureIds: T[],
appFeaturesConfigs: AppFeatureKibanaConfig[] productFeaturesConfigs: ProductFeatureKibanaConfig[]
): KibanaFeatureConfig { ): KibanaFeatureConfig {
const mergedKibanaFeatureConfig = cloneDeep(kibanaFeatureConfig) as KibanaFeatureConfig; const mergedKibanaFeatureConfig = cloneDeep(kibanaFeatureConfig) as KibanaFeatureConfig;
const subFeaturesPrivilegesToMerge: SubFeaturesPrivileges[] = []; const subFeaturesPrivilegesToMerge: SubFeaturesPrivileges[] = [];
@ -38,9 +38,9 @@ export class AppFeaturesConfigMerger<T extends string = string> {
kibanaSubFeatureIds.map((id) => [id, true]) kibanaSubFeatureIds.map((id) => [id, true])
); );
appFeaturesConfigs.forEach((appFeatureConfig) => { productFeaturesConfigs.forEach((productFeatureConfig) => {
const { subFeaturesPrivileges, subFeatureIds, ...appFeatureConfigToMerge } = const { subFeaturesPrivileges, subFeatureIds, ...productFeatureConfigToMerge } =
cloneDeep(appFeatureConfig); cloneDeep(productFeatureConfig);
subFeatureIds?.forEach((subFeatureId) => { subFeatureIds?.forEach((subFeatureId) => {
enabledSubFeaturesIndexed[subFeatureId] = true; enabledSubFeaturesIndexed[subFeatureId] = true;
@ -49,7 +49,7 @@ export class AppFeaturesConfigMerger<T extends string = string> {
if (subFeaturesPrivileges) { if (subFeaturesPrivileges) {
subFeaturesPrivilegesToMerge.push(...subFeaturesPrivileges); subFeaturesPrivilegesToMerge.push(...subFeaturesPrivileges);
} }
mergeWith(mergedKibanaFeatureConfig, appFeatureConfigToMerge, featureConfigMerger); mergeWith(mergedKibanaFeatureConfig, productFeatureConfigToMerge, featureConfigMerger);
}); });
// generate sub features configs from enabled sub feature ids, preserving map order // generate sub features configs from enabled sub feature ids, preserving map order

View file

@ -0,0 +1,284 @@
/*
* 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 { ProductFeaturesService } from './product_features_service';
import { ProductFeatures } from './product_features';
import type {
ProductFeaturesConfig,
BaseKibanaFeatureConfig,
} from '@kbn/security-solution-features';
import { loggerMock } from '@kbn/logging-mocks';
import type { ExperimentalFeatures } from '../../../common';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { ProductFeaturesConfigurator } from './types';
import type {
AssistantSubFeatureId,
CasesSubFeatureId,
SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys';
import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import { httpServiceMock } from '@kbn/core-http-server-mocks';
import type {
KibanaRequest,
LifecycleResponseFactory,
OnPostAuthHandler,
} from '@kbn/core-http-server';
jest.mock('./product_features');
const MockedProductFeatures = ProductFeatures as unknown as jest.MockedClass<
typeof ProductFeatures
>;
const productFeature = {
subFeaturesMap: new Map(),
baseKibanaFeature: {} as BaseKibanaFeatureConfig,
baseKibanaSubFeatureIds: [],
};
const mockGetFeature = jest.fn().mockReturnValue(productFeature);
jest.mock('@kbn/security-solution-features/product_features', () => ({
getAssistantFeature: () => mockGetFeature(),
getCasesFeature: () => mockGetFeature(),
getSecurityFeature: () => mockGetFeature(),
}));
describe('ProductFeaturesService', () => {
beforeEach(() => {
jest.clearAllMocks();
});
it('should create ProductFeatureService instance', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
new ProductFeaturesService(loggerMock.create(), experimentalFeatures);
expect(mockGetFeature).toHaveBeenCalledTimes(3);
expect(MockedProductFeatures).toHaveBeenCalledTimes(3);
});
it('should init all ProductFeatures when initialized', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
const featuresSetup = featuresPluginMock.createSetup();
productFeaturesService.init(featuresSetup);
expect(MockedProductFeatures.mock.instances[0].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedProductFeatures.mock.instances[1].init).toHaveBeenCalledWith(featuresSetup);
expect(MockedProductFeatures.mock.instances[2].init).toHaveBeenCalledWith(featuresSetup);
});
it('should configure ProductFeatures', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
const featuresSetup = featuresPluginMock.createSetup();
productFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map() as ProductFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map() as ProductFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map() as ProductFeaturesConfig<AssistantSubFeatureId>;
const configurator: ProductFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
productFeaturesService.setProductFeaturesConfigurator(configurator);
expect(configurator.security).toHaveBeenCalled();
expect(configurator.cases).toHaveBeenCalled();
expect(configurator.securityAssistant).toHaveBeenCalled();
expect(MockedProductFeatures.mock.instances[0].setConfig).toHaveBeenCalledWith(
mockSecurityConfig
);
expect(MockedProductFeatures.mock.instances[1].setConfig).toHaveBeenCalledWith(mockCasesConfig);
expect(MockedProductFeatures.mock.instances[2].setConfig).toHaveBeenCalledWith(
mockAssistantConfig
);
});
it('should return isEnabled for enabled features', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
const featuresSetup = featuresPluginMock.createSetup();
productFeaturesService.init(featuresSetup);
const mockSecurityConfig = new Map([
[ProductFeatureKey.advancedInsights, {}],
[ProductFeatureKey.endpointExceptions, {}],
]) as ProductFeaturesConfig<SecuritySubFeatureId>;
const mockCasesConfig = new Map([
[ProductFeatureKey.casesConnectors, {}],
]) as ProductFeaturesConfig<CasesSubFeatureId>;
const mockAssistantConfig = new Map([
[ProductFeatureKey.assistant, {}],
]) as ProductFeaturesConfig<AssistantSubFeatureId>;
const configurator: ProductFeaturesConfigurator = {
security: jest.fn(() => mockSecurityConfig),
cases: jest.fn(() => mockCasesConfig),
securityAssistant: jest.fn(() => mockAssistantConfig),
};
productFeaturesService.setProductFeaturesConfigurator(configurator);
expect(productFeaturesService.isEnabled(ProductFeatureKey.advancedInsights)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.endpointExceptions)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.casesConnectors)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.assistant)).toEqual(true);
expect(productFeaturesService.isEnabled(ProductFeatureKey.externalRuleActions)).toEqual(false);
});
it('should call isApiPrivilegeEnabled for api actions', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.isApiPrivilegeEnabled('writeEndpointExceptions');
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedProductFeatures.mock.instances[1].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
expect(MockedProductFeatures.mock.instances[2].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-writeEndpointExceptions'
);
});
describe('registerApiAccessControl', () => {
const mockHttpSetup = httpServiceMock.createSetupContract();
let lastRegisteredFn: OnPostAuthHandler;
mockHttpSetup.registerOnPostAuth.mockImplementation((fn) => {
lastRegisteredFn = fn;
});
const getReq = (tags: string[] = []) =>
({
route: { options: { tags } },
url: { pathname: '', search: '' },
} as unknown as KibanaRequest);
const res = { notFound: jest.fn() } as unknown as LifecycleResponseFactory;
const toolkit = { next: jest.fn() };
beforeEach(() => {
jest.clearAllMocks();
});
it('should register api authorization http route interceptor', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
expect(mockHttpSetup.registerOnPostAuth).toHaveBeenCalledTimes(1);
});
it('should authorize when no tag matches', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
lastRegisteredFn(getReq(['access:something', 'access:securitySolution']), res, toolkit);
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).not.toHaveBeenCalled();
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when tag matches and return not found when not action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedProductFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
false
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when tag matches and continue when action registered', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
(MockedProductFeatures.mock.instances[0].isActionRegistered as jest.Mock).mockReturnValueOnce(
true
);
lastRegisteredFn(getReq(['access:securitySolution-foo']), res, toolkit);
expect(MockedProductFeatures.mock.instances[0].isActionRegistered).toHaveBeenCalledWith(
'api:securitySolution-foo'
);
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
it('should check when productFeature tag when it matches and return not found when not enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
productFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(false);
lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit);
expect(productFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).toHaveBeenCalledTimes(1);
expect(toolkit.next).not.toHaveBeenCalled();
});
it('should check when productFeature tag when it matches and continue when enabled', () => {
const experimentalFeatures = {} as ExperimentalFeatures;
const productFeaturesService = new ProductFeaturesService(
loggerMock.create(),
experimentalFeatures
);
productFeaturesService.registerApiAccessControl(mockHttpSetup);
productFeaturesService.isEnabled = jest.fn().mockReturnValueOnce(true);
lastRegisteredFn(getReq(['securitySolutionProductFeature:foo']), res, toolkit);
expect(productFeaturesService.isEnabled).toHaveBeenCalledWith('foo');
expect(res.notFound).not.toHaveBeenCalled();
expect(toolkit.next).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -14,24 +14,24 @@
import type { HttpServiceSetup, Logger } from '@kbn/core/server'; import type { HttpServiceSetup, Logger } from '@kbn/core/server';
import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects'; import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects';
import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server'; import type { PluginSetupContract as FeaturesPluginSetup } from '@kbn/features-plugin/server';
import type { AppFeatureKeyType } from '@kbn/security-solution-features'; import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { import {
getAssistantFeature, getAssistantFeature,
getCasesFeature, getCasesFeature,
getSecurityFeature, getSecurityFeature,
} from '@kbn/security-solution-features/app_features'; } from '@kbn/security-solution-features/product_features';
import type { ExperimentalFeatures } from '../../../common'; import type { ExperimentalFeatures } from '../../../common';
import { APP_ID } from '../../../common'; import { APP_ID } from '../../../common';
import { AppFeatures } from './app_features'; import { ProductFeatures } from './product_features';
import type { AppFeaturesConfigurator } from './types'; import type { ProductFeaturesConfigurator } from './types';
import { securityDefaultSavedObjects } from './security_saved_objects'; import { securityDefaultSavedObjects } from './security_saved_objects';
import { casesApiTags, casesUiCapabilities } from './cases_privileges'; import { casesApiTags, casesUiCapabilities } from './cases_privileges';
export class AppFeaturesService { export class ProductFeaturesService {
private securityAppFeatures: AppFeatures; private securityProductFeatures: ProductFeatures;
private casesAppFeatures: AppFeatures; private casesProductFeatures: ProductFeatures;
private securityAssistantAppFeatures: AppFeatures; private securityAssistantProductFeatures: ProductFeatures;
private appFeatures?: Set<AppFeatureKeyType>; private productFeatures?: Set<ProductFeatureKeyType>;
constructor( constructor(
private readonly logger: Logger, private readonly logger: Logger,
@ -41,7 +41,7 @@ export class AppFeaturesService {
savedObjects: securityDefaultSavedObjects, savedObjects: securityDefaultSavedObjects,
experimentalFeatures: this.experimentalFeatures, experimentalFeatures: this.experimentalFeatures,
}); });
this.securityAppFeatures = new AppFeatures( this.securityProductFeatures = new ProductFeatures(
this.logger, this.logger,
securityFeature.subFeaturesMap, securityFeature.subFeaturesMap,
securityFeature.baseKibanaFeature, securityFeature.baseKibanaFeature,
@ -53,7 +53,7 @@ export class AppFeaturesService {
apiTags: casesApiTags, apiTags: casesApiTags,
savedObjects: { files: filesSavedObjectTypes }, savedObjects: { files: filesSavedObjectTypes },
}); });
this.casesAppFeatures = new AppFeatures( this.casesProductFeatures = new ProductFeatures(
this.logger, this.logger,
casesFeature.subFeaturesMap, casesFeature.subFeaturesMap,
casesFeature.baseKibanaFeature, casesFeature.baseKibanaFeature,
@ -61,7 +61,7 @@ export class AppFeaturesService {
); );
const assistantFeature = getAssistantFeature(); const assistantFeature = getAssistantFeature();
this.securityAssistantAppFeatures = new AppFeatures( this.securityAssistantProductFeatures = new ProductFeatures(
this.logger, this.logger,
assistantFeature.subFeaturesMap, assistantFeature.subFeaturesMap,
assistantFeature.baseKibanaFeature, assistantFeature.baseKibanaFeature,
@ -70,44 +70,44 @@ export class AppFeaturesService {
} }
public init(featuresSetup: FeaturesPluginSetup) { public init(featuresSetup: FeaturesPluginSetup) {
this.securityAppFeatures.init(featuresSetup); this.securityProductFeatures.init(featuresSetup);
this.casesAppFeatures.init(featuresSetup); this.casesProductFeatures.init(featuresSetup);
this.securityAssistantAppFeatures.init(featuresSetup); this.securityAssistantProductFeatures.init(featuresSetup);
} }
public setAppFeaturesConfigurator(configurator: AppFeaturesConfigurator) { public setProductFeaturesConfigurator(configurator: ProductFeaturesConfigurator) {
const securityAppFeaturesConfig = configurator.security(); const securityProductFeaturesConfig = configurator.security();
this.securityAppFeatures.setConfig(securityAppFeaturesConfig); this.securityProductFeatures.setConfig(securityProductFeaturesConfig);
const casesAppFeaturesConfig = configurator.cases(); const casesProductFeaturesConfig = configurator.cases();
this.casesAppFeatures.setConfig(casesAppFeaturesConfig); this.casesProductFeatures.setConfig(casesProductFeaturesConfig);
const securityAssistantAppFeaturesConfig = configurator.securityAssistant(); const securityAssistantProductFeaturesConfig = configurator.securityAssistant();
this.securityAssistantAppFeatures.setConfig(securityAssistantAppFeaturesConfig); this.securityAssistantProductFeatures.setConfig(securityAssistantProductFeaturesConfig);
this.appFeatures = new Set<AppFeatureKeyType>( this.productFeatures = new Set<ProductFeatureKeyType>(
Object.freeze([ Object.freeze([
...securityAppFeaturesConfig.keys(), ...securityProductFeaturesConfig.keys(),
...casesAppFeaturesConfig.keys(), ...casesProductFeaturesConfig.keys(),
...securityAssistantAppFeaturesConfig.keys(), ...securityAssistantProductFeaturesConfig.keys(),
]) as readonly AppFeatureKeyType[] ]) as readonly ProductFeatureKeyType[]
); );
} }
public isEnabled(appFeatureKey: AppFeatureKeyType): boolean { public isEnabled(productFeatureKey: ProductFeatureKeyType): boolean {
if (!this.appFeatures) { if (!this.productFeatures) {
throw new Error('AppFeatures has not yet been configured'); throw new Error('ProductFeatures has not yet been configured');
} }
return this.appFeatures.has(appFeatureKey); return this.productFeatures.has(productFeatureKey);
} }
public getApiActionName = (apiPrivilege: string) => `api:${APP_ID}-${apiPrivilege}`; public getApiActionName = (apiPrivilege: string) => `api:${APP_ID}-${apiPrivilege}`;
public isActionRegistered(action: string) { public isActionRegistered(action: string) {
return ( return (
this.securityAppFeatures.isActionRegistered(action) || this.securityProductFeatures.isActionRegistered(action) ||
this.casesAppFeatures.isActionRegistered(action) || this.casesProductFeatures.isActionRegistered(action) ||
this.securityAssistantAppFeatures.isActionRegistered(action) this.securityAssistantProductFeatures.isActionRegistered(action)
); );
} }
@ -116,13 +116,13 @@ export class AppFeaturesService {
} }
public registerApiAccessControl(http: HttpServiceSetup) { public registerApiAccessControl(http: HttpServiceSetup) {
// The `securitySolutionAppFeature:` prefix is used for AppFeature based control. // The `securitySolutionProductFeature:` prefix is used for ProductFeature based control.
// Should be used only by routes that do not need RBAC, only direct appFeature control. // Should be used only by routes that do not need RBAC, only direct productFeature control.
const APP_FEATURE_TAG_PREFIX = 'securitySolutionAppFeature:'; const APP_FEATURE_TAG_PREFIX = 'securitySolutionProductFeature:';
// The "access:securitySolution-" prefix is used for API action based control. // The "access:securitySolution-" prefix is used for API action based control.
// Should be used by routes that need RBAC, extending the `access:` role privilege check from the security plugin. // Should be used by routes that need RBAC, extending the `access:` role privilege check from the security plugin.
// An additional check is performed to ensure the privilege has been registered by the appFeature service, // An additional check is performed to ensure the privilege has been registered by the productFeature service,
// preventing full access (`*`) roles, such as superuser, from bypassing appFeature controls. // preventing full access (`*`) roles, such as superuser, from bypassing productFeature controls.
const API_ACTION_TAG_PREFIX = `access:${APP_ID}-`; const API_ACTION_TAG_PREFIX = `access:${APP_ID}-`;
http.registerOnPostAuth((request, response, toolkit) => { http.registerOnPostAuth((request, response, toolkit) => {
@ -130,7 +130,7 @@ export class AppFeaturesService {
let isEnabled = true; let isEnabled = true;
if (tag.startsWith(APP_FEATURE_TAG_PREFIX)) { if (tag.startsWith(APP_FEATURE_TAG_PREFIX)) {
isEnabled = this.isEnabled( isEnabled = this.isEnabled(
tag.substring(APP_FEATURE_TAG_PREFIX.length) as AppFeatureKeyType tag.substring(APP_FEATURE_TAG_PREFIX.length) as ProductFeatureKeyType
); );
} else if (tag.startsWith(API_ACTION_TAG_PREFIX)) { } else if (tag.startsWith(API_ACTION_TAG_PREFIX)) {
isEnabled = this.isApiPrivilegeEnabled(tag.substring(API_ACTION_TAG_PREFIX.length)); isEnabled = this.isApiPrivilegeEnabled(tag.substring(API_ACTION_TAG_PREFIX.length));

View file

@ -5,15 +5,15 @@
* 2.0. * 2.0.
*/ */
import type { AppFeaturesConfig } from '@kbn/security-solution-features'; import type { ProductFeaturesConfig } from '@kbn/security-solution-features';
import type { import type {
SecuritySubFeatureId, SecuritySubFeatureId,
CasesSubFeatureId, CasesSubFeatureId,
AssistantSubFeatureId, AssistantSubFeatureId,
} from '@kbn/security-solution-features/keys'; } from '@kbn/security-solution-features/keys';
export interface AppFeaturesConfigurator { export interface ProductFeaturesConfigurator {
security: () => AppFeaturesConfig<SecuritySubFeatureId>; security: () => ProductFeaturesConfig<SecuritySubFeatureId>;
cases: () => AppFeaturesConfig<CasesSubFeatureId>; cases: () => ProductFeaturesConfig<CasesSubFeatureId>;
securityAssistant: () => AppFeaturesConfig<AssistantSubFeatureId>; securityAssistant: () => ProductFeaturesConfig<AssistantSubFeatureId>;
} }

View file

@ -120,7 +120,7 @@ import {
ENDPOINT_SEARCH_STRATEGY, ENDPOINT_SEARCH_STRATEGY,
} from '../common/endpoint/constants'; } from '../common/endpoint/constants';
import { AppFeaturesService } from './lib/app_features_service/app_features_service'; import { ProductFeaturesService } from './lib/product_features_service/product_features_service';
import { registerRiskScoringTask } from './lib/entity_analytics/risk_score/tasks/risk_scoring_task'; import { registerRiskScoringTask } from './lib/entity_analytics/risk_score/tasks/risk_scoring_task';
import { registerProtectionUpdatesNoteRoutes } from './endpoint/routes/protection_updates_note'; import { registerProtectionUpdatesNoteRoutes } from './endpoint/routes/protection_updates_note';
import { import {
@ -139,7 +139,7 @@ export class Plugin implements ISecuritySolutionPlugin {
private readonly config: ConfigType; private readonly config: ConfigType;
private readonly logger: Logger; private readonly logger: Logger;
private readonly appClientFactory: AppClientFactory; private readonly appClientFactory: AppClientFactory;
private readonly appFeaturesService: AppFeaturesService; private readonly productFeaturesService: ProductFeaturesService;
private readonly ruleMonitoringService: IRuleMonitoringService; private readonly ruleMonitoringService: IRuleMonitoringService;
private readonly endpointAppContextService = new EndpointAppContextService(); private readonly endpointAppContextService = new EndpointAppContextService();
@ -163,7 +163,10 @@ export class Plugin implements ISecuritySolutionPlugin {
this.config = serverConfig; this.config = serverConfig;
this.logger = context.logger.get(); this.logger = context.logger.get();
this.appClientFactory = new AppClientFactory(); this.appClientFactory = new AppClientFactory();
this.appFeaturesService = new AppFeaturesService(this.logger, this.config.experimentalFeatures); this.productFeaturesService = new ProductFeaturesService(
this.logger,
this.config.experimentalFeatures
);
this.ruleMonitoringService = createRuleMonitoringService(this.config, this.logger); this.ruleMonitoringService = createRuleMonitoringService(this.config, this.logger);
this.telemetryEventsSender = new TelemetryEventsSender(this.logger); this.telemetryEventsSender = new TelemetryEventsSender(this.logger);
@ -188,12 +191,12 @@ export class Plugin implements ISecuritySolutionPlugin {
): SecuritySolutionPluginSetup { ): SecuritySolutionPluginSetup {
this.logger.debug('plugin setup'); this.logger.debug('plugin setup');
const { appClientFactory, appFeaturesService, pluginContext, config, logger } = this; const { appClientFactory, productFeaturesService, pluginContext, config, logger } = this;
const experimentalFeatures = config.experimentalFeatures; const experimentalFeatures = config.experimentalFeatures;
initSavedObjects(core.savedObjects); initSavedObjects(core.savedObjects);
initUiSettings(core.uiSettings, experimentalFeatures, config.enableUiSettingsValidations); initUiSettings(core.uiSettings, experimentalFeatures, config.enableUiSettingsValidations);
appFeaturesService.init(plugins.features); productFeaturesService.init(plugins.features);
events.forEach((eventConfig) => core.analytics.registerEventType(eventConfig)); events.forEach((eventConfig) => core.analytics.registerEventType(eventConfig));
@ -220,7 +223,7 @@ export class Plugin implements ISecuritySolutionPlugin {
kibanaBranch: pluginContext.env.packageInfo.branch, kibanaBranch: pluginContext.env.packageInfo.branch,
}); });
appFeaturesService.registerApiAccessControl(core.http); productFeaturesService.registerApiAccessControl(core.http);
const router = core.http.createRouter<SecuritySolutionRequestHandlerContext>(); const router = core.http.createRouter<SecuritySolutionRequestHandlerContext>();
core.http.registerRouteHandlerContext<SecuritySolutionRequestHandlerContext, typeof APP_ID>( core.http.registerRouteHandlerContext<SecuritySolutionRequestHandlerContext, typeof APP_ID>(
APP_ID, APP_ID,
@ -494,8 +497,8 @@ export class Plugin implements ISecuritySolutionPlugin {
featureUsageService.setup(plugins.licensing); featureUsageService.setup(plugins.licensing);
return { return {
setAppFeaturesConfigurator: setProductFeaturesConfigurator:
appFeaturesService.setAppFeaturesConfigurator.bind(appFeaturesService), productFeaturesService.setProductFeaturesConfigurator.bind(productFeaturesService),
experimentalFeatures: { ...config.experimentalFeatures }, experimentalFeatures: { ...config.experimentalFeatures },
}; };
} }
@ -504,7 +507,7 @@ export class Plugin implements ISecuritySolutionPlugin {
core: SecuritySolutionPluginCoreStartDependencies, core: SecuritySolutionPluginCoreStartDependencies,
plugins: SecuritySolutionPluginStartDependencies plugins: SecuritySolutionPluginStartDependencies
): SecuritySolutionPluginStart { ): SecuritySolutionPluginStart {
const { config, logger, appFeaturesService } = this; const { config, logger, productFeaturesService } = this;
this.ruleMonitoringService.start(core, plugins); this.ruleMonitoringService.start(core, plugins);
@ -567,7 +570,7 @@ export class Plugin implements ISecuritySolutionPlugin {
experimentalFeatures: config.experimentalFeatures, experimentalFeatures: config.experimentalFeatures,
packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize, packagerTaskPackagePolicyUpdateBatchSize: config.packagerTaskPackagePolicyUpdateBatchSize,
esClient: core.elasticsearch.client.asInternalUser, esClient: core.elasticsearch.client.asInternalUser,
appFeaturesService, productFeaturesService,
}); });
// Migrate artifacts to fleet and then start the manifest task after that is done // Migrate artifacts to fleet and then start the manifest task after that is done
@ -584,13 +587,13 @@ export class Plugin implements ISecuritySolutionPlugin {
turnOffPolicyProtectionsIfNotSupported( turnOffPolicyProtectionsIfNotSupported(
core.elasticsearch.client.asInternalUser, core.elasticsearch.client.asInternalUser,
endpointFleetServicesFactory.asInternalUser(), endpointFleetServicesFactory.asInternalUser(),
appFeaturesService, productFeaturesService,
logger logger
); );
turnOffAgentPolicyFeatures( turnOffAgentPolicyFeatures(
endpointFleetServicesFactory.asInternalUser(), endpointFleetServicesFactory.asInternalUser(),
appFeaturesService, productFeaturesService,
logger logger
); );
}); });
@ -636,7 +639,7 @@ export class Plugin implements ISecuritySolutionPlugin {
), ),
createFleetActionsClient, createFleetActionsClient,
esClient: core.elasticsearch.client.asInternalUser, esClient: core.elasticsearch.client.asInternalUser,
appFeaturesService, productFeaturesService,
savedObjectsClient, savedObjectsClient,
}); });

View file

@ -42,7 +42,7 @@ import type { SharePluginStart } from '@kbn/share-plugin/server';
import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server'; import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server';
import type { PluginSetup as UnifiedSearchServerPluginSetup } from '@kbn/unified-search-plugin/server'; import type { PluginSetup as UnifiedSearchServerPluginSetup } from '@kbn/unified-search-plugin/server';
import type { ElasticAssistantPluginStart } from '@kbn/elastic-assistant-plugin/server'; import type { ElasticAssistantPluginStart } from '@kbn/elastic-assistant-plugin/server';
import type { AppFeaturesService } from './lib/app_features_service/app_features_service'; import type { ProductFeaturesService } from './lib/product_features_service/product_features_service';
import type { ExperimentalFeatures } from '../common'; import type { ExperimentalFeatures } from '../common';
export interface SecuritySolutionPluginSetupDependencies { export interface SecuritySolutionPluginSetupDependencies {
@ -90,7 +90,7 @@ export interface SecuritySolutionPluginSetup {
/** /**
* Sets the configurations for app features that are available to the Security Solution * Sets the configurations for app features that are available to the Security Solution
*/ */
setAppFeaturesConfigurator: AppFeaturesService['setAppFeaturesConfigurator']; setProductFeaturesConfigurator: ProductFeaturesService['setProductFeaturesConfigurator'];
/** /**
* The security solution generic experimental features * The security solution generic experimental features
*/ */

View file

@ -1,22 +0,0 @@
/*
* 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 type { AppFeatureKeys } from '@kbn/security-solution-features';
import type { AppFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/app_features_service/types';
import { getCasesAppFeaturesConfigurator } from './cases_app_features_config';
import { getSecurityAppFeaturesConfigurator } from './security_app_features_config';
import { getSecurityAssistantAppFeaturesConfigurator } from './security_assistant_app_features_config';
export const getProductAppFeaturesConfigurator = (
enabledAppFeatureKeys: AppFeatureKeys
): AppFeaturesConfigurator => {
return {
security: getSecurityAppFeaturesConfigurator(enabledAppFeatureKeys),
cases: getCasesAppFeaturesConfigurator(enabledAppFeatureKeys),
securityAssistant: getSecurityAssistantAppFeaturesConfigurator(enabledAppFeatureKeys),
};
};

View file

@ -5,8 +5,8 @@
* 2.0. * 2.0.
*/ */
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
// Just copying all feature keys for now. // Just copying all feature keys for now.
// We may need a different set of keys in the future if we create serverless-specific appFeatures // We may need a different set of keys in the future if we create serverless-specific productFeatures
export const DEFAULT_APP_FEATURES = [...ALL_APP_FEATURE_KEYS]; export const DEFAULT_PRODUCT_FEATURES = [...ALL_PRODUCT_FEATURE_KEYS];

View file

@ -6,8 +6,8 @@
*/ */
import type { Plugin, CoreSetup } from '@kbn/core/server'; import type { Plugin, CoreSetup } from '@kbn/core/server';
import { getProductAppFeaturesConfigurator } from './app_features'; import { getProductProductFeaturesConfigurator } from './product_features';
import { DEFAULT_APP_FEATURES } from './constants'; import { DEFAULT_PRODUCT_FEATURES } from './constants';
import type { import type {
SecuritySolutionEssPluginSetup, SecuritySolutionEssPluginSetup,
@ -26,8 +26,9 @@ export class SecuritySolutionEssPlugin
> >
{ {
public setup(_coreSetup: CoreSetup, pluginsSetup: SecuritySolutionEssPluginSetupDeps) { public setup(_coreSetup: CoreSetup, pluginsSetup: SecuritySolutionEssPluginSetupDeps) {
const appFeaturesConfigurator = getProductAppFeaturesConfigurator(DEFAULT_APP_FEATURES); const productFeaturesConfigurator =
pluginsSetup.securitySolution.setAppFeaturesConfigurator(appFeaturesConfigurator); getProductProductFeaturesConfigurator(DEFAULT_PRODUCT_FEATURES);
pluginsSetup.securitySolution.setProductFeaturesConfigurator(productFeaturesConfigurator);
return {}; return {};
} }

View file

@ -5,26 +5,29 @@
* 2.0. * 2.0.
*/ */
import type { import type {
AppFeatureKeys, ProductFeatureKeys,
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
AppFeaturesAssistantConfig, ProductFeaturesAssistantConfig,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import { import {
assistantDefaultAppFeaturesConfig, assistantDefaultProductFeaturesConfig,
createEnabledAppFeaturesConfigMap, createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config'; } from '@kbn/security-solution-features/config';
import type { import type {
AppFeatureAssistantKey, ProductFeatureAssistantKey,
AssistantSubFeatureId, AssistantSubFeatureId,
} from '@kbn/security-solution-features/keys'; } from '@kbn/security-solution-features/keys';
export const getSecurityAssistantAppFeaturesConfigurator = export const getSecurityAssistantProductFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesAssistantConfig => { (enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesAssistantConfig => {
return createEnabledAppFeaturesConfigMap(assistantAppFeaturesConfig, enabledAppFeatureKeys); return createEnabledProductFeaturesConfigMap(
assistantProductFeaturesConfig,
enabledProductFeatureKeys
);
}; };
/** /**
* Maps the AppFeatures keys to Kibana privileges that will be merged * Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Assistant app. * into the base privileges config for the Security Assistant app.
* *
* Privileges can be added in different ways: * Privileges can be added in different ways:
@ -32,10 +35,10 @@ export const getSecurityAssistantAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Assistant subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Assistant subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Assistant subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Assistant subFeature with the privilege `id` specified.
*/ */
const assistantAppFeaturesConfig: Record< const assistantProductFeaturesConfig: Record<
AppFeatureAssistantKey, ProductFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId> ProductFeatureKibanaConfig<AssistantSubFeatureId>
> = { > = {
...assistantDefaultAppFeaturesConfig, ...assistantDefaultProductFeaturesConfig,
// ess-specific app features configs here // ess-specific app features configs here
}; };

View file

@ -5,14 +5,17 @@
* 2.0. * 2.0.
*/ */
import type { import type {
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
AppFeaturesCasesConfig, ProductFeaturesCasesConfig,
AppFeatureKeys, ProductFeatureKeys,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import type { AppFeatureCasesKey, CasesSubFeatureId } from '@kbn/security-solution-features/keys'; import type {
ProductFeatureCasesKey,
CasesSubFeatureId,
} from '@kbn/security-solution-features/keys';
import { import {
getCasesDefaultAppFeaturesConfig, getCasesDefaultProductFeaturesConfig,
createEnabledAppFeaturesConfigMap, createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config'; } from '@kbn/security-solution-features/config';
import { import {
@ -20,13 +23,16 @@ import {
GET_CONNECTORS_CONFIGURE_API_TAG, GET_CONNECTORS_CONFIGURE_API_TAG,
} from '@kbn/cases-plugin/common/constants'; } from '@kbn/cases-plugin/common/constants';
export const getCasesAppFeaturesConfigurator = export const getCasesProductFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesCasesConfig => { (enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesCasesConfig => {
return createEnabledAppFeaturesConfigMap(casesAppFeaturesConfig, enabledAppFeatureKeys); return createEnabledProductFeaturesConfigMap(
casesProductFeaturesConfig,
enabledProductFeatureKeys
);
}; };
/** /**
* Maps the AppFeatures keys to Kibana privileges that will be merged * Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Cases app. * into the base privileges config for the Security Cases app.
* *
* Privileges can be added in different ways: * Privileges can be added in different ways:
@ -34,11 +40,11 @@ export const getCasesAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Cases subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Cases subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Cases subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Cases subFeature with the privilege `id` specified.
*/ */
const casesAppFeaturesConfig: Record< const casesProductFeaturesConfig: Record<
AppFeatureCasesKey, ProductFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId> ProductFeatureKibanaConfig<CasesSubFeatureId>
> = { > = {
...getCasesDefaultAppFeaturesConfig({ ...getCasesDefaultProductFeaturesConfig({
apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG }, apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG },
uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY }, uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY },
}), }),

View file

@ -0,0 +1,22 @@
/*
* 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 type { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { ProductFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/product_features_service/types';
import { getCasesProductFeaturesConfigurator } from './cases_product_features_config';
import { getSecurityProductFeaturesConfigurator } from './security_product_features_config';
import { getSecurityAssistantProductFeaturesConfigurator } from './assistant_product_features_config';
export const getProductProductFeaturesConfigurator = (
enabledProductFeatureKeys: ProductFeatureKeys
): ProductFeaturesConfigurator => {
return {
security: getSecurityProductFeaturesConfigurator(enabledProductFeatureKeys),
cases: getCasesProductFeaturesConfigurator(enabledProductFeatureKeys),
securityAssistant: getSecurityAssistantProductFeaturesConfigurator(enabledProductFeatureKeys),
};
};

View file

@ -5,30 +5,33 @@
* 2.0. * 2.0.
*/ */
import type { import type {
AppFeatureKeys, ProductFeatureKeys,
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
AppFeaturesSecurityConfig, ProductFeaturesSecurityConfig,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import { import {
AppFeatureSecurityKey, ProductFeatureSecurityKey,
type SecuritySubFeatureId, type SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys'; } from '@kbn/security-solution-features/keys';
import { import {
securityDefaultAppFeaturesConfig, securityDefaultProductFeaturesConfig,
createEnabledAppFeaturesConfigMap, createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config'; } from '@kbn/security-solution-features/config';
import { import {
AppFeaturesPrivilegeId, ProductFeaturesPrivilegeId,
AppFeaturesPrivileges, ProductFeaturesPrivileges,
} from '@kbn/security-solution-features/privileges'; } from '@kbn/security-solution-features/privileges';
export const getSecurityAppFeaturesConfigurator = export const getSecurityProductFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesSecurityConfig => { (enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesSecurityConfig => {
return createEnabledAppFeaturesConfigMap(securityAppFeaturesConfig, enabledAppFeatureKeys); return createEnabledProductFeaturesConfigMap(
securityProductFeaturesConfig,
enabledProductFeatureKeys
);
}; };
/** /**
* Maps the AppFeatures keys to Kibana privileges that will be merged * Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security app. * into the base privileges config for the Security app.
* *
* Privileges can be added in different ways: * Privileges can be added in different ways:
@ -36,12 +39,12 @@ export const getSecurityAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/ */
const securityAppFeaturesConfig: Record< const securityProductFeaturesConfig: Record<
AppFeatureSecurityKey, ProductFeatureSecurityKey,
AppFeatureKibanaConfig<SecuritySubFeatureId> ProductFeatureKibanaConfig<SecuritySubFeatureId>
> = { > = {
...securityDefaultAppFeaturesConfig, ...securityDefaultProductFeaturesConfig,
[AppFeatureSecurityKey.endpointExceptions]: { [ProductFeatureSecurityKey.endpointExceptions]: {
privileges: AppFeaturesPrivileges[AppFeaturesPrivilegeId.endpointExceptions], privileges: ProductFeaturesPrivileges[ProductFeaturesPrivilegeId.endpointExceptions],
}, },
}; };

View file

@ -5,38 +5,41 @@
* 2.0. * 2.0.
*/ */
import type { AppFeatureKeys } from '@kbn/security-solution-features'; import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import { AppFeatureKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import type { SecurityProductLine, SecurityProductTier } from '../config'; import type { SecurityProductLine, SecurityProductTier } from '../config';
type PliAppFeatures = Readonly< type PliProductFeatures = Readonly<
Record<SecurityProductLine, Readonly<Record<SecurityProductTier, Readonly<AppFeatureKeys>>>> Record<SecurityProductLine, Readonly<Record<SecurityProductTier, Readonly<ProductFeatureKeys>>>>
>; >;
export const PLI_APP_FEATURES: PliAppFeatures = { export const PLI_PRODUCT_FEATURES: PliProductFeatures = {
security: { security: {
essentials: [AppFeatureKey.endpointHostManagement, AppFeatureKey.endpointPolicyManagement], essentials: [
ProductFeatureKey.endpointHostManagement,
ProductFeatureKey.endpointPolicyManagement,
],
complete: [ complete: [
AppFeatureKey.advancedInsights, ProductFeatureKey.advancedInsights,
AppFeatureKey.assistant, ProductFeatureKey.assistant,
AppFeatureKey.investigationGuide, ProductFeatureKey.investigationGuide,
AppFeatureKey.threatIntelligence, ProductFeatureKey.threatIntelligence,
AppFeatureKey.casesConnectors, ProductFeatureKey.casesConnectors,
AppFeatureKey.externalRuleActions, ProductFeatureKey.externalRuleActions,
], ],
}, },
endpoint: { endpoint: {
essentials: [ essentials: [
AppFeatureKey.endpointPolicyProtections, ProductFeatureKey.endpointPolicyProtections,
AppFeatureKey.endpointArtifactManagement, ProductFeatureKey.endpointArtifactManagement,
AppFeatureKey.endpointExceptions, ProductFeatureKey.endpointExceptions,
], ],
complete: [ complete: [
AppFeatureKey.endpointResponseActions, ProductFeatureKey.endpointResponseActions,
AppFeatureKey.osqueryAutomatedResponseActions, ProductFeatureKey.osqueryAutomatedResponseActions,
AppFeatureKey.endpointAgentTamperProtection, ProductFeatureKey.endpointAgentTamperProtection,
AppFeatureKey.endpointExceptions, ProductFeatureKey.endpointExceptions,
AppFeatureKey.endpointProtectionUpdates, ProductFeatureKey.endpointProtectionUpdates,
], ],
}, },
cloud: { cloud: {

View file

@ -4,44 +4,44 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import { getProductAppFeatures } from './pli_features'; import { getProductProductFeatures } from './pli_features';
import * as pliConfig from './pli_config'; import * as pliConfig from './pli_config';
import { ProductLine, ProductTier } from '../product'; import { ProductLine, ProductTier } from '../product';
describe('getProductAppFeatures', () => { describe('getProductProductFeatures', () => {
it('should return the essentials PLIs features', () => { it('should return the essentials PLIs features', () => {
// @ts-ignore reassigning readonly value for testing // @ts-ignore reassigning readonly value for testing
pliConfig.PLI_APP_FEATURES = { pliConfig.PLI_PRODUCT_FEATURES = {
security: { security: {
essentials: ['foo'], essentials: ['foo'],
complete: ['baz'], complete: ['baz'],
}, },
}; };
const appFeatureKeys = getProductAppFeatures([ const productFeatureKeys = getProductProductFeatures([
{ product_line: ProductLine.security, product_tier: ProductTier.essentials }, { product_line: ProductLine.security, product_tier: ProductTier.essentials },
]); ]);
expect(appFeatureKeys).toEqual(['foo']); expect(productFeatureKeys).toEqual(['foo']);
}); });
it('should return the complete PLIs features, which includes essentials', () => { it('should return the complete PLIs features, which includes essentials', () => {
// @ts-ignore reassigning readonly value for testing // @ts-ignore reassigning readonly value for testing
pliConfig.PLI_APP_FEATURES = { pliConfig.PLI_PRODUCT_FEATURES = {
security: { security: {
essentials: ['foo'], essentials: ['foo'],
complete: ['baz'], complete: ['baz'],
}, },
}; };
const appFeatureKeys = getProductAppFeatures([ const productFeatureKeys = getProductProductFeatures([
{ product_line: ProductLine.security, product_tier: ProductTier.complete }, { product_line: ProductLine.security, product_tier: ProductTier.complete },
]); ]);
expect(appFeatureKeys).toEqual(['foo', 'baz']); expect(productFeatureKeys).toEqual(['foo', 'baz']);
}); });
it('returns an empty object if no PLIs are enabled', () => { it('returns an empty object if no PLIs are enabled', () => {
expect(getProductAppFeatures([])).toEqual([]); expect(getProductProductFeatures([])).toEqual([]);
}); });
}); });

View file

@ -5,24 +5,26 @@
* 2.0. * 2.0.
*/ */
import type { AppFeatureKeys } from '@kbn/security-solution-features/src/types'; import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { SecurityProductTypes } from '../config'; import type { SecurityProductTypes } from '../config';
import { ProductTier } from '../product'; import { ProductTier } from '../product';
import { PLI_APP_FEATURES } from './pli_config'; import { PLI_PRODUCT_FEATURES } from './pli_config';
/** /**
* Returns the U (union) of all PLIs from the enabled productTypes in a single array. * Returns the U (union) of all PLIs from the enabled productTypes in a single array.
*/ */
export const getProductAppFeatures = (productTypes: SecurityProductTypes): AppFeatureKeys => { export const getProductProductFeatures = (
const appFeatureKeys = productTypes.reduce<AppFeatureKeys>( productTypes: SecurityProductTypes
(appFeatures, { product_line: line, product_tier: tier }) => { ): ProductFeatureKeys => {
const productFeatureKeys = productTypes.reduce<ProductFeatureKeys>(
(productFeatures, { product_line: line, product_tier: tier }) => {
if (tier === ProductTier.complete) { if (tier === ProductTier.complete) {
appFeatures.push(...PLI_APP_FEATURES[line][ProductTier.essentials]); productFeatures.push(...PLI_PRODUCT_FEATURES[line][ProductTier.essentials]);
} }
appFeatures.push(...PLI_APP_FEATURES[line][tier]); productFeatures.push(...PLI_PRODUCT_FEATURES[line][tier]);
return appFeatures; return productFeatures;
}, },
[] []
); );
return appFeatureKeys; return productFeatureKeys;
}; };

View file

@ -5,26 +5,26 @@
* 2.0. * 2.0.
*/ */
import type { AppFeatureKeyType } from '@kbn/security-solution-features'; import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { PLI_APP_FEATURES } from '../../../common/pli/pli_config'; import { PLI_PRODUCT_FEATURES } from '../../../common/pli/pli_config';
export const getProductTypeByPLI = (requiredPLI: AppFeatureKeyType): string | null => { export const getProductTypeByPLI = (requiredPLI: ProductFeatureKeyType): string | null => {
if (PLI_APP_FEATURES.security.essentials.includes(requiredPLI)) { if (PLI_PRODUCT_FEATURES.security.essentials.includes(requiredPLI)) {
return 'Security Essentials'; return 'Security Essentials';
} }
if (PLI_APP_FEATURES.security.complete.includes(requiredPLI)) { if (PLI_PRODUCT_FEATURES.security.complete.includes(requiredPLI)) {
return 'Security Complete'; return 'Security Complete';
} }
if (PLI_APP_FEATURES.endpoint.essentials.includes(requiredPLI)) { if (PLI_PRODUCT_FEATURES.endpoint.essentials.includes(requiredPLI)) {
return 'Endpoint Essentials'; return 'Endpoint Essentials';
} }
if (PLI_APP_FEATURES.endpoint.complete.includes(requiredPLI)) { if (PLI_PRODUCT_FEATURES.endpoint.complete.includes(requiredPLI)) {
return 'Endpoint Complete'; return 'Endpoint Complete';
} }
if (PLI_APP_FEATURES.cloud.essentials.includes(requiredPLI)) { if (PLI_PRODUCT_FEATURES.cloud.essentials.includes(requiredPLI)) {
return 'Cloud Essentials'; return 'Cloud Essentials';
} }
if (PLI_APP_FEATURES.cloud.complete.includes(requiredPLI)) { if (PLI_PRODUCT_FEATURES.cloud.complete.includes(requiredPLI)) {
return 'Cloud Complete'; return 'Cloud Complete';
} }
return null; return null;

View file

@ -8,10 +8,10 @@
import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui'; import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import React, { memo } from 'react'; import React, { memo } from 'react';
import type { AppFeatureKeyType } from '@kbn/security-solution-features/keys'; import type { ProductFeatureKeyType } from '@kbn/security-solution-features/keys';
import { getProductTypeByPLI } from '../../hooks/use_product_type_by_pli'; import { getProductTypeByPLI } from '../../hooks/use_product_type_by_pli';
const EndpointExceptionsDetailsUpselling: React.FC<{ requiredPLI: AppFeatureKeyType }> = memo( const EndpointExceptionsDetailsUpselling: React.FC<{ requiredPLI: ProductFeatureKeyType }> = memo(
({ requiredPLI }) => { ({ requiredPLI }) => {
const productTypeRequired = getProductTypeByPLI(requiredPLI); const productTypeRequired = getProductTypeByPLI(requiredPLI);

View file

@ -9,10 +9,10 @@ import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react'; import React from 'react';
import type { AppFeatureKeyType } from '@kbn/security-solution-features'; import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli';
const OsqueryResponseActionsUpsellingSection: React.FC<{ requiredPLI: AppFeatureKeyType }> = const OsqueryResponseActionsUpsellingSection: React.FC<{ requiredPLI: ProductFeatureKeyType }> =
React.memo(({ requiredPLI }) => { React.memo(({ requiredPLI }) => {
const productTypeRequired = getProductTypeByPLI(requiredPLI); const productTypeRequired = getProductTypeByPLI(requiredPLI);

View file

@ -8,11 +8,11 @@
import React from 'react'; import React from 'react';
import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui'; import { EuiEmptyPrompt, EuiIcon } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react'; import { FormattedMessage } from '@kbn/i18n-react';
import type { AppFeatureKeyType } from '@kbn/security-solution-features'; import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template'; import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli'; import { getProductTypeByPLI } from '../hooks/use_product_type_by_pli';
const ThreatIntelligencePaywall: React.FC<{ requiredPLI: AppFeatureKeyType }> = React.memo( const ThreatIntelligencePaywall: React.FC<{ requiredPLI: ProductFeatureKeyType }> = React.memo(
function PaywallComponent({ requiredPLI }) { function PaywallComponent({ requiredPLI }) {
const productTypeRequired = getProductTypeByPLI(requiredPLI); const productTypeRequired = getProductTypeByPLI(requiredPLI);

View file

@ -13,13 +13,13 @@ import {
} from './register_upsellings'; } from './register_upsellings';
import { ProductLine, ProductTier } from '../../common/product'; import { ProductLine, ProductTier } from '../../common/product';
import type { SecurityProductTypes } from '../../common/config'; import type { SecurityProductTypes } from '../../common/config';
import { ALL_APP_FEATURE_KEYS } from '@kbn/security-solution-features/keys'; import { ALL_PRODUCT_FEATURE_KEYS } from '@kbn/security-solution-features/keys';
import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { UpsellingService } from '@kbn/security-solution-upselling/service';
import { mockServices } from '../common/services/__mocks__/services.mock'; import { mockServices } from '../common/services/__mocks__/services.mock';
const mockGetProductAppFeatures = jest.fn(); const mockGetProductProductFeatures = jest.fn();
jest.mock('../../common/pli/pli_features', () => ({ jest.mock('../../common/pli/pli_features', () => ({
getProductAppFeatures: () => mockGetProductAppFeatures(), getProductProductFeatures: () => mockGetProductProductFeatures(),
})); }));
const allProductTypes: SecurityProductTypes = [ const allProductTypes: SecurityProductTypes = [
@ -30,7 +30,7 @@ const allProductTypes: SecurityProductTypes = [
describe('registerUpsellings', () => { describe('registerUpsellings', () => {
it('should not register anything when all PLIs features are enabled', () => { it('should not register anything when all PLIs features are enabled', () => {
mockGetProductAppFeatures.mockReturnValue(ALL_APP_FEATURE_KEYS); mockGetProductProductFeatures.mockReturnValue(ALL_PRODUCT_FEATURE_KEYS);
const setPages = jest.fn(); const setPages = jest.fn();
const setSections = jest.fn(); const setSections = jest.fn();
@ -54,7 +54,7 @@ describe('registerUpsellings', () => {
}); });
it('should register all upsellings pages, sections and messages when PLIs features are disabled', () => { it('should register all upsellings pages, sections and messages when PLIs features are disabled', () => {
mockGetProductAppFeatures.mockReturnValue([]); mockGetProductProductFeatures.mockReturnValue([]);
const setPages = jest.fn(); const setPages = jest.fn();
const setSections = jest.fn(); const setSections = jest.fn();

View file

@ -15,8 +15,8 @@ import type {
import type { UpsellingService } from '@kbn/security-solution-upselling/service'; import type { UpsellingService } from '@kbn/security-solution-upselling/service';
import React from 'react'; import React from 'react';
import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/messages'; import { UPGRADE_INVESTIGATION_GUIDE } from '@kbn/security-solution-upselling/messages';
import { AppFeatureKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureKey } from '@kbn/security-solution-features/keys';
import type { AppFeatureKeyType } from '@kbn/security-solution-features'; import type { ProductFeatureKeyType } from '@kbn/security-solution-features';
import { import {
EndpointAgentTamperProtectionLazy, EndpointAgentTamperProtectionLazy,
EndpointPolicyProtectionsLazy, EndpointPolicyProtectionsLazy,
@ -24,7 +24,7 @@ import {
RuleDetailsEndpointExceptionsLazy, RuleDetailsEndpointExceptionsLazy,
} from './sections/endpoint_management'; } from './sections/endpoint_management';
import type { SecurityProductTypes } from '../../common/config'; import type { SecurityProductTypes } from '../../common/config';
import { getProductAppFeatures } from '../../common/pli/pli_features'; import { getProductProductFeatures } from '../../common/pli/pli_features';
import { import {
EndpointExceptionsDetailsUpsellingLazy, EndpointExceptionsDetailsUpsellingLazy,
EntityAnalyticsUpsellingLazy, EntityAnalyticsUpsellingLazy,
@ -36,12 +36,12 @@ import type { Services } from '../common/services';
import { withServicesProvider } from '../common/services'; import { withServicesProvider } from '../common/services';
interface UpsellingsConfig { interface UpsellingsConfig {
pli: AppFeatureKeyType; pli: ProductFeatureKeyType;
component: React.ComponentType; component: React.ComponentType;
} }
interface UpsellingsMessageConfig { interface UpsellingsMessageConfig {
pli: AppFeatureKeyType; pli: ProductFeatureKeyType;
message: string; message: string;
id: UpsellingMessageId; id: UpsellingMessageId;
} }
@ -55,7 +55,7 @@ export const registerUpsellings = (
productTypes: SecurityProductTypes, productTypes: SecurityProductTypes,
services: Services services: Services
) => { ) => {
const enabledPLIsSet = new Set(getProductAppFeatures(productTypes)); const enabledPLIsSet = new Set(getProductProductFeatures(productTypes));
const upsellingPagesToRegister = upsellingPages.reduce<PageUpsellings>( const upsellingPagesToRegister = upsellingPages.reduce<PageUpsellings>(
(pageUpsellings, { pageName, pli, component }) => { (pageUpsellings, { pageName, pli, component }) => {
@ -97,25 +97,25 @@ export const upsellingPages: UpsellingPages = [
// It is highly advisable to make use of lazy loaded components to minimize bundle size. // It is highly advisable to make use of lazy loaded components to minimize bundle size.
{ {
pageName: SecurityPageName.entityAnalytics, pageName: SecurityPageName.entityAnalytics,
pli: AppFeatureKey.advancedInsights, pli: ProductFeatureKey.advancedInsights,
component: () => ( component: () => (
<EntityAnalyticsUpsellingLazy <EntityAnalyticsUpsellingLazy
requiredProduct={getProductTypeByPLI(AppFeatureKey.advancedInsights) ?? undefined} requiredProduct={getProductTypeByPLI(ProductFeatureKey.advancedInsights) ?? undefined}
/> />
), ),
}, },
{ {
pageName: SecurityPageName.threatIntelligence, pageName: SecurityPageName.threatIntelligence,
pli: AppFeatureKey.threatIntelligence, pli: ProductFeatureKey.threatIntelligence,
component: () => ( component: () => (
<ThreatIntelligencePaywallLazy requiredPLI={AppFeatureKey.threatIntelligence} /> <ThreatIntelligencePaywallLazy requiredPLI={ProductFeatureKey.threatIntelligence} />
), ),
}, },
{ {
pageName: SecurityPageName.exceptions, pageName: SecurityPageName.exceptions,
pli: AppFeatureKey.endpointExceptions, pli: ProductFeatureKey.endpointExceptions,
component: () => ( component: () => (
<EndpointExceptionsDetailsUpsellingLazy requiredPLI={AppFeatureKey.endpointExceptions} /> <EndpointExceptionsDetailsUpsellingLazy requiredPLI={ProductFeatureKey.endpointExceptions} />
), ),
}, },
]; ];
@ -125,31 +125,31 @@ export const upsellingSections: UpsellingSections = [
// It is highly advisable to make use of lazy loaded components to minimize bundle size. // It is highly advisable to make use of lazy loaded components to minimize bundle size.
{ {
id: 'osquery_automated_response_actions', id: 'osquery_automated_response_actions',
pli: AppFeatureKey.osqueryAutomatedResponseActions, pli: ProductFeatureKey.osqueryAutomatedResponseActions,
component: () => ( component: () => (
<OsqueryResponseActionsUpsellingSectionLazy <OsqueryResponseActionsUpsellingSectionLazy
requiredPLI={AppFeatureKey.osqueryAutomatedResponseActions} requiredPLI={ProductFeatureKey.osqueryAutomatedResponseActions}
/> />
), ),
}, },
{ {
id: 'endpoint_agent_tamper_protection', id: 'endpoint_agent_tamper_protection',
pli: AppFeatureKey.endpointAgentTamperProtection, pli: ProductFeatureKey.endpointAgentTamperProtection,
component: EndpointAgentTamperProtectionLazy, component: EndpointAgentTamperProtectionLazy,
}, },
{ {
id: 'endpointPolicyProtections', id: 'endpointPolicyProtections',
pli: AppFeatureKey.endpointPolicyProtections, pli: ProductFeatureKey.endpointPolicyProtections,
component: EndpointPolicyProtectionsLazy, component: EndpointPolicyProtectionsLazy,
}, },
{ {
id: 'ruleDetailsEndpointExceptions', id: 'ruleDetailsEndpointExceptions',
pli: AppFeatureKey.endpointExceptions, pli: ProductFeatureKey.endpointExceptions,
component: RuleDetailsEndpointExceptionsLazy, component: RuleDetailsEndpointExceptionsLazy,
}, },
{ {
id: 'endpoint_protection_updates', id: 'endpoint_protection_updates',
pli: AppFeatureKey.endpointProtectionUpdates, pli: ProductFeatureKey.endpointProtectionUpdates,
component: EndpointProtectionUpdatesLazy, component: EndpointProtectionUpdatesLazy,
}, },
]; ];
@ -158,9 +158,9 @@ export const upsellingSections: UpsellingSections = [
export const upsellingMessages: UpsellingMessages = [ export const upsellingMessages: UpsellingMessages = [
{ {
id: 'investigation_guide', id: 'investigation_guide',
pli: AppFeatureKey.investigationGuide, pli: ProductFeatureKey.investigationGuide,
message: UPGRADE_INVESTIGATION_GUIDE( message: UPGRADE_INVESTIGATION_GUIDE(
getProductTypeByPLI(AppFeatureKey.investigationGuide) ?? '' getProductTypeByPLI(ProductFeatureKey.investigationGuide) ?? ''
), ),
}, },
]; ];

View file

@ -1,27 +0,0 @@
/*
* 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 type { AppFeatureKeys } from '@kbn/security-solution-features';
import type { AppFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/app_features_service/types';
import type { ServerlessSecurityConfig } from '../config';
import { getCasesAppFeaturesConfigurator } from './cases_app_features_config';
import { getSecurityAppFeaturesConfigurator } from './security_app_features_config';
import { getSecurityAssistantAppFeaturesConfigurator } from './security_assistant_app_features_config';
export const getProductAppFeaturesConfigurator = (
enabledAppFeatureKeys: AppFeatureKeys,
config: ServerlessSecurityConfig
): AppFeaturesConfigurator => {
return {
security: getSecurityAppFeaturesConfigurator(
enabledAppFeatureKeys,
config.experimentalFeatures
),
cases: getCasesAppFeaturesConfigurator(enabledAppFeatureKeys),
securityAssistant: getSecurityAssistantAppFeaturesConfigurator(enabledAppFeatureKeys),
};
};

View file

@ -14,7 +14,7 @@ import type {
} from '@kbn/core/server'; } from '@kbn/core/server';
import { SECURITY_PROJECT_SETTINGS } from '@kbn/serverless-security-settings'; import { SECURITY_PROJECT_SETTINGS } from '@kbn/serverless-security-settings';
import { getProductAppFeatures } from '../common/pli/pli_features'; import { getProductProductFeatures } from '../common/pli/pli_features';
import type { ServerlessSecurityConfig } from './config'; import type { ServerlessSecurityConfig } from './config';
import { createConfig } from './config'; import { createConfig } from './config';
@ -26,7 +26,7 @@ import type {
} from './types'; } from './types';
import { SecurityUsageReportingTask } from './task_manager/usage_reporting_task'; import { SecurityUsageReportingTask } from './task_manager/usage_reporting_task';
import { cloudSecurityMetringTaskProperties } from './cloud_security/cloud_security_metering_task_config'; import { cloudSecurityMetringTaskProperties } from './cloud_security/cloud_security_metering_task_config';
import { getProductAppFeaturesConfigurator } from './app_features'; import { getProductProductFeaturesConfigurator } from './product_features';
import { METERING_TASK as ENDPOINT_METERING_TASK } from './endpoint/constants/metering'; import { METERING_TASK as ENDPOINT_METERING_TASK } from './endpoint/constants/metering';
import { import {
endpointMeteringService, endpointMeteringService,
@ -55,7 +55,7 @@ export class SecuritySolutionServerlessPlugin
public setup(coreSetup: CoreSetup, pluginsSetup: SecuritySolutionServerlessPluginSetupDeps) { public setup(coreSetup: CoreSetup, pluginsSetup: SecuritySolutionServerlessPluginSetupDeps) {
this.config = createConfig(this.initializerContext, pluginsSetup.securitySolution); this.config = createConfig(this.initializerContext, pluginsSetup.securitySolution);
const enabledAppFeatures = getProductAppFeatures(this.config.productTypes); const enabledProductFeatures = getProductProductFeatures(this.config.productTypes);
// securitySolutionEss plugin should always be disabled when securitySolutionServerless is enabled. // securitySolutionEss plugin should always be disabled when securitySolutionServerless is enabled.
// This check is an additional layer of security to prevent double registrations when // This check is an additional layer of security to prevent double registrations when
@ -64,14 +64,17 @@ export class SecuritySolutionServerlessPlugin
if (shouldRegister) { if (shouldRegister) {
const productTypesStr = JSON.stringify(this.config.productTypes, null, 2); const productTypesStr = JSON.stringify(this.config.productTypes, null, 2);
this.logger.info(`Security Solution running with product types:\n${productTypesStr}`); this.logger.info(`Security Solution running with product types:\n${productTypesStr}`);
const appFeaturesConfigurator = getProductAppFeaturesConfigurator( const productFeaturesConfigurator = getProductProductFeaturesConfigurator(
enabledAppFeatures, enabledProductFeatures,
this.config this.config
); );
pluginsSetup.securitySolution.setAppFeaturesConfigurator(appFeaturesConfigurator); pluginsSetup.securitySolution.setProductFeaturesConfigurator(productFeaturesConfigurator);
} }
enableRuleActions({ actions: pluginsSetup.actions, appFeatureKeys: enabledAppFeatures }); enableRuleActions({
actions: pluginsSetup.actions,
productFeatureKeys: enabledProductFeatures,
});
this.cloudSecurityUsageReportingTask = new SecurityUsageReportingTask({ this.cloudSecurityUsageReportingTask = new SecurityUsageReportingTask({
core: coreSetup, core: coreSetup,

View file

@ -5,26 +5,29 @@
* 2.0. * 2.0.
*/ */
import type { import type {
AppFeatureKeys, ProductFeatureKeys,
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
AppFeaturesAssistantConfig, ProductFeaturesAssistantConfig,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import { import {
assistantDefaultAppFeaturesConfig, assistantDefaultProductFeaturesConfig,
createEnabledAppFeaturesConfigMap, createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config'; } from '@kbn/security-solution-features/config';
import type { import type {
AppFeatureAssistantKey, ProductFeatureAssistantKey,
AssistantSubFeatureId, AssistantSubFeatureId,
} from '@kbn/security-solution-features/keys'; } from '@kbn/security-solution-features/keys';
export const getSecurityAssistantAppFeaturesConfigurator = export const getSecurityAssistantProductFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesAssistantConfig => { (enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesAssistantConfig => {
return createEnabledAppFeaturesConfigMap(assistantAppFeaturesConfig, enabledAppFeatureKeys); return createEnabledProductFeaturesConfigMap(
assistantProductFeaturesConfig,
enabledProductFeatureKeys
);
}; };
/** /**
* Maps the AppFeatures keys to Kibana privileges that will be merged * Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Assistant app. * into the base privileges config for the Security Assistant app.
* *
* Privileges can be added in different ways: * Privileges can be added in different ways:
@ -32,10 +35,10 @@ export const getSecurityAssistantAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Assistant subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Assistant subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Assistant subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Assistant subFeature with the privilege `id` specified.
*/ */
const assistantAppFeaturesConfig: Record< const assistantProductFeaturesConfig: Record<
AppFeatureAssistantKey, ProductFeatureAssistantKey,
AppFeatureKibanaConfig<AssistantSubFeatureId> ProductFeatureKibanaConfig<AssistantSubFeatureId>
> = { > = {
...assistantDefaultAppFeaturesConfig, ...assistantDefaultProductFeaturesConfig,
// serverless-specific app features configs here // serverless-specific app features configs here
}; };

View file

@ -5,27 +5,33 @@
* 2.0. * 2.0.
*/ */
import type { import type {
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
AppFeaturesCasesConfig, ProductFeaturesCasesConfig,
AppFeatureKeys, ProductFeatureKeys,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import { import {
getCasesDefaultAppFeaturesConfig, getCasesDefaultProductFeaturesConfig,
createEnabledAppFeaturesConfigMap, createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config'; } from '@kbn/security-solution-features/config';
import type { AppFeatureCasesKey, CasesSubFeatureId } from '@kbn/security-solution-features/keys'; import type {
ProductFeatureCasesKey,
CasesSubFeatureId,
} from '@kbn/security-solution-features/keys';
import { import {
CASES_CONNECTORS_CAPABILITY, CASES_CONNECTORS_CAPABILITY,
GET_CONNECTORS_CONFIGURE_API_TAG, GET_CONNECTORS_CONFIGURE_API_TAG,
} from '@kbn/cases-plugin/common/constants'; } from '@kbn/cases-plugin/common/constants';
export const getCasesAppFeaturesConfigurator = export const getCasesProductFeaturesConfigurator =
(enabledAppFeatureKeys: AppFeatureKeys) => (): AppFeaturesCasesConfig => { (enabledProductFeatureKeys: ProductFeatureKeys) => (): ProductFeaturesCasesConfig => {
return createEnabledAppFeaturesConfigMap(casesAppFeaturesConfig, enabledAppFeatureKeys); return createEnabledProductFeaturesConfigMap(
casesProductFeaturesConfig,
enabledProductFeatureKeys
);
}; };
/** /**
* Maps the AppFeatures keys to Kibana privileges that will be merged * Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security Cases app. * into the base privileges config for the Security Cases app.
* *
* Privileges can be added in different ways: * Privileges can be added in different ways:
@ -33,11 +39,11 @@ export const getCasesAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Cases subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Cases subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Cases subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Cases subFeature with the privilege `id` specified.
*/ */
const casesAppFeaturesConfig: Record< const casesProductFeaturesConfig: Record<
AppFeatureCasesKey, ProductFeatureCasesKey,
AppFeatureKibanaConfig<CasesSubFeatureId> ProductFeatureKibanaConfig<CasesSubFeatureId>
> = { > = {
...getCasesDefaultAppFeaturesConfig({ ...getCasesDefaultProductFeaturesConfig({
apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG }, apiTags: { connectors: GET_CONNECTORS_CONFIGURE_API_TAG },
uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY }, uiCapabilities: { connectors: CASES_CONNECTORS_CAPABILITY },
}), }),

View file

@ -0,0 +1,27 @@
/*
* 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 type { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { ProductFeaturesConfigurator } from '@kbn/security-solution-plugin/server/lib/product_features_service/types';
import type { ServerlessSecurityConfig } from '../config';
import { getCasesProductFeaturesConfigurator } from './cases_product_features_config';
import { getSecurityProductFeaturesConfigurator } from './security_product_features_config';
import { getSecurityAssistantProductFeaturesConfigurator } from './assistant_product_features_config';
export const getProductProductFeaturesConfigurator = (
enabledProductFeatureKeys: ProductFeatureKeys,
config: ServerlessSecurityConfig
): ProductFeaturesConfigurator => {
return {
security: getSecurityProductFeaturesConfigurator(
enabledProductFeatureKeys,
config.experimentalFeatures
),
cases: getCasesProductFeaturesConfigurator(enabledProductFeatureKeys),
securityAssistant: getSecurityAssistantProductFeaturesConfigurator(enabledProductFeatureKeys),
};
};

View file

@ -5,28 +5,34 @@
* 2.0. * 2.0.
*/ */
import type { import type {
AppFeatureKeys, ProductFeatureKeys,
AppFeatureKibanaConfig, ProductFeatureKibanaConfig,
AppFeaturesSecurityConfig, ProductFeaturesSecurityConfig,
} from '@kbn/security-solution-features'; } from '@kbn/security-solution-features';
import { import {
securityDefaultAppFeaturesConfig, securityDefaultProductFeaturesConfig,
createEnabledAppFeaturesConfigMap, createEnabledProductFeaturesConfigMap,
} from '@kbn/security-solution-features/config'; } from '@kbn/security-solution-features/config';
import { AppFeatureSecurityKey, SecuritySubFeatureId } from '@kbn/security-solution-features/keys'; import {
ProductFeatureSecurityKey,
SecuritySubFeatureId,
} from '@kbn/security-solution-features/keys';
import type { ExperimentalFeatures } from '../../common/experimental_features'; import type { ExperimentalFeatures } from '../../common/experimental_features';
export const getSecurityAppFeaturesConfigurator = export const getSecurityProductFeaturesConfigurator =
( (
enabledAppFeatureKeys: AppFeatureKeys, enabledProductFeatureKeys: ProductFeatureKeys,
_: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use _: ExperimentalFeatures // currently un-used, but left here as a convenience for possible future use
) => ) =>
(): AppFeaturesSecurityConfig => { (): ProductFeaturesSecurityConfig => {
return createEnabledAppFeaturesConfigMap(securityAppFeaturesConfig, enabledAppFeatureKeys); return createEnabledProductFeaturesConfigMap(
securityProductFeaturesConfig,
enabledProductFeatureKeys
);
}; };
/** /**
* Maps the AppFeatures keys to Kibana privileges that will be merged * Maps the ProductFeatures keys to Kibana privileges that will be merged
* into the base privileges config for the Security app. * into the base privileges config for the Security app.
* *
* Privileges can be added in different ways: * Privileges can be added in different ways:
@ -34,12 +40,12 @@ export const getSecurityAppFeaturesConfigurator =
* - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry. * - `subFeatureIds`: the ids of the sub-features that will be added into the Security subFeatures entry.
* - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified. * - `subFeaturesPrivileges`: the privileges that will be added into the existing Security subFeature with the privilege `id` specified.
*/ */
const securityAppFeaturesConfig: Record< const securityProductFeaturesConfig: Record<
AppFeatureSecurityKey, ProductFeatureSecurityKey,
AppFeatureKibanaConfig<SecuritySubFeatureId> ProductFeatureKibanaConfig<SecuritySubFeatureId>
> = { > = {
...securityDefaultAppFeaturesConfig, ...securityDefaultProductFeaturesConfig,
[AppFeatureSecurityKey.endpointExceptions]: { [ProductFeatureSecurityKey.endpointExceptions]: {
subFeatureIds: [SecuritySubFeatureId.endpointExceptions], subFeatureIds: [SecuritySubFeatureId.endpointExceptions],
}, },
}; };

View file

@ -4,14 +4,14 @@
* 2.0; you may not use this file except in compliance with the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License
* 2.0. * 2.0.
*/ */
import { AppFeatureSecurityKey } from '@kbn/security-solution-features/keys'; import { ProductFeatureSecurityKey } from '@kbn/security-solution-features/keys';
import { import {
IndexConnectorTypeId, IndexConnectorTypeId,
SlackWebhookConnectorTypeId, SlackWebhookConnectorTypeId,
EmailConnectorTypeId, EmailConnectorTypeId,
} from '@kbn/stack-connectors-plugin/server/connector_types'; } from '@kbn/stack-connectors-plugin/server/connector_types';
import { EnabledActionTypes } from '@kbn/actions-plugin/server/config'; import { EnabledActionTypes } from '@kbn/actions-plugin/server/config';
import type { AppFeatureKeys } from '@kbn/security-solution-features/src/types'; import type { ProductFeatureKeys } from '@kbn/security-solution-features';
import type { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server'; import type { PluginSetupContract as ActionsPluginSetupContract } from '@kbn/actions-plugin/server';
@ -22,16 +22,16 @@ const INTERNAL_RULE_ACTIONS = [
]; ];
/** /**
* enable rule actions based on AppFeature Config * enable rule actions based on ProductFeature Config
*/ */
export const enableRuleActions = ({ export const enableRuleActions = ({
actions, actions,
appFeatureKeys, productFeatureKeys,
}: { }: {
actions: ActionsPluginSetupContract; actions: ActionsPluginSetupContract;
appFeatureKeys: AppFeatureKeys; productFeatureKeys: ProductFeatureKeys;
}) => { }) => {
if (appFeatureKeys.includes(AppFeatureSecurityKey.externalRuleActions)) { if (productFeatureKeys.includes(ProductFeatureSecurityKey.externalRuleActions)) {
// enables all rule actions // enables all rule actions
actions.setEnabledConnectorTypes([EnabledActionTypes.Any]); actions.setEnabledConnectorTypes([EnabledActionTypes.Any]);
} else { } else {