Added scope field to features config. (#191634)

## Summary
Kibana needs to more tightly control the set of visible features within
a space, in order to support the new solution-based navigation.
Added `scope` field to the features configuration. This enhancement is
intended to prevent new features from appearing in Space Visibility
Toggles.


### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios


__Fixes: https://github.com/elastic/kibana/issues/191299__

## Release Note

Added `scope` field to the features configuration. This enhancement is
intended to prevent new features from appearing in Space Visibility
Toggles.

---------

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Elena Shostak 2024-09-13 02:22:20 +02:00 committed by GitHub
parent 303d8f27e4
commit a71c9ba38a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
74 changed files with 491 additions and 58 deletions

View file

@ -59,6 +59,11 @@ of features within the management screens.
|See <<example-3-discover,Example 3>>
|The set of subfeatures that enables finer access control than the `all` and `read` feature privileges. These options are only available in the Gold subscription level and higher.
|`scope` (optional)
|`string[]`
|`["spaces", "security"]`
| Default `security`. Scope identifies if feature should appear in both Spaces Visibility Toggles and Security Feature Privileges or only in Security Feature Privileges.
|===
==== Privilege definition

View file

@ -12,6 +12,7 @@ import {
FeaturesPluginSetup,
// PluginStartContract as FeaturesPluginStart,
} from '@kbn/features-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { FEATURE_PRIVILEGES_PLUGIN_ID } from '../common';
export interface FeatureControlExampleDeps {
@ -27,6 +28,7 @@ export class FeatureControlsPluginExample
name: 'Feature Plugin Examples',
category: DEFAULT_APP_CATEGORIES.management,
app: ['FeaturePluginExample'],
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
privileges: {
all: {
app: ['FeaturePluginExample'],

View file

@ -17,6 +17,7 @@ import {
DEFAULT_APP_CATEGORIES,
} from '@kbn/core/server';
import { schema } from '@kbn/config-schema';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import type { AIAssistantManagementSelectionConfig } from './config';
import type {
AIAssistantManagementSelectionPluginServerDependenciesSetup,
@ -111,6 +112,7 @@ export class AIAssistantManagementSelectionPlugin
order: 8600,
app: [],
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
management: {
kibana: [
'aiAssistantManagementSelection',

View file

@ -10,6 +10,7 @@
import type { KibanaFeatureConfig } from '@kbn/features-plugin/common';
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { PLUGIN_FEATURE, PLUGIN_ID } from '../common/constants';
import { guideStateSavedObjectsType, pluginStateSavedObjectsType } from './saved_objects';
@ -19,6 +20,7 @@ export const GUIDED_ONBOARDING_FEATURE: KibanaFeatureConfig = {
defaultMessage: 'Setup guides',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [PLUGIN_ID],
privileges: {
all: {

View file

@ -12,6 +12,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { PluginSetupContract as AlertingSetup } from '@kbn/alerting-plugin/server';
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { ruleType as alwaysFiringRule } from './rule_types/always_firing';
import { ruleType as peopleInSpaceRule } from './rule_types/astros';
import { ruleType as patternRule } from './rule_types/pattern';
@ -41,6 +42,7 @@ export class AlertingExamplePlugin implements Plugin<void, void, AlertingExample
insightsAndAlerting: ['triggersActions'],
},
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
alerting: [alwaysFiringRule.id, peopleInSpaceRule.id, INDEX_THRESHOLD_ID],
privileges: {
all: {

View file

@ -8,6 +8,7 @@
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { type BaseKibanaFeatureConfig } from '../types';
import { APP_ID, ASSISTANT_FEATURE_ID } from '../constants';
@ -21,6 +22,7 @@ export const getAssistantBaseKibanaFeature = (): BaseKibanaFeatureConfig => ({
),
order: 1100,
category: DEFAULT_APP_CATEGORIES.security,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [ASSISTANT_FEATURE_ID, 'kibana'],
catalogue: [APP_ID],
minimumLicense: 'enterprise',

View file

@ -7,6 +7,7 @@
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { i18n } from '@kbn/i18n';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { APP_ID, ATTACK_DISCOVERY_FEATURE_ID } from '../constants';
import { type BaseKibanaFeatureConfig } from '../types';
@ -21,6 +22,7 @@ export const getAttackDiscoveryBaseKibanaFeature = (): BaseKibanaFeatureConfig =
),
order: 1100,
category: DEFAULT_APP_CATEGORIES.security,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [ATTACK_DISCOVERY_FEATURE_ID, 'kibana'],
catalogue: [APP_ID],
minimumLicense: 'enterprise',

View file

@ -8,6 +8,7 @@
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import type { BaseKibanaFeatureConfig } from '../types';
import { APP_ID, CASES_FEATURE_ID } from '../constants';
import type { CasesFeatureParams } from './types';
@ -27,6 +28,7 @@ export const getCasesBaseKibanaFeature = ({
),
order: 1100,
category: DEFAULT_APP_CATEGORIES.security,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [CASES_FEATURE_ID, 'kibana'],
catalogue: [APP_ID],
cases: [APP_ID],

View file

@ -6,6 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import {
@ -52,6 +53,7 @@ export const getSecurityBaseKibanaFeature = ({
),
order: 1100,
category: DEFAULT_APP_CATEGORIES.security,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [APP_ID, CLOUD_POSTURE_APP_ID, CLOUD_DEFEND_APP_ID, 'kibana'],
catalogue: [APP_ID],
management: {

View file

@ -7,6 +7,7 @@
import { DEFAULT_APP_CATEGORIES } from '@kbn/core-application-common';
import { i18n } from '@kbn/i18n';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import {
ACTION_SAVED_OBJECT_TYPE,
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
@ -28,6 +29,7 @@ export const ACTIONS_FEATURE = {
defaultMessage: 'Actions and Connectors',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [],
order: FEATURE_ORDER,
management: {

View file

@ -8,6 +8,7 @@
import { i18n } from '@kbn/i18n';
import { KibanaFeatureConfig } from '@kbn/features-plugin/common';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import {
MAINTENANCE_WINDOW_FEATURE_ID,
MAINTENANCE_WINDOW_API_PRIVILEGES,
@ -20,6 +21,7 @@ export const maintenanceWindowFeature: KibanaFeatureConfig = {
defaultMessage: 'Maintenance Windows',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [],
management: {
insightsAndAlerting: ['maintenanceWindows'],

View file

@ -16,6 +16,7 @@ test('returns rule settings feature with query delay subfeature if serverless',
label: 'Management',
order: 5000,
},
scope: ['spaces', 'security'],
id: 'rulesSettings',
management: {
insightsAndAlerting: ['triggersActions'],
@ -125,6 +126,7 @@ test('returns rule settings feature without query delay subfeature if not server
label: 'Management',
order: 5000,
},
scope: ['spaces', 'security'],
id: 'rulesSettings',
management: {
insightsAndAlerting: ['triggersActions'],

View file

@ -8,6 +8,7 @@
import { i18n } from '@kbn/i18n';
import { KibanaFeatureConfig } from '@kbn/features-plugin/common';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import {
RULES_SETTINGS_FEATURE_ID,
READ_FLAPPING_SETTINGS_SUB_FEATURE_ID,
@ -25,6 +26,7 @@ export function getRulesSettingsFeature(isServerless: boolean): KibanaFeatureCon
defaultMessage: 'Rules Settings',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [],
management: {
insightsAndAlerting: ['triggersActions'],

View file

@ -78,6 +78,10 @@ it('Provides a feature declaration ', () => {
],
},
},
"scope": Array [
"spaces",
"security",
],
"subFeatures": Array [],
}
`);
@ -152,6 +156,10 @@ it(`Calls on Reporting whether to include Generate PDF as a sub-feature`, () =>
],
},
},
"scope": Array [
"spaces",
"security",
],
"subFeatures": Array [
Object {
"name": "Reporting",

View file

@ -9,6 +9,7 @@ import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureConfig } from '@kbn/features-plugin/common';
import { ReportingStart } from '@kbn/reporting-plugin/server/types';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
/*
* Register Canvas as a Kibana feature,
@ -22,6 +23,7 @@ export function getCanvasFeature(plugins: { reporting?: ReportingStart }): Kiban
name: 'Canvas',
order: 300,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['canvas', 'kibana'],
management: {
...(includeReporting ? { insightsAndAlerting: ['reporting'] } : {}),

View file

@ -11,6 +11,7 @@ import type { KibanaFeatureConfig } from '@kbn/features-plugin/common';
import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/saved_objects';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { APP_ID, FEATURE_ID } from '../common/constants';
import { createUICapabilities, getApiTags } from '../common';
@ -32,6 +33,7 @@ export const getCasesKibanaFeature = (): KibanaFeatureConfig => {
defaultMessage: 'Cases',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [],
order: FEATURE_ORDER,
management: {

View file

@ -19,6 +19,7 @@ import {
import { CustomIntegrationsPluginSetup } from '@kbn/custom-integrations-plugin/server';
import { DataPluginStart } from '@kbn/data-plugin/server/plugin';
import { ENTERPRISE_SEARCH_APP_ID } from '@kbn/deeplinks-search';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { GlobalSearchPluginSetup } from '@kbn/global-search-plugin/server';
import type { GuidedOnboardingPluginSetup } from '@kbn/guided-onboarding-plugin/server';
@ -202,6 +203,7 @@ export class EnterpriseSearchPlugin implements Plugin {
name: SEARCH_PRODUCT_NAME,
order: 0,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana', ...PLUGIN_IDS],
catalogue: PLUGIN_IDS,
privileges: {

View file

@ -10,7 +10,7 @@ export type { FeatureKibanaPrivileges } from './feature_kibana_privileges';
export type { ElasticsearchFeatureConfig } from './elasticsearch_feature';
export { ElasticsearchFeature } from './elasticsearch_feature';
export type { KibanaFeatureConfig } from './kibana_feature';
export { KibanaFeature } from './kibana_feature';
export { KibanaFeature, KibanaFeatureScope } from './kibana_feature';
export type {
SubFeatureConfig,
SubFeaturePrivilegeConfig,

View file

@ -12,6 +12,16 @@ import { FeatureKibanaPrivileges } from './feature_kibana_privileges';
import { SubFeatureConfig, SubFeature as KibanaSubFeature } from './sub_feature';
import { ReservedKibanaPrivilege } from './reserved_kibana_privilege';
/**
* Enum for allowed feature scope values.
* security - The feature is available in Security Feature Privileges.
* spaces - The feature is available in the Spaces Visibility Toggles.
*/
export enum KibanaFeatureScope {
Security = 'security',
Spaces = 'spaces',
}
/**
* Interface for registering a feature.
* Feature registration allows plugins to hide their applications with spaces,
@ -149,6 +159,11 @@ export interface KibanaFeatureConfig {
* are visible.
*/
hidden?: boolean;
/**
* Indicates whether the feature is available in Security Feature Privileges and the Spaces Visibility Toggles.
*/
scope?: readonly KibanaFeatureScope[];
}
export class KibanaFeature {
@ -220,6 +235,10 @@ export class KibanaFeature {
return this.config.reserved;
}
public get scope() {
return this.config.scope;
}
public toRaw() {
return { ...this.config } as KibanaFeatureConfig;
}

View file

@ -14,7 +14,7 @@ export type {
SubFeatureConfig,
SubFeaturePrivilegeConfig,
} from '../common';
export { KibanaFeature } from '../common';
export { KibanaFeature, KibanaFeatureScope } from '../common';
export type { FeaturesPluginSetup, FeaturesPluginStart } from './plugin';

View file

@ -200,6 +200,25 @@ describe('FeatureRegistry', () => {
});
});
it('requires only a valid scope registered', () => {
const feature: KibanaFeatureConfig = {
id: 'test-feature',
name: 'Test Feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: null,
// @ts-expect-error
scope: ['foo', 'bar'],
};
const featureRegistry = new FeatureRegistry();
expect(() =>
featureRegistry.registerKibanaFeature(feature)
).toThrowErrorMatchingInlineSnapshot(
`"Feature test-feature has unknown scope entries: foo, bar"`
);
});
it(`requires a value for privileges`, () => {
const feature: KibanaFeatureConfig = {
id: 'test-feature',

View file

@ -15,6 +15,7 @@ import {
ElasticsearchFeatureConfig,
ElasticsearchFeature,
SubFeaturePrivilegeConfig,
KibanaFeatureScope,
} from '../common';
import { validateKibanaFeature, validateElasticsearchFeature } from './feature_schema';
import type { ConfigOverridesType } from './config';
@ -41,6 +42,10 @@ export class FeatureRegistry {
throw new Error(`Feature with id ${feature.id} is already registered.`);
}
if (!feature.scope) {
feature.scope = [KibanaFeatureScope.Security];
}
const featureCopy = cloneDeep(feature);
this.kibanaFeatures[feature.id] = applyAutomaticPrivilegeGrants(featureCopy);

View file

@ -9,7 +9,7 @@ import { schema } from '@kbn/config-schema';
import { difference } from 'lodash';
import { Capabilities as UICapabilities } from '@kbn/core/server';
import { KibanaFeatureConfig } from '../common';
import { KibanaFeatureConfig, KibanaFeatureScope } from '../common';
import { FeatureKibanaPrivileges, ElasticsearchFeatureConfig } from '.';
// Each feature gets its own property on the UICapabilities object,
@ -202,6 +202,7 @@ const kibanaFeatureSchema = schema.object({
}),
name: schema.string(),
category: appCategorySchema,
scope: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
description: schema.maybe(schema.string()),
order: schema.maybe(schema.number()),
excludeFromBasePrivileges: schema.maybe(schema.boolean()),
@ -211,13 +212,23 @@ const kibanaFeatureSchema = schema.object({
catalogue: schema.maybe(catalogueSchema),
alerting: schema.maybe(alertingSchema),
cases: schema.maybe(casesSchema),
privileges: schema.oneOf([
schema.literal(null),
schema.object({
all: schema.maybe(kibanaPrivilegeSchema),
read: schema.maybe(kibanaPrivilegeSchema),
// Features registered only for the spaces scope should not have a `privileges` property.
// Such features are applicable only to the Spaces Visibility Toggles
privileges: schema.conditional(
schema.siblingRef('scope'),
schema.arrayOf(schema.literal('spaces'), {
minSize: 1,
maxSize: 1,
}),
]),
schema.literal(null),
schema.oneOf([
schema.literal(null),
schema.object({
all: schema.maybe(kibanaPrivilegeSchema),
read: schema.maybe(kibanaPrivilegeSchema),
}),
])
),
subFeatures: schema.maybe(
schema.conditional(
schema.siblingRef('privileges'),
@ -275,6 +286,14 @@ const elasticsearchFeatureSchema = schema.object({
export function validateKibanaFeature(feature: KibanaFeatureConfig) {
kibanaFeatureSchema.validate(feature);
const unknownScopesEntries = difference(feature.scope ?? [], Object.values(KibanaFeatureScope));
if (unknownScopesEntries.length) {
throw new Error(
`Feature ${feature.id} has unknown scope entries: ${unknownScopesEntries.join(', ')}`
);
}
// the following validation can't be enforced by the Joi schema, since it'd require us looking "up" the object graph for the list of valid value, which they explicitly forbid.
const { app = [], management = {}, catalogue = [], alerting = [], cases = [] } = feature;

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureScope } from '../common';
import type { KibanaFeatureConfig, SubFeatureConfig } from '../common';
export interface BuildOSSFeaturesParams {
@ -30,6 +31,7 @@ export const buildOSSFeatures = ({
},
order: 100,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['discover', 'kibana'],
catalogue: ['discover'],
privileges: {
@ -125,6 +127,7 @@ export const buildOSSFeatures = ({
},
order: 700,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['visualize', 'lens', 'kibana'],
catalogue: ['visualize'],
privileges: {
@ -189,6 +192,7 @@ export const buildOSSFeatures = ({
},
order: 200,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['dashboards', 'kibana'],
catalogue: ['dashboard'],
privileges: {
@ -302,6 +306,7 @@ export const buildOSSFeatures = ({
}),
order: 1300,
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['dev_tools', 'kibana'],
catalogue: ['console', 'searchprofiler', 'grokdebugger'],
privileges: {
@ -338,6 +343,7 @@ export const buildOSSFeatures = ({
}),
order: 1500,
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana'],
catalogue: ['advanced_settings'],
management: {
@ -377,6 +383,7 @@ export const buildOSSFeatures = ({
}),
order: 1600,
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana'],
catalogue: ['indexPatterns'],
management: {
@ -416,6 +423,7 @@ export const buildOSSFeatures = ({
}),
order: 1600,
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana'],
catalogue: [],
management: {
@ -455,6 +463,7 @@ export const buildOSSFeatures = ({
}),
order: 1600,
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana'],
catalogue: [],
privilegesTooltip: i18n.translate('xpack.features.filesSharedImagesPrivilegesTooltip', {
@ -488,6 +497,7 @@ export const buildOSSFeatures = ({
}),
order: 1700,
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana'],
catalogue: ['saved_objects'],
management: {
@ -529,6 +539,7 @@ export const buildOSSFeatures = ({
}),
order: 1750,
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['kibana'],
catalogue: [],
privilegesTooltip: i18n.translate('xpack.features.savedQueryManagementTooltip', {

View file

@ -188,6 +188,9 @@ describe('Features Plugin', () => {
"ui": Array [],
},
},
"scope": Array [
"security",
],
},
"subFeatures": Array [],
}

View file

@ -55,6 +55,7 @@ import type { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import type { SavedObjectTaggingStart } from '@kbn/saved-objects-tagging-plugin/server';
import { SECURITY_EXTENSION_ID } from '@kbn/core-saved-objects-server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import type { FleetConfigType } from '../common/types';
import type { FleetAuthz } from '../common';
@ -317,6 +318,7 @@ export class FleetPlugin
id: `fleetv2`,
name: 'Fleet',
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [PLUGIN_ID],
catalogue: ['fleet'],
privilegesTooltip: i18n.translate('xpack.fleet.serverPlugin.privilegesTooltip', {
@ -479,6 +481,7 @@ export class FleetPlugin
id: 'fleet', // for BWC
name: 'Integrations',
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [INTEGRATIONS_PLUGIN_ID],
catalogue: ['fleet'],
privileges: {

View file

@ -12,6 +12,7 @@ import { LicensingPluginSetup, LicensingPluginStart } from '@kbn/licensing-plugi
import { HomeServerPluginSetup } from '@kbn/home-plugin/server';
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { ContentManagementServerSetup } from '@kbn/content-management-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { LicenseState } from './lib/license_state';
import { registerSearchRoute } from './routes/search';
import { registerExploreRoute } from './routes/explore';
@ -68,6 +69,7 @@ export class GraphPlugin implements Plugin {
}),
order: 600,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['graph', 'kibana'],
catalogue: ['graph'],
minimumLicense: 'platinum',

View file

@ -18,6 +18,7 @@ import { HomeServerPluginSetup } from '@kbn/home-plugin/server';
import { DataViewPersistableStateService } from '@kbn/data-views-plugin/common';
import type { EMSSettings } from '@kbn/maps-ems-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { CONTENT_ID, LATEST_VERSION } from '../common/content_management';
import { getEcommerceSavedObjects } from './sample_data/ecommerce_saved_objects';
import { getFlightsSavedObjects } from './sample_data/flights_saved_objects';
@ -175,6 +176,7 @@ export class MapsPlugin implements Plugin {
}),
order: 400,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [APP_ID, 'kibana'],
catalogue: [APP_ID],
privileges: {

View file

@ -25,6 +25,7 @@ import type { SpacesPluginSetup } from '@kbn/spaces-plugin/server';
import type { FieldFormatsStart } from '@kbn/field-formats-plugin/server';
import type { HomeServerPluginSetup } from '@kbn/home-plugin/server';
import type { CasesServerSetup } from '@kbn/cases-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import type { PluginsSetup, PluginsStart, RouteInitialization } from './types';
import type { MlCapabilities } from '../common/types/capabilities';
import { notificationsRoutes } from './routes/notifications';
@ -129,6 +130,7 @@ export class MlServerPlugin
}),
order: 500,
category: DEFAULT_APP_CATEGORIES.kibana,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [PLUGIN_ID, 'kibana'],
catalogue: [PLUGIN_ID, `${PLUGIN_ID}_file_data_visualizer`],
privilegesTooltip: i18n.translate('xpack.ml.featureRegistry.privilegesTooltip', {

View file

@ -23,6 +23,7 @@ import {
import { get } from 'lodash';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { RouteMethod } from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import {
KIBANA_MONITORING_LOGGING_TAG,
KIBANA_STATS_TYPE_MONITORING,
@ -272,6 +273,7 @@ export class MonitoringPlugin
defaultMessage: 'Stack Monitoring',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['monitoring', 'kibana'],
catalogue: ['monitoring'],
privileges: null,

View file

@ -15,6 +15,7 @@ import {
import { APM_INDEX_SETTINGS_SAVED_OBJECT_TYPE } from '@kbn/apm-data-access-plugin/server/saved_objects/apm_indices';
import { ApmRuleType } from '@kbn/rule-data-utils';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { APM_SERVER_FEATURE_ID } from '../common/rules/apm_rule_types';
const ruleTypes = Object.values(ApmRuleType);
@ -26,6 +27,7 @@ export const APM_FEATURE = {
}),
order: 900,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [APM_SERVER_FEATURE_ID, 'ux', 'kibana'],
catalogue: [APM_SERVER_FEATURE_ID],
management: {

View file

@ -14,6 +14,7 @@ import {
} from '@kbn/rule-data-utils';
import { ES_QUERY_ID } from '@kbn/rule-data-utils';
import { metricsDataSourceSavedObjectName } from '@kbn/metrics-data-access-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { LOG_DOCUMENT_COUNT_RULE_TYPE_ID } from '../common/alerting/logs/log_threshold/types';
import {
METRIC_INVENTORY_THRESHOLD_ALERT_TYPE_ID,
@ -37,6 +38,7 @@ export const METRICS_FEATURE = {
}),
order: 800,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['infra', 'metrics', 'kibana'],
catalogue: ['infraops', 'metrics'],
management: {
@ -103,6 +105,7 @@ export const LOGS_FEATURE = {
}),
order: 700,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['infra', 'logs', 'kibana'],
catalogue: ['infralogging', 'logs'],
management: {

View file

@ -37,6 +37,7 @@ import { SharePluginSetup } from '@kbn/share-plugin/server';
import { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { UsageCollectionSetup } from '@kbn/usage-collection-plugin/server';
import { DataViewsServerPluginStart } from '@kbn/data-views-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { ObservabilityConfig } from '.';
import { casesFeatureId, observabilityFeatureId } from '../common';
import {
@ -112,6 +113,7 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
}),
order: 1100,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [casesFeatureId, 'kibana'],
catalogue: [observabilityFeatureId],
cases: [observabilityFeatureId],
@ -235,6 +237,7 @@ export class ObservabilityPlugin implements Plugin<ObservabilityPluginSetup> {
}),
order: 1000,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [observabilityFeatureId],
catalogue: [observabilityFeatureId],
alerting: o11yRuleTypes,

View file

@ -20,6 +20,7 @@ import {
ACTION_TASK_PARAMS_SAVED_OBJECT_TYPE,
} from '@kbn/actions-plugin/server/constants/saved_objects';
import { firstValueFrom } from 'rxjs';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { OBSERVABILITY_AI_ASSISTANT_FEATURE_ID } from '../common/feature';
import type { ObservabilityAIAssistantConfig } from './config';
import { registerServerRoutes } from './routes/register_routes';
@ -69,6 +70,7 @@ export class ObservabilityAIAssistantPlugin
}),
order: 8600,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID, 'kibana'],
catalogue: [OBSERVABILITY_AI_ASSISTANT_FEATURE_ID],
minimumLicense: 'enterprise',

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
export const PROFILING_SERVER_FEATURE_ID = 'profiling';
@ -17,6 +18,7 @@ export const PROFILING_FEATURE = {
}),
order: 1200,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [PROFILING_SERVER_FEATURE_ID, 'ux', 'kibana'],
// see x-pack/plugins/features/common/feature_kibana_privileges.ts
privileges: {

View file

@ -33,6 +33,7 @@ import { SpacesPluginSetup, SpacesPluginStart } from '@kbn/spaces-plugin/server'
import { AlertsLocatorDefinition } from '@kbn/observability-plugin/common';
import { SLO_BURN_RATE_RULE_TYPE_ID } from '@kbn/rule-data-utils';
import { sloFeatureId } from '@kbn/observability-plugin/common';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { registerSloUsageCollector } from './lib/collectors/register';
import { SloOrphanSummaryCleanupTask } from './services/tasks/orphan_summary_cleanup_task';
import { slo, SO_SLO_TYPE } from './saved_objects';
@ -88,6 +89,7 @@ export class SloPlugin implements Plugin<SloPluginSetup> {
}),
order: 1200,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [sloFeatureId, 'kibana'],
catalogue: [sloFeatureId, 'observability'],
alerting: sloRuleTypes,

View file

@ -11,6 +11,7 @@ import {
SubFeaturePrivilegeGroupConfig,
SubFeaturePrivilegeGroupType,
} from '@kbn/features-plugin/common';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { syntheticsMonitorType, syntheticsParamType } from '../common/types/saved_objects';
import { SYNTHETICS_RULE_TYPES } from '../common/constants/synthetics_alerts';
import { privateLocationsSavedObjectName } from '../common/saved_objects/private_locations';
@ -55,6 +56,7 @@ export const syntheticsFeature = {
category: DEFAULT_APP_CATEGORIES.observability,
app: ['uptime', 'kibana', 'synthetics'],
catalogue: ['uptime'],
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
management: {
insightsAndAlerting: ['triggersActions'],
},

View file

@ -7,6 +7,7 @@
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import {
packSavedObjectType,
packAssetSavedObjectType,
@ -22,6 +23,7 @@ export const registerFeatures = (features: SetupPlugins['features']) => {
defaultMessage: 'Osquery',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [PLUGIN_ID, 'kibana'],
catalogue: [PLUGIN_ID],
order: 2300,

View file

@ -8,6 +8,7 @@
import { DEFAULT_APP_CATEGORIES, type Logger } from '@kbn/core/server';
import { i18n } from '@kbn/i18n';
import type { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
interface FeatureRegistrationOpts {
features: FeaturesPluginSetup;
@ -37,6 +38,7 @@ export function registerFeatures({
defaultMessage: 'Reporting',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [],
privileges: {
all: { savedObject: { all: [], read: [] }, ui: [] },

View file

@ -186,6 +186,7 @@ describe('Reporting Plugin', () => {
id: 'reporting',
name: 'Reporting',
category: DEFAULT_APP_CATEGORIES.management,
scope: ['spaces', 'security'],
app: [],
privileges: {
all: { savedObject: { all: [], read: [] }, ui: [] },

View file

@ -8,6 +8,7 @@
import { i18n } from '@kbn/i18n';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { KibanaFeatureConfig } from '@kbn/features-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { tagSavedObjectTypeName, tagManagementSectionId, tagFeatureId } from '../common/constants';
export const savedObjectsTaggingFeature: KibanaFeatureConfig = {
@ -16,6 +17,7 @@ export const savedObjectsTaggingFeature: KibanaFeatureConfig = {
defaultMessage: 'Tag Management',
}),
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
order: 1800,
app: [],
management: {

View file

@ -13,6 +13,7 @@ import {
Plugin,
PluginInitializerContext,
} from '@kbn/core/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { defineRoutes } from './routes';
import {
SearchInferenceEndpointsPluginSetup,
@ -56,6 +57,7 @@ export class SearchInferenceEndpointsPlugin
order: 0,
category: DEFAULT_APP_CATEGORIES.enterpriseSearch,
app: ['kibana', PLUGIN_ID],
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
catalogue: [PLUGIN_ID],
privileges: {
all: {

View file

@ -55,6 +55,10 @@ exports[`EnabledFeatures renders as expected 1`] = `
"id": "feature-1",
"name": "Feature 1",
"privileges": null,
"scope": Array [
"spaces",
"security",
],
},
Object {
"app": Array [],
@ -67,6 +71,10 @@ exports[`EnabledFeatures renders as expected 1`] = `
"id": "feature-2",
"name": "Feature 2",
"privileges": null,
"scope": Array [
"spaces",
"security",
],
},
]
}

View file

@ -9,6 +9,7 @@ import type { EuiCheckboxProps } from '@elastic/eui';
import React from 'react';
import { DEFAULT_APP_CATEGORIES } from '@kbn/core/public';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import type { KibanaFeatureConfig } from '@kbn/features-plugin/public';
import { findTestSubject, mountWithIntl, nextTick, shallowWithIntl } from '@kbn/test-jest-helpers';
@ -18,6 +19,7 @@ const features: KibanaFeatureConfig[] = [
{
id: 'feature-1',
name: 'Feature 1',
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [],
category: DEFAULT_APP_CATEGORIES.kibana,
privileges: null,
@ -25,6 +27,7 @@ const features: KibanaFeatureConfig[] = [
{
id: 'feature-2',
name: 'Feature 2',
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: [],
category: DEFAULT_APP_CATEGORIES.kibana,
privileges: null,

View file

@ -20,11 +20,13 @@ const features = [
id: 'feature_1',
name: 'Feature 1',
app: [],
scope: ['spaces', 'security'],
},
{
id: 'feature_2',
name: 'Feature 2',
app: ['feature2'],
scope: ['spaces', 'security'],
catalogue: ['feature2Entry'],
management: {
kibana: ['somethingElse'],
@ -44,6 +46,7 @@ const features = [
id: 'feature_3',
name: 'Feature 3',
app: ['feature3_app'],
scope: ['spaces', 'security'],
catalogue: ['feature3Entry'],
management: {
kibana: ['indices'],
@ -64,6 +67,7 @@ const features = [
id: 'feature_4',
name: 'Feature 4',
app: ['feature3', 'feature3_app'],
scope: ['spaces', 'security'],
catalogue: ['feature3Entry'],
management: {
kibana: ['indices'],

View file

@ -117,7 +117,7 @@ describe('Spaces plugin', () => {
const coreStart = coreMock.createStart();
const spacesStart = plugin.start(coreStart);
const spacesStart = plugin.start(coreStart, { features: featuresPluginMock.createStart() });
expect(spacesStart).toMatchInlineSnapshot(`
Object {
"hasOnlyDefaultSpace$": Observable {
@ -154,7 +154,7 @@ describe('Spaces plugin', () => {
const spacesSetup = plugin.setup(core, { features, licensing, usageCollection });
const coreStart = coreMock.createStart();
const spacesStart = plugin.start(coreStart);
const spacesStart = plugin.start(coreStart, { features: featuresPluginMock.createStart() });
await expect(firstValueFrom(spacesSetup.hasOnlyDefaultSpace$)).resolves.toEqual(true);
await expect(firstValueFrom(spacesStart.hasOnlyDefaultSpace$)).resolves.toEqual(true);
@ -172,7 +172,7 @@ describe('Spaces plugin', () => {
const spacesSetup = plugin.setup(core, { features, licensing, usageCollection });
const coreStart = coreMock.createStart();
const spacesStart = plugin.start(coreStart);
const spacesStart = plugin.start(coreStart, { features: featuresPluginMock.createStart() });
await expect(firstValueFrom(spacesSetup.hasOnlyDefaultSpace$)).resolves.toEqual(false);
await expect(firstValueFrom(spacesStart.hasOnlyDefaultSpace$)).resolves.toEqual(false);

View file

@ -232,8 +232,8 @@ export class SpacesPlugin
};
}
public start(core: CoreStart) {
const spacesClientStart = this.spacesClientService.start(core);
public start(core: CoreStart, plugins: PluginsStart) {
const spacesClientStart = this.spacesClientService.start(core, plugins.features);
this.spacesServiceStart = this.spacesService.start({
basePath: core.http.basePath,

View file

@ -17,6 +17,7 @@ import {
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { SPACES_EXTENSION_ID } from '@kbn/core-saved-objects-server';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initCopyToSpacesApi } from './copy_to_space';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -72,7 +73,7 @@ describe('copy to space', () => {
usageStatsServiceMock.createSetupContract(usageStatsClient)
);
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -16,6 +16,7 @@ import {
httpServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initDeleteSpacesApi } from './delete';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -54,7 +55,7 @@ describe('Spaces Public API', () => {
const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract());
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -15,6 +15,7 @@ import {
httpServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initDisableLegacyUrlAliasesApi } from './disable_legacy_url_aliases';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -57,7 +58,7 @@ describe('_disable_legacy_url_aliases', () => {
usageStatsServiceMock.createSetupContract(usageStatsClient)
);
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -14,6 +14,7 @@ import {
httpServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initGetSpaceApi } from './get';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -53,7 +54,7 @@ describe('GET space', () => {
const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract());
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -16,6 +16,7 @@ import {
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { getRequestValidation } from '@kbn/core-http-server';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initGetAllSpacesApi } from './get_all';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -55,7 +56,7 @@ describe('GET /spaces/space', () => {
const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract());
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -15,6 +15,7 @@ import {
httpServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initGetShareableReferencesApi } from './get_shareable_references';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -54,7 +55,7 @@ describe('get shareable references', () => {
const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract());
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -16,6 +16,7 @@ import {
httpServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initPostSpacesApi } from './post';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -52,9 +53,13 @@ describe('Spaces Public API', () => {
basePath: httpService.basePath,
});
const featuresPluginMockStart = featuresPluginMock.createStart();
featuresPluginMockStart.getKibanaFeatures.mockReturnValue([]);
const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract());
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMockStart);
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -16,6 +16,7 @@ import {
httpServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initPutSpacesApi } from './put';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -52,9 +53,13 @@ describe('PUT /api/spaces/space', () => {
basePath: httpService.basePath,
});
const featuresPluginMockStart = featuresPluginMock.createStart();
featuresPluginMockStart.getKibanaFeatures.mockReturnValue([]);
const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract());
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMockStart);
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -16,6 +16,7 @@ import {
httpServiceMock,
loggingSystemMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initUpdateObjectsSpacesApi } from './update_objects_spaces';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -55,7 +56,7 @@ describe('update_objects_spaces', () => {
const usageStatsServicePromise = Promise.resolve(usageStatsServiceMock.createSetupContract());
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -17,6 +17,7 @@ import {
savedObjectsClientMock,
savedObjectsTypeRegistryMock,
} from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { SpaceContentTypeSummaryItem } from './get_content_summary';
import { initGetSpaceContentSummaryApi } from './get_content_summary';
@ -81,7 +82,7 @@ describe('GET /internal/spaces/{spaceId}/content_summary', () => {
basePath: httpService.basePath,
});
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -10,6 +10,7 @@ import * as Rx from 'rxjs';
import type { RouteValidatorConfig } from '@kbn/core/server';
import { kibanaResponseFactory } from '@kbn/core/server';
import { coreMock, httpServerMock, httpServiceMock } from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { initSetSolutionSpaceApi } from './set_solution_space';
import { spacesConfig } from '../../../lib/__fixtures__';
@ -43,7 +44,7 @@ describe('PUT /internal/spaces/space/{id}/solution', () => {
basePath: httpService.basePath,
});
const clientServiceStart = clientService.start(coreStart);
const clientServiceStart = clientService.start(coreStart, featuresPluginMock.createStart());
const spacesServiceStart = service.start({
basePath: coreStart.http.basePath,

View file

@ -7,6 +7,9 @@
import { savedObjectsRepositoryMock } from '@kbn/core/server/mocks';
import type { SavedObject } from '@kbn/core-saved-objects-server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { KibanaFeature } from '@kbn/features-plugin/server';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { SpacesClient } from './spaces_client';
import type { GetAllSpacesPurpose, Space } from '../../common';
@ -113,7 +116,8 @@ describe('#getAll', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const actualSpaces = await client.getAll();
@ -140,7 +144,8 @@ describe('#getAll', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'serverless'
'serverless',
featuresPluginMock.createStart()
);
const [actualSpace] = await client.getAll();
const [{ solution, ...expectedSpace }] = expectedSpaces;
@ -164,7 +169,8 @@ describe('#getAll', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
await expect(
client.getAll({ purpose: 'invalid_purpose' as GetAllSpacesPurpose })
@ -211,7 +217,8 @@ describe('#get', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
const actualSpace = await client.get(id);
@ -234,7 +241,8 @@ describe('#get', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'serverless'
'serverless',
featuresPluginMock.createStart()
);
const id = savedObject.id;
const actualSpace = await client.get(id);
@ -257,7 +265,8 @@ describe('#get', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
const actualSpace = await client.get(id);
@ -320,7 +329,8 @@ describe('#create', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const actualSpace = await client.create(spaceToCreate);
@ -336,6 +346,60 @@ describe('#create', () => {
});
});
test(`throws bad request when creating space with disabled features`, async () => {
const maxSpaces = 5;
const mockDebugLogger = createMockDebugLogger();
const mockConfig = createMockConfig();
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
mockCallWithRequestRepository.create.mockResolvedValue(savedObject);
mockCallWithRequestRepository.find.mockResolvedValue({
total: maxSpaces - 1,
} as any);
const featuresMock = featuresPluginMock.createStart();
featuresMock.getKibanaFeatures.mockReturnValue([
new KibanaFeature({
id: 'feature-1',
name: 'KibanaFeature',
app: [],
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Security],
privileges: {
all: {
savedObject: {
all: [],
read: [],
},
ui: ['foo'],
},
read: {
savedObject: {
all: [],
read: [],
},
ui: ['foo'],
},
},
subFeatures: [],
}),
]);
const client = new SpacesClient(
mockDebugLogger,
mockConfig,
mockCallWithRequestRepository,
[],
'traditional',
featuresMock
);
await expect(
client.create({ ...spaceToCreate, disabledFeatures: ['feature-1'] })
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Unable to create Space, one or more disabledFeatures do not have the required space scope"`
);
});
test(`throws bad request when we are at the maximum number of spaces`, async () => {
const maxSpaces = 5;
const mockDebugLogger = createMockDebugLogger();
@ -357,7 +421,8 @@ describe('#create', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
await expect(client.create(spaceToCreate)).rejects.toThrowErrorMatchingInlineSnapshot(
@ -393,7 +458,8 @@ describe('#create', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'serverless'
'serverless',
featuresPluginMock.createStart()
);
await expect(
@ -440,7 +506,8 @@ describe('#create', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const actualSpace = await client.create({ ...spaceToCreate, solution: 'es' });
@ -483,7 +550,8 @@ describe('#create', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const actualSpace = await client.create(spaceToCreate);
@ -520,7 +588,8 @@ describe('#create', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
await expect(
@ -560,7 +629,8 @@ describe('#create', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
await expect(
@ -624,7 +694,8 @@ describe('#update', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
const actualSpace = await client.update(id, spaceToUpdate);
@ -634,6 +705,56 @@ describe('#update', () => {
expect(mockCallWithRequestRepository.get).toHaveBeenCalledWith('space', id);
});
test(`throws bad request when creating space with disabled features`, async () => {
const mockDebugLogger = createMockDebugLogger();
const mockConfig = createMockConfig();
const mockCallWithRequestRepository = savedObjectsRepositoryMock.create();
mockCallWithRequestRepository.get.mockResolvedValue(savedObject);
const featuresMock = featuresPluginMock.createStart();
featuresMock.getKibanaFeatures.mockReturnValue([
new KibanaFeature({
id: 'feature-1',
name: 'KibanaFeature',
app: [],
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Security],
privileges: {
all: {
savedObject: {
all: [],
read: [],
},
ui: ['foo'],
},
read: {
savedObject: {
all: [],
read: [],
},
ui: ['foo'],
},
},
subFeatures: [],
}),
]);
const client = new SpacesClient(
mockDebugLogger,
mockConfig,
mockCallWithRequestRepository,
[],
'traditional',
featuresMock
);
await expect(
client.create({ ...spaceToUpdate, disabledFeatures: ['feature-1'] })
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Cannot destructure property 'total' of '(intermediate value)' as it is undefined."`
);
});
test('throws bad request when solution property is provided in serverless build', async () => {
const mockDebugLogger = createMockDebugLogger();
const mockConfig = createMockConfig();
@ -645,7 +766,8 @@ describe('#update', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'serverless'
'serverless',
featuresPluginMock.createStart()
);
const id = savedObject.id;
@ -677,7 +799,8 @@ describe('#update', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
@ -703,7 +826,8 @@ describe('#update', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
await client.update(id, { ...spaceToUpdate, solution: 'es' });
@ -732,7 +856,8 @@ describe('#update', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
const actualSpace = await client.update(id, spaceToUpdate);
@ -758,7 +883,8 @@ describe('#update', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
@ -790,7 +916,8 @@ describe('#update', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const id = savedObject.id;
@ -843,7 +970,8 @@ describe('#delete', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
await expect(client.delete(id)).rejects.toThrowErrorMatchingInlineSnapshot(
@ -864,7 +992,8 @@ describe('#delete', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
await client.delete(id);
@ -886,7 +1015,8 @@ describe('#disableLegacyUrlAliases', () => {
mockConfig,
mockCallWithRequestRepository,
[],
'traditional'
'traditional',
featuresPluginMock.createStart()
);
const aliases = [
{ targetSpace: 'space1', targetType: 'foo', sourceId: '123' },

View file

@ -14,6 +14,8 @@ import type {
SavedObject,
} from '@kbn/core/server';
import type { LegacyUrlAliasTarget } from '@kbn/core-saved-objects-common';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import type { FeaturesPluginStart } from '@kbn/features-plugin/server';
import { isReservedSpace } from '../../common';
import type { spaceV1 as v1 } from '../../common';
@ -88,7 +90,8 @@ export class SpacesClient implements ISpacesClient {
private readonly config: ConfigType,
private readonly repository: ISavedObjectsRepository,
private readonly nonGlobalTypeNames: string[],
private readonly buildFlavour: BuildFlavor
private readonly buildFlavour: BuildFlavor,
private readonly features: FeaturesPluginStart
) {
this.isServerless = this.buildFlavour === 'serverless';
}
@ -150,6 +153,8 @@ export class SpacesClient implements ISpacesClient {
throw Boom.badRequest('Unable to create Space, solution property cannot be empty');
}
this.validateDisabledFeatures(space);
this.debugLogger(`SpacesClient.create(), using RBAC. Attempting to create space`);
const id = space.id;
@ -183,6 +188,8 @@ export class SpacesClient implements ISpacesClient {
throw Boom.badRequest('Unable to update Space, solution property cannot be empty');
}
this.validateDisabledFeatures(space);
const attributes = this.generateSpaceAttributes(space);
await this.repository.update('space', id, attributes);
const updatedSavedObject = await this.repository.get('space', id);
@ -216,6 +223,28 @@ export class SpacesClient implements ISpacesClient {
await this.repository.bulkUpdate(objectsToUpdate);
}
private validateDisabledFeatures = (space: v1.Space) => {
if (!space.disabledFeatures.length || this.isServerless) {
return;
}
const kibanaFeatures = this.features.getKibanaFeatures();
if (
space.disabledFeatures.some((feature) => {
const disabledKibanaFeature = kibanaFeatures.find((f) => f.id === feature);
return (
disabledKibanaFeature && !disabledKibanaFeature.scope?.includes(KibanaFeatureScope.Spaces)
);
})
) {
throw Boom.badRequest(
'Unable to create Space, one or more disabledFeatures do not have the required space scope'
);
}
};
private transformSavedObjectToSpace = (savedObject: SavedObject<any>): v1.Space => {
return {
id: savedObject.id,

View file

@ -8,6 +8,7 @@
import * as Rx from 'rxjs';
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import type { ISpacesClient } from './spaces_client';
import { SpacesClient } from './spaces_client';
@ -49,7 +50,7 @@ describe('SpacesClientService', () => {
const service = new SpacesClientService(debugLogger, 'traditional');
service.setup({ config$: new Rx.Observable<ConfigType>() });
const coreStart = coreMock.createStart();
const start = service.start(coreStart);
const start = service.start(coreStart, featuresPluginMock.createStart());
const request = httpServerMock.createKibanaRequest();
@ -64,7 +65,7 @@ describe('SpacesClientService', () => {
service.setup({ config$: Rx.of(spacesConfig) });
const coreStart = coreMock.createStart();
const start = service.start(coreStart);
const start = service.start(coreStart, featuresPluginMock.createStart());
const request = httpServerMock.createKibanaRequest();
const client = start.createSpacesClient(request);
@ -85,7 +86,7 @@ describe('SpacesClientService', () => {
setup.setClientRepositoryFactory(customRepositoryFactory);
const coreStart = coreMock.createStart();
const start = service.start(coreStart);
const start = service.start(coreStart, featuresPluginMock.createStart());
const request = httpServerMock.createKibanaRequest();
const client = start.createSpacesClient(request);
@ -107,7 +108,7 @@ describe('SpacesClientService', () => {
setup.registerClientWrapper(clientWrapper);
const coreStart = coreMock.createStart();
const start = service.start(coreStart);
const start = service.start(coreStart, featuresPluginMock.createStart());
const request = httpServerMock.createKibanaRequest();
const client = start.createSpacesClient(request);
@ -135,7 +136,7 @@ describe('SpacesClientService', () => {
setup.registerClientWrapper(clientWrapper);
const coreStart = coreMock.createStart();
const start = service.start(coreStart);
const start = service.start(coreStart, featuresPluginMock.createStart());
const request = httpServerMock.createKibanaRequest();
const client = start.createSpacesClient(request);

View file

@ -14,6 +14,7 @@ import type {
KibanaRequest,
SavedObjectsServiceStart,
} from '@kbn/core/server';
import type { FeaturesPluginStart } from '@kbn/features-plugin/server';
import type { ISpacesClient } from './spaces_client';
import { SpacesClient } from './spaces_client';
@ -99,7 +100,7 @@ export class SpacesClientService {
};
}
public start(coreStart: CoreStart): SpacesClientServiceStart {
public start(coreStart: CoreStart, features: FeaturesPluginStart): SpacesClientServiceStart {
const nonGlobalTypes = coreStart.savedObjects
.getTypeRegistry()
.getAllTypes()
@ -122,7 +123,8 @@ export class SpacesClientService {
this.config,
this.repositoryFactory!(request, coreStart.savedObjects),
nonGlobalTypeNames,
this.buildFlavour
this.buildFlavour,
features
);
if (this.clientWrapper) {
return this.clientWrapper(request, baseClient);

View file

@ -10,13 +10,13 @@ import * as Rx from 'rxjs';
import type { HttpServiceSetup, KibanaRequest, SavedObjectsRepository } from '@kbn/core/server';
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
import { featuresPluginMock } from '@kbn/features-plugin/server/mocks';
import { SpacesService } from './spaces_service';
import { DEFAULT_SPACE_ID } from '../../common/constants';
import { getSpaceIdFromPath } from '../../common/lib/spaces_url_parser';
import { spacesConfig } from '../lib/__fixtures__';
import { SpacesClientService } from '../spaces_client';
const createService = (serverBasePath: string = '') => {
const spacesService = new SpacesService();
@ -74,7 +74,10 @@ const createService = (serverBasePath: string = '') => {
config$: Rx.of(spacesConfig),
});
const spacesClientServiceStart = spacesClientService.start(coreStart);
const spacesClientServiceStart = spacesClientService.start(
coreStart,
featuresPluginMock.createStart()
);
const spacesServiceStart = spacesService.start({
basePath: coreStart.http.basePath,

View file

@ -11,6 +11,7 @@ import { DEFAULT_APP_CATEGORIES } from '@kbn/core/server';
import { TRANSFORM_RULE_TYPE } from '@kbn/transform-plugin/common';
import { STACK_ALERTS_FEATURE_ID } from '@kbn/rule-data-utils';
import { ES_QUERY_ID as ElasticsearchQuery } from '@kbn/rule-data-utils';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { ID as IndexThreshold } from './rule_types/index_threshold/rule_type';
import { GEO_CONTAINMENT_ID as GeoContainment } from './rule_types/geo_containment';
@ -23,6 +24,7 @@ export const BUILT_IN_ALERTS_FEATURE: KibanaFeatureConfig = {
}),
app: [],
category: DEFAULT_APP_CATEGORIES.management,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
management: {
insightsAndAlerting: ['triggersActions'],
},

View file

@ -16,6 +16,7 @@ import {
PluginStartContract as ActionsPluginStartContract,
} from '@kbn/actions-plugin/server/plugin';
import { ActionType } from '@kbn/actions-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { initPlugin as initPagerduty } from './pagerduty_simulation';
import { initPlugin as initSwimlane } from './swimlane_simulation';
import { initPlugin as initServiceNow } from './servicenow_simulation';
@ -127,6 +128,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'actionsSimulators',
app: ['actions', 'kibana'],
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
privileges: {
all: {
app: ['actions', 'kibana'],

View file

@ -25,6 +25,7 @@ import { RuleRegistryPluginSetupContract } from '@kbn/rule-registry-plugin/serve
import { IEventLogClientService } from '@kbn/event-log-plugin/server';
import { NotificationsPluginStart } from '@kbn/notifications-plugin/server';
import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { defineRoutes } from './routes';
import { defineActionTypes } from './action_types';
import { defineRuleTypes } from './rule_types';
@ -71,6 +72,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'Alerts',
app: ['alerts', 'kibana'],
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
alerting: [
'test.always-firing',
'test.cumulative-firing',

View file

@ -11,6 +11,7 @@ import { PluginSetupContract as AlertingPluginSetup } from '@kbn/alerting-plugin
import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-plugin/server';
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { RULE_SAVED_OBJECT_TYPE } from '@kbn/alerting-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { defineAlertTypes } from './alert_types';
export interface FixtureSetupDeps {
@ -30,6 +31,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'AlertRestricted',
app: ['alerts', 'kibana'],
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
alerting: ['test.restricted-noop', 'test.unrestricted-noop', 'test.noop'],
privileges: {
all: {

View file

@ -7,6 +7,7 @@
import expect from '@kbn/expect';
import { KibanaFeature } from '@kbn/features-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
@ -136,6 +137,63 @@ export default function ({ getService }: FtrProviderContext) {
].sort()
);
});
it('should return a full feature set with correct scope', async () => {
const { body } = await supertest.get('/api/features').expect(200);
expect(body).to.be.an(Array);
const scopeAgnosticFeatures = [
'discover',
'visualize',
'dashboard',
'dev_tools',
'actions',
'enterpriseSearch',
'filesManagement',
'filesSharedImage',
'advancedSettings',
'aiAssistantManagementSelection',
'indexPatterns',
'graph',
'guidedOnboardingFeature',
'monitoring',
'observabilityAIAssistant',
'observabilityCases',
'savedObjectsManagement',
'savedQueryManagement',
'savedObjectsTagging',
'ml',
'apm',
'stackAlerts',
'canvas',
'generalCases',
'infrastructure',
'logs',
'maintenanceWindow',
'maps',
'osquery',
'rulesSettings',
'uptime',
'searchInferenceEndpoints',
'siem',
'slo',
'securitySolutionAssistant',
'securitySolutionAttackDiscovery',
'securitySolutionCases',
'fleet',
'fleetv2',
];
const features = body.filter(
(f: KibanaFeature) =>
f.scope?.includes(KibanaFeatureScope.Spaces) &&
f.scope?.includes(KibanaFeatureScope.Security)
);
expect(features.every((f: KibanaFeature) => scopeAgnosticFeatures.includes(f.id))).to.be(
true
);
});
});
});
}

View file

@ -12,6 +12,7 @@ import { SecurityPluginStart } from '@kbn/security-plugin/server';
import type { CasesServerStart, CasesServerSetup } from '@kbn/cases-plugin/server';
import { FilesSetup } from '@kbn/files-plugin/server';
import { PluginStartContract as ActionsPluginsStart } from '@kbn/actions-plugin/server/plugin';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { getPersistableStateAttachment } from './attachments/persistable_state';
import { getExternalReferenceAttachment } from './attachments/external_reference';
import { registerRoutes } from './routes';
@ -52,6 +53,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'TestNoCasesConnectorFixture',
app: ['kibana'],
category: { id: 'cases-fixtures', label: 'Cases Fixtures' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
cases: ['testNoCasesConnectorFixture'],
privileges: {
all: {

View file

@ -10,6 +10,7 @@ import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/s
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { SecurityPluginStart } from '@kbn/security-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
export interface FixtureSetupDeps {
features: FeaturesPluginSetup;
@ -28,6 +29,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'ObservabilityFixture',
app: ['kibana'],
category: { id: 'cases-fixtures', label: 'Cases Fixtures' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
cases: ['observabilityFixture'],
privileges: {
all: {

View file

@ -10,6 +10,7 @@ import { hiddenTypes as filesSavedObjectTypes } from '@kbn/files-plugin/server/s
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { SecurityPluginStart } from '@kbn/security-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
export interface FixtureSetupDeps {
features: FeaturesPluginSetup;
@ -36,6 +37,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'SecuritySolutionFixture',
app: ['kibana'],
category: { id: 'cases-fixtures', label: 'Cases Fixtures' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
cases: ['securitySolutionFixture'],
privileges: {
all: {
@ -121,6 +123,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'TestDisabledFixture',
app: ['kibana'],
category: { id: 'cases-fixtures', label: 'Cases Fixtures' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
// testDisabledFixture is disabled in space1
cases: ['testDisabledFixture'],
privileges: {

View file

@ -12,6 +12,7 @@ import { EncryptedSavedObjectsPluginStart } from '@kbn/encrypted-saved-objects-p
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { SpacesPluginStart } from '@kbn/spaces-plugin/server';
import { SecurityPluginStart } from '@kbn/security-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
export interface FixtureSetupDeps {
features: FeaturesPluginSetup;
@ -33,6 +34,7 @@ export class FixturePlugin implements Plugin<void, void, FixtureSetupDeps, Fixtu
name: 'Alerts',
app: ['alerts', 'kibana'],
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
alerting: ['test.executionContext'],
privileges: {
all: {

View file

@ -12,6 +12,7 @@ import {
RuleTypeParams,
} from '@kbn/alerting-plugin/server';
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
// this plugin's dependendencies
export interface AlertingExampleDeps {
@ -116,6 +117,7 @@ export class AlertingFixturePlugin implements Plugin<void, void, AlertingExample
name: 'alerting_fixture',
app: [],
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
alerting: ['test.always-firing', 'test.noop', 'test.failing'],
privileges: {
all: {

View file

@ -7,6 +7,7 @@
import { CoreSetup, Plugin } from '@kbn/core/server';
import { FeaturesPluginSetup } from '@kbn/features-plugin/server';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
export const plugin = async () => new FooPlugin();
@ -20,6 +21,7 @@ class FooPlugin implements Plugin {
id: 'foo',
name: 'Foo',
category: { id: 'foo', label: 'foo' },
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['foo_plugin', 'kibana'],
catalogue: ['foo'],
privileges: {