Skip to content

Commit 50d9838

Browse files
darnautovtsullivan
andauthored
[9.1] Add reporting_user feature for reserved set of privileges (#231533) (#232384)
# Backport This will backport the following commits from `main` to `9.1`: - [Add `reporting_user` feature for reserved set of privileges (#231533)](#231533) <!--- Backport version: 10.0.1 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sorenlouv/backport) <!--BACKPORT [{"author":{"name":"Tim Sullivan","email":"[email protected]"},"sourceCommit":{"committedDate":"2025-08-20T11:57:52Z","message":"Add `reporting_user` feature for reserved set of privileges (#231533)\n\n## Summary\n\nWe want to switch the reserved `reporting_user` role to use a \"reserved\nprivilege definition\" and uses just that privilege. This PR satisfies\nthe Kibana requirements. There is a corresponding Elasticsearch PR:\nhttps://github.com/elastic/elasticsearch/pull/132766\n\n## Testing\n**NOTE: PNG/PDF reporting requires a Trial, or Gold+ license**\n\n1. Create `test_reporting_user` role\n\n ```\n POST /_security/role/test_reporting_user\n {\n \"cluster\": [],\n \"indices\": [],\n \"application\": [{\n \"application\": \"kibana-*\",\n \"privileges\": [\"reserved_reporting_user\"],\n \"resources\": [\"*\"]\n }]\n }\n ```\n\n2. Create `test_analyst_user` role\n\n ```\n POST /_security/role/test_analyst_user\n {\n \"cluster\": [],\n \"indices\": [\n {\n \"names\": [\"kibana_sample_*\"],\n \"privileges\": [\"all\"],\n \"field_security\": {\n \"grant\": [\"*\"],\n \"except\": []\n },\n \"allow_restricted_indices\": false\n }\n ],\n \"applications\": [\n {\n \"application\": \"kibana-.kibana\",\n \"privileges\": [\n \"feature_discover_v2.read\",\n \"feature_dashboard_v2.read\",\n \"feature_canvas.read\",\n \"feature_visualize_v2.read\"\n ],\n \"resources\": [\"space:default\"]\n }\n ],\n \"run_as\": [],\n \"metadata\": {},\n \"transient_metadata\": {\n \"enabled\": true\n }\n }\n ```\n\n3. Create a test user with just those two roles. Install sample data.\nLog in using the new test user.\n4. Test cases\n\n | App | Reporting feature\n |-|-\n | Dashboard | PDF, PNG, CSV (from saved search panel action)\n | Discover | CSV\n | Canvas | PDF\n | Lens | PDF, PNG\n| Stack Management | List reports, download reports, view report info,\ndelete reports\n\n6. As admin, create an additional Space which the test user should not\nhave access to. Ensure the test user does not have access to those\nspaces.\n7. Remove the `test_reporting_user` role from the user and ensure they\ndo not see any Reporting controls in the UI, and can not access Stack\nManagement > Reporting.\n\n## Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- ~~[ ] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)~~\n- ~~[ ]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials~~\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- ~~[ ] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~~\n- ~~[ ] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.~~\n- ~~[ ] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed~~\n- [ ] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n- [x] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\n---------\n\nCo-authored-by: Larry Gregory <[email protected]>","sha":"f9be58be65e59b85dc6c4d8fa74970a4f8c1971e","branchLabelMapping":{"^v9.2.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:version","v9.2.0","v9.1.3","v9.0.6"],"title":"Add `reporting_user` feature for reserved set of privileges","number":231533,"url":"https://github.com/elastic/kibana/pull/231533","mergeCommit":{"message":"Add `reporting_user` feature for reserved set of privileges (#231533)\n\n## Summary\n\nWe want to switch the reserved `reporting_user` role to use a \"reserved\nprivilege definition\" and uses just that privilege. This PR satisfies\nthe Kibana requirements. There is a corresponding Elasticsearch PR:\nhttps://github.com/elastic/elasticsearch/pull/132766\n\n## Testing\n**NOTE: PNG/PDF reporting requires a Trial, or Gold+ license**\n\n1. Create `test_reporting_user` role\n\n ```\n POST /_security/role/test_reporting_user\n {\n \"cluster\": [],\n \"indices\": [],\n \"application\": [{\n \"application\": \"kibana-*\",\n \"privileges\": [\"reserved_reporting_user\"],\n \"resources\": [\"*\"]\n }]\n }\n ```\n\n2. Create `test_analyst_user` role\n\n ```\n POST /_security/role/test_analyst_user\n {\n \"cluster\": [],\n \"indices\": [\n {\n \"names\": [\"kibana_sample_*\"],\n \"privileges\": [\"all\"],\n \"field_security\": {\n \"grant\": [\"*\"],\n \"except\": []\n },\n \"allow_restricted_indices\": false\n }\n ],\n \"applications\": [\n {\n \"application\": \"kibana-.kibana\",\n \"privileges\": [\n \"feature_discover_v2.read\",\n \"feature_dashboard_v2.read\",\n \"feature_canvas.read\",\n \"feature_visualize_v2.read\"\n ],\n \"resources\": [\"space:default\"]\n }\n ],\n \"run_as\": [],\n \"metadata\": {},\n \"transient_metadata\": {\n \"enabled\": true\n }\n }\n ```\n\n3. Create a test user with just those two roles. Install sample data.\nLog in using the new test user.\n4. Test cases\n\n | App | Reporting feature\n |-|-\n | Dashboard | PDF, PNG, CSV (from saved search panel action)\n | Discover | CSV\n | Canvas | PDF\n | Lens | PDF, PNG\n| Stack Management | List reports, download reports, view report info,\ndelete reports\n\n6. As admin, create an additional Space which the test user should not\nhave access to. Ensure the test user does not have access to those\nspaces.\n7. Remove the `test_reporting_user` role from the user and ensure they\ndo not see any Reporting controls in the UI, and can not access Stack\nManagement > Reporting.\n\n## Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- ~~[ ] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)~~\n- ~~[ ]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials~~\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- ~~[ ] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~~\n- ~~[ ] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.~~\n- ~~[ ] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed~~\n- [ ] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n- [x] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\n---------\n\nCo-authored-by: Larry Gregory <[email protected]>","sha":"f9be58be65e59b85dc6c4d8fa74970a4f8c1971e"}},"sourceBranch":"main","suggestedTargetBranches":["9.1","9.0"],"targetPullRequestStates":[{"branch":"main","label":"v9.2.0","branchLabelMappingKey":"^v9.2.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/231533","number":231533,"mergeCommit":{"message":"Add `reporting_user` feature for reserved set of privileges (#231533)\n\n## Summary\n\nWe want to switch the reserved `reporting_user` role to use a \"reserved\nprivilege definition\" and uses just that privilege. This PR satisfies\nthe Kibana requirements. There is a corresponding Elasticsearch PR:\nhttps://github.com/elastic/elasticsearch/pull/132766\n\n## Testing\n**NOTE: PNG/PDF reporting requires a Trial, or Gold+ license**\n\n1. Create `test_reporting_user` role\n\n ```\n POST /_security/role/test_reporting_user\n {\n \"cluster\": [],\n \"indices\": [],\n \"application\": [{\n \"application\": \"kibana-*\",\n \"privileges\": [\"reserved_reporting_user\"],\n \"resources\": [\"*\"]\n }]\n }\n ```\n\n2. Create `test_analyst_user` role\n\n ```\n POST /_security/role/test_analyst_user\n {\n \"cluster\": [],\n \"indices\": [\n {\n \"names\": [\"kibana_sample_*\"],\n \"privileges\": [\"all\"],\n \"field_security\": {\n \"grant\": [\"*\"],\n \"except\": []\n },\n \"allow_restricted_indices\": false\n }\n ],\n \"applications\": [\n {\n \"application\": \"kibana-.kibana\",\n \"privileges\": [\n \"feature_discover_v2.read\",\n \"feature_dashboard_v2.read\",\n \"feature_canvas.read\",\n \"feature_visualize_v2.read\"\n ],\n \"resources\": [\"space:default\"]\n }\n ],\n \"run_as\": [],\n \"metadata\": {},\n \"transient_metadata\": {\n \"enabled\": true\n }\n }\n ```\n\n3. Create a test user with just those two roles. Install sample data.\nLog in using the new test user.\n4. Test cases\n\n | App | Reporting feature\n |-|-\n | Dashboard | PDF, PNG, CSV (from saved search panel action)\n | Discover | CSV\n | Canvas | PDF\n | Lens | PDF, PNG\n| Stack Management | List reports, download reports, view report info,\ndelete reports\n\n6. As admin, create an additional Space which the test user should not\nhave access to. Ensure the test user does not have access to those\nspaces.\n7. Remove the `test_reporting_user` role from the user and ensure they\ndo not see any Reporting controls in the UI, and can not access Stack\nManagement > Reporting.\n\n## Checklist\n\nCheck the PR satisfies following conditions. \n\nReviewers should verify this PR satisfies this list as well.\n\n- ~~[ ] Any text added follows [EUI's writing\nguidelines](https://elastic.github.io/eui/#/guidelines/writing), uses\nsentence case text and includes [i18n\nsupport](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)~~\n- ~~[ ]\n[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)\nwas added for features that require explanation or tutorials~~\n- [x] [Unit or functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere updated or added to match the most common scenarios\n- ~~[ ] If a plugin configuration key changed, check if it needs to be\nallowlisted in the cloud and added to the [docker\nlist](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)~~\n- ~~[ ] This was checked for breaking HTTP API changes, and any breaking\nchanges have been approved by the breaking-change committee. The\n`release_note:breaking` label should be applied in these situations.~~\n- ~~[ ] [Flaky Test\nRunner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was\nused on any tests changed~~\n- [ ] The PR description includes the appropriate Release Notes section,\nand the correct `release_note:*` label is applied per the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n- [x] Review the [backport\nguidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)\nand apply applicable `backport:*` labels.\n\n---------\n\nCo-authored-by: Larry Gregory <[email protected]>","sha":"f9be58be65e59b85dc6c4d8fa74970a4f8c1971e"}},{"branch":"9.1","label":"v9.1.3","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"9.0","label":"v9.0.6","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}] BACKPORT--> Co-authored-by: Tim Sullivan <[email protected]>
1 parent b9ba1ee commit 50d9838

File tree

23 files changed

+666
-75
lines changed

23 files changed

+666
-75
lines changed

src/platform/packages/private/kbn-reporting/get_csv_panel_actions/panel_actions/get_csv_panel_action.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,11 @@ export class ReportingCsvPanelAction implements ActionDefinition<EmbeddableApiCo
148148
const license = await firstValueFrom(licensing.license$);
149149
const licenseHasCsvReporting = checkLicense(license.check('reporting', 'basic')).showLinks;
150150

151+
const capabilities = application.capabilities;
151152
// NOTE: For historical reasons capability identifier is called `downloadCsv. It can not be renamed.
152-
const capabilityHasCsvReporting = application.capabilities.dashboard_v2?.downloadCsv === true;
153+
const capabilityHasCsvReporting =
154+
capabilities.dashboard_v2?.downloadCsv === true ||
155+
capabilities.reportingLegacy?.generateReport === true;
153156
if (!licenseHasCsvReporting || !capabilityHasCsvReporting) {
154157
return false;
155158
}

src/platform/packages/private/kbn-reporting/public/share/share_context_menu/register_csv_modal_reporting.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,9 @@ export const reportingCsvExportProvider = ({
183183

184184
const licenseHasCsvReporting = licenseCheck.showLinks;
185185

186-
const capabilityHasCsvReporting = capabilities.discover_v2?.generateCsv === true;
186+
const capabilityHasCsvReporting =
187+
capabilities.discover_v2?.generateCsv === true ||
188+
capabilities.reportingLegacy?.generateReport === true;
187189

188190
if (!(licenseHasCsvReporting && capabilityHasCsvReporting)) {
189191
return false;

src/platform/packages/private/kbn-reporting/public/share/share_context_menu/register_pdf_png_modal_reporting.tsx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
import { i18n } from '@kbn/i18n';
1111
import { FormattedMessage } from '@kbn/i18n-react';
12+
import type { Capabilities } from '@kbn/core/public';
1213
import { toMountPoint } from '@kbn/react-kibana-mount';
1314
import { ShareContext } from '@kbn/share-plugin/public';
1415
import React from 'react';
@@ -79,6 +80,13 @@ const getJobParams = (opts: JobParamsProviderOptions, type: 'pngV2' | 'printable
7980
};
8081
};
8182

83+
const hasCapabilityByKey = (capabilities: Capabilities, capabilityKey: keyof Capabilities) => {
84+
return (
85+
capabilities[capabilityKey]?.generateScreenshot === true ||
86+
capabilities.reportingLegacy?.generateReport === true
87+
);
88+
};
89+
8290
export const reportingPDFExportProvider = ({
8391
apiClient,
8492
startServices$,
@@ -217,24 +225,18 @@ export const reportingPDFExportProvider = ({
217225
license.check('reporting', 'gold')
218226
);
219227

220-
const capabilityHasDashboardScreenshotReporting =
221-
capabilities.dashboard_v2?.generateScreenshot === true;
222-
const capabilityHasVisualizeScreenshotReporting =
223-
capabilities.visualize_v2?.generateScreenshot === true;
228+
const capabilityHasDashboardReporting = hasCapabilityByKey(capabilities, 'dashboard_v2');
229+
const capabilityHasVisualizeReporting = hasCapabilityByKey(capabilities, 'visualize_v2');
224230

225231
if (!licenseHasScreenshotReporting) {
226232
return false;
227233
}
228234

229-
if (objectType === 'dashboard' && !capabilityHasDashboardScreenshotReporting) {
235+
if (objectType === 'dashboard' && !capabilityHasDashboardReporting) {
230236
return false;
231237
}
232238

233-
if (
234-
isSupportedType &&
235-
!capabilityHasVisualizeScreenshotReporting &&
236-
!capabilityHasDashboardScreenshotReporting
237-
) {
239+
if (isSupportedType && !capabilityHasVisualizeReporting && !capabilityHasDashboardReporting) {
238240
return false;
239241
}
240242

@@ -380,24 +382,18 @@ export const reportingPNGExportProvider = ({
380382
const { showLinks } = checkLicense(license.check('reporting', 'gold'));
381383
const licenseHasScreenshotReporting = showLinks;
382384

383-
const capabilityHasDashboardScreenshotReporting =
384-
capabilities.dashboard_v2?.generateScreenshot === true;
385-
const capabilityHasVisualizeScreenshotReporting =
386-
capabilities.visualize_v2?.generateScreenshot === true;
385+
const capabilityHasDashboardReporting = hasCapabilityByKey(capabilities, 'dashboard_v2');
386+
const capabilityHasVisualizeReporting = hasCapabilityByKey(capabilities, 'visualize_v2');
387387

388388
if (!licenseHasScreenshotReporting) {
389389
return false;
390390
}
391391

392-
if (objectType === 'dashboard' && !capabilityHasDashboardScreenshotReporting) {
392+
if (objectType === 'dashboard' && !capabilityHasDashboardReporting) {
393393
return false;
394394
}
395395

396-
if (
397-
isSupportedType &&
398-
!capabilityHasVisualizeScreenshotReporting &&
399-
!capabilityHasDashboardScreenshotReporting
400-
) {
396+
if (isSupportedType && !capabilityHasVisualizeReporting && !capabilityHasDashboardReporting) {
401397
return false;
402398
}
403399

src/platform/test/functional/page_objects/export_page.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ export class ExportPageObject extends FtrService {
1919
return await this.testSubjects.exists('exportTopNavButton');
2020
}
2121

22+
async exportButtonMissingOrFail() {
23+
await this.testSubjects.missingOrFail('exportTopNavButton', { timeout: 1000 });
24+
}
25+
2226
async clickExportTopNavButton() {
2327
return this.testSubjects.click('exportTopNavButton');
2428
}

x-pack/platform/packages/private/security/authorization_core/src/privileges/privileges.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ export function privilegesFactory(
245245
read: [actions.login, ...readActions],
246246
},
247247
reserved: features.reduce((acc: Record<string, string[]>, feature: KibanaFeature) => {
248+
// Reserved privileges are intentionally not excluded from registration based on their `hidden` attribute.
249+
// This is explicitly to support the legacy reporting use case.
248250
if (feature.reserved) {
249251
feature.reserved.privileges.forEach((reservedPrivilege) => {
250252
acc[reservedPrivilege.id] = [

x-pack/platform/plugins/private/canvas/public/services/kibana_services.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,17 @@ export const setKibanaServices = (
4343
kibanaVersion = initContext.env.packageInfo.version;
4444

4545
coreServices = kibanaCore;
46+
const capabilities = kibanaCore.application.capabilities;
4647
contentManagementService = deps.contentManagement;
4748
dataService = deps.data;
4849
dataViewsService = deps.dataViews;
4950
embeddableService = deps.embeddable;
5051
expressionsService = deps.expressions;
5152
presentationUtilService = deps.presentationUtil;
52-
reportingService = Boolean(kibanaCore.application.capabilities.canvas?.generatePdf)
53+
reportingService = Boolean(
54+
capabilities.canvas?.generatePdf === true ||
55+
capabilities.reportingLegacy?.generateReport === true
56+
)
5357
? deps.reporting
5458
: undefined;
5559
spacesService = deps.spaces;

x-pack/platform/plugins/private/reporting/server/features.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@ interface FeatureRegistrationOpts {
2121
}
2222

2323
export function registerFeatures({ isServerless, features }: FeatureRegistrationOpts) {
24-
// Register a 'shell' feature specifically for Serverless. If granted, it will automatically provide access to
25-
// reporting capabilities in other features, such as Discover, Dashboards, and Visualizations. On its own, this
26-
// feature doesn't grant any additional privileges.
24+
// Register a 'shell' features for Reporting. On their own, they don't grant specific privileges.
25+
26+
// Shell feature for Serverless. If granted, it will automatically provide access to
27+
// reporting capabilities in other features, such as Discover, Dashboards, and Visualizations.
2728
if (isServerless) {
2829
features.registerKibanaFeature({
2930
id: 'reporting',
@@ -39,6 +40,44 @@ export function registerFeatures({ isServerless, features }: FeatureRegistration
3940
read: { disabled: true, savedObject: { all: [], read: [] }, ui: [] },
4041
},
4142
});
43+
} else {
44+
// Shell feature for self-managed environments, to be leveraged by a reserved privilege defined
45+
// in ES. This grants access to reporting features in a legacy fashion.
46+
features.registerKibanaFeature({
47+
id: 'reportingLegacy',
48+
name: i18n.translate('xpack.reporting.features.reportingLegacyFeatureName', {
49+
defaultMessage: 'Reporting Legacy',
50+
}),
51+
category: DEFAULT_APP_CATEGORIES.management,
52+
management: { insightsAndAlerting: ['reporting'] },
53+
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
54+
hidden: true,
55+
app: [],
56+
privileges: null,
57+
reserved: {
58+
description: i18n.translate(
59+
'xpack.reporting.features.reportingLegacyFeatureReservedDescription',
60+
{
61+
defaultMessage:
62+
'Reserved for use by the Reporting plugin. This feature is used to grant access to Reporting capabilities in a legacy manner.',
63+
}
64+
),
65+
privileges: [
66+
{
67+
id: 'reporting_user',
68+
privilege: {
69+
excludeFromBasePrivileges: true,
70+
app: [],
71+
catalogue: [],
72+
management: { insightsAndAlerting: ['reporting'] },
73+
savedObject: { all: [], read: [] },
74+
api: ['generateReport'],
75+
ui: ['generateReport'],
76+
},
77+
},
78+
],
79+
},
80+
});
4281
}
4382

4483
features.enableReportingUiCapabilities();

x-pack/platform/plugins/private/reporting/server/plugin.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ describe('Reporting Plugin', () => {
185185
describe('features registration', () => {
186186
it('registers Kibana manage scheduled reporting feature in traditional build flavour', async () => {
187187
plugin.setup(coreSetup, pluginSetup);
188-
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledTimes(1);
188+
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledTimes(2); // manage scheduled reports + shell feature for self-managed
189189
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledWith({
190190
id: 'manageReporting',
191191
name: 'Manage Scheduled Reports',
@@ -212,7 +212,7 @@ describe('Reporting Plugin', () => {
212212
plugin = new ReportingPlugin(serverlessInitContext);
213213

214214
plugin.setup(coreSetup, pluginSetup);
215-
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledTimes(2);
215+
expect(featuresSetup.registerKibanaFeature).toHaveBeenCalledTimes(2); // manage scheduled reports + shell feature for serverless
216216
expect(featuresSetup.registerKibanaFeature).toHaveBeenNthCalledWith(1, {
217217
id: 'reporting',
218218
name: 'Reporting',

x-pack/platform/plugins/shared/features/common/kibana_feature.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ export interface KibanaFeatureConfig {
159159
* Indicates whether the feature is available as a standalone feature. The feature can still be
160160
* referenced by other features, but it will not be displayed in any feature management UIs. By default, all features
161161
* are visible.
162+
*
163+
* @note This flag is designed for use via configuration overrides, and very select use cases. Please consult prior to use.
162164
*/
163165
hidden?: boolean;
164166

x-pack/platform/plugins/shared/features/server/feature_registry.test.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,6 +2030,114 @@ describe('FeatureRegistry', () => {
20302030
);
20312031
});
20322032

2033+
it('allows reserved features to be hidden', () => {
2034+
const feature: KibanaFeatureConfig = {
2035+
id: 'test-feature',
2036+
name: 'Test Feature',
2037+
app: [],
2038+
category: { id: 'foo', label: 'foo' },
2039+
privileges: null,
2040+
hidden: true,
2041+
reserved: {
2042+
description: 'my reserved privileges',
2043+
privileges: [
2044+
{
2045+
id: 'a_reserved_1',
2046+
privilege: {
2047+
savedObject: {
2048+
all: [],
2049+
read: [],
2050+
},
2051+
ui: [],
2052+
app: [],
2053+
},
2054+
},
2055+
],
2056+
},
2057+
};
2058+
2059+
const featureRegistry = new FeatureRegistry();
2060+
expect(() => featureRegistry.registerKibanaFeature(feature)).not.toThrowError();
2061+
});
2062+
2063+
it('does not allow features with both regular and reserved privileges to be hidden', () => {
2064+
const feature: KibanaFeatureConfig = {
2065+
id: 'test-feature',
2066+
name: 'Test Feature',
2067+
app: [],
2068+
category: { id: 'foo', label: 'foo' },
2069+
privileges: {
2070+
all: {
2071+
savedObject: {
2072+
all: [],
2073+
read: [],
2074+
},
2075+
ui: [],
2076+
},
2077+
read: {
2078+
savedObject: {
2079+
all: [],
2080+
read: [],
2081+
},
2082+
ui: [],
2083+
},
2084+
},
2085+
hidden: true,
2086+
reserved: {
2087+
description: 'my reserved privileges',
2088+
privileges: [
2089+
{
2090+
id: 'a_reserved_1',
2091+
privilege: {
2092+
savedObject: {
2093+
all: [],
2094+
read: [],
2095+
},
2096+
ui: [],
2097+
app: [],
2098+
},
2099+
},
2100+
],
2101+
},
2102+
};
2103+
2104+
const featureRegistry = new FeatureRegistry();
2105+
expect(() =>
2106+
featureRegistry.registerKibanaFeature(feature)
2107+
).toThrowErrorMatchingInlineSnapshot(`"Feature test-feature cannot be hidden."`);
2108+
});
2109+
2110+
it('does not allow features with regular privileges to be hidden', () => {
2111+
const feature: KibanaFeatureConfig = {
2112+
id: 'test-feature',
2113+
name: 'Test Feature',
2114+
app: [],
2115+
category: { id: 'foo', label: 'foo' },
2116+
privileges: {
2117+
all: {
2118+
savedObject: {
2119+
all: [],
2120+
read: [],
2121+
},
2122+
ui: [],
2123+
},
2124+
read: {
2125+
savedObject: {
2126+
all: [],
2127+
read: [],
2128+
},
2129+
ui: [],
2130+
},
2131+
},
2132+
hidden: true,
2133+
};
2134+
2135+
const featureRegistry = new FeatureRegistry();
2136+
expect(() =>
2137+
featureRegistry.registerKibanaFeature(feature)
2138+
).toThrowErrorMatchingInlineSnapshot(`"Feature test-feature cannot be hidden."`);
2139+
});
2140+
20332141
it('allows independent sub-feature privileges to register a minimumLicense', () => {
20342142
const feature1: KibanaFeatureConfig = {
20352143
id: 'test-feature',

0 commit comments

Comments
 (0)