Add support for licensed sub feature privileges (#80905)

This commit is contained in:
Larry Gregory 2020-11-16 14:50:20 -05:00 committed by GitHub
parent bc3bb2afa8
commit fe33579272
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1641 additions and 55 deletions

View file

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`buildOSSFeatures returns the advancedSettings feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the advancedSettings feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
@ -51,7 +51,7 @@ Array [
]
`;
exports[`buildOSSFeatures returns the dashboard feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the dashboard feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
@ -128,7 +128,7 @@ Array [
]
`;
exports[`buildOSSFeatures returns the dev_tools feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the dev_tools feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
@ -182,7 +182,7 @@ Array [
]
`;
exports[`buildOSSFeatures returns the discover feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the discover feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
@ -243,7 +243,7 @@ Array [
]
`;
exports[`buildOSSFeatures returns the indexPatterns feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the indexPatterns feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
@ -296,7 +296,7 @@ Array [
]
`;
exports[`buildOSSFeatures returns the savedObjectsManagement feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the savedObjectsManagement feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
@ -363,7 +363,7 @@ Array [
]
`;
exports[`buildOSSFeatures returns the timelion feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the timelion feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
@ -411,7 +411,489 @@ Array [
]
`;
exports[`buildOSSFeatures returns the visualize feature augmented with appropriate sub feature privileges 1`] = `
exports[`buildOSSFeatures with a basic license returns the visualize feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"alerting": Object {
"all": Array [],
"read": Array [],
},
"api": Array [],
"app": Array [
"visualize",
"lens",
"kibana",
],
"catalogue": Array [
"visualize",
],
"management": Object {},
"savedObject": Object {
"all": Array [
"visualization",
"query",
"lens",
"url",
],
"read": Array [
"index-pattern",
"search",
"tag",
],
},
"ui": Array [
"show",
"delete",
"save",
"saveQuery",
"createShortUrl",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"app": Array [
"visualize",
"lens",
"kibana",
],
"catalogue": Array [
"visualize",
],
"savedObject": Object {
"all": Array [],
"read": Array [
"index-pattern",
"search",
"visualization",
"query",
"lens",
"tag",
],
},
"ui": Array [
"show",
],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the advancedSettings feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"app": Array [
"kibana",
],
"catalogue": Array [
"advanced_settings",
],
"management": Object {
"kibana": Array [
"settings",
],
},
"savedObject": Object {
"all": Array [
"config",
],
"read": Array [],
},
"ui": Array [
"save",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"app": Array [
"kibana",
],
"catalogue": Array [
"advanced_settings",
],
"management": Object {
"kibana": Array [
"settings",
],
},
"savedObject": Object {
"all": Array [],
"read": Array [],
},
"ui": Array [],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the dashboard feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"alerting": Object {
"all": Array [],
"read": Array [],
},
"api": Array [],
"app": Array [
"dashboards",
"kibana",
],
"catalogue": Array [
"dashboard",
],
"management": Object {},
"savedObject": Object {
"all": Array [
"dashboard",
"query",
"url",
],
"read": Array [
"index-pattern",
"search",
"visualization",
"timelion-sheet",
"canvas-workpad",
"lens",
"map",
"tag",
],
},
"ui": Array [
"createNew",
"show",
"showWriteControls",
"saveQuery",
"createShortUrl",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"app": Array [
"dashboards",
"kibana",
],
"catalogue": Array [
"dashboard",
],
"savedObject": Object {
"all": Array [],
"read": Array [
"index-pattern",
"search",
"visualization",
"timelion-sheet",
"canvas-workpad",
"lens",
"map",
"dashboard",
"query",
"tag",
],
},
"ui": Array [
"show",
],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the dev_tools feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"api": Array [
"console",
],
"app": Array [
"dev_tools",
"kibana",
],
"catalogue": Array [
"console",
"searchprofiler",
"grokdebugger",
],
"savedObject": Object {
"all": Array [],
"read": Array [],
},
"ui": Array [
"show",
"save",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"api": Array [
"console",
],
"app": Array [
"dev_tools",
"kibana",
],
"catalogue": Array [
"console",
"searchprofiler",
"grokdebugger",
],
"savedObject": Object {
"all": Array [],
"read": Array [],
},
"ui": Array [
"show",
],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the discover feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"alerting": Object {
"all": Array [],
"read": Array [],
},
"api": Array [],
"app": Array [
"discover",
"kibana",
],
"catalogue": Array [
"discover",
],
"management": Object {},
"savedObject": Object {
"all": Array [
"search",
"query",
"index-pattern",
"url",
],
"read": Array [],
},
"ui": Array [
"show",
"save",
"saveQuery",
"createShortUrl",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"app": Array [
"discover",
"kibana",
],
"catalogue": Array [
"discover",
],
"savedObject": Object {
"all": Array [],
"read": Array [
"index-pattern",
"search",
"query",
],
},
"ui": Array [
"show",
],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the indexPatterns feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"app": Array [
"kibana",
],
"catalogue": Array [
"indexPatterns",
],
"management": Object {
"kibana": Array [
"indexPatterns",
],
},
"savedObject": Object {
"all": Array [
"index-pattern",
],
"read": Array [],
},
"ui": Array [
"save",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"app": Array [
"kibana",
],
"catalogue": Array [
"indexPatterns",
],
"management": Object {
"kibana": Array [
"indexPatterns",
],
},
"savedObject": Object {
"all": Array [],
"read": Array [
"index-pattern",
],
},
"ui": Array [],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the savedObjectsManagement feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"api": Array [
"copySavedObjectsToSpaces",
],
"app": Array [
"kibana",
],
"catalogue": Array [
"saved_objects",
],
"management": Object {
"kibana": Array [
"objects",
],
},
"savedObject": Object {
"all": Array [
"foo",
"bar",
],
"read": Array [],
},
"ui": Array [
"read",
"edit",
"delete",
"copyIntoSpace",
"shareIntoSpace",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"api": Array [
"copySavedObjectsToSpaces",
],
"app": Array [
"kibana",
],
"catalogue": Array [
"saved_objects",
],
"management": Object {
"kibana": Array [
"objects",
],
},
"savedObject": Object {
"all": Array [],
"read": Array [
"foo",
"bar",
],
},
"ui": Array [
"read",
],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the timelion feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {
"app": Array [
"timelion",
"kibana",
],
"catalogue": Array [
"timelion",
],
"savedObject": Object {
"all": Array [
"timelion-sheet",
],
"read": Array [
"index-pattern",
],
},
"ui": Array [
"save",
],
},
"privilegeId": "all",
},
Object {
"privilege": Object {
"app": Array [
"timelion",
"kibana",
],
"catalogue": Array [
"timelion",
],
"savedObject": Object {
"all": Array [],
"read": Array [
"index-pattern",
"timelion-sheet",
],
},
"ui": Array [],
},
"privilegeId": "read",
},
]
`;
exports[`buildOSSFeatures with a enterprise license returns the visualize feature augmented with appropriate sub feature privileges 1`] = `
Array [
Object {
"privilege": Object {

View file

@ -6,6 +6,7 @@
import { FeatureRegistry } from './feature_registry';
import { ElasticsearchFeatureConfig, KibanaFeatureConfig } from '../common';
import { licensingMock } from '../../licensing/server/mocks';
describe('FeatureRegistry', () => {
describe('Kibana Features', () => {
@ -1280,6 +1281,123 @@ describe('FeatureRegistry', () => {
);
});
it('allows independent sub-feature privileges to register a minimumLicense', () => {
const feature1: KibanaFeatureConfig = {
id: 'test-feature',
name: 'Test Feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: {
savedObject: {
all: [],
read: [],
},
ui: [],
},
read: {
savedObject: {
all: [],
read: [],
},
ui: [],
},
},
subFeatures: [
{
name: 'foo',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'foo',
name: 'foo',
minimumLicense: 'platinum',
includeIn: 'all',
savedObject: {
all: [],
read: [],
},
ui: [],
},
],
},
],
},
],
};
const featureRegistry = new FeatureRegistry();
featureRegistry.registerKibanaFeature(feature1);
});
it('prevents mutually exclusive sub-feature privileges from registering a minimumLicense', () => {
const feature1: KibanaFeatureConfig = {
id: 'test-feature',
name: 'Test Feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: {
savedObject: {
all: [],
read: [],
},
ui: [],
},
read: {
savedObject: {
all: [],
read: [],
},
ui: [],
},
},
subFeatures: [
{
name: 'foo',
privilegeGroups: [
{
groupType: 'mutually_exclusive',
privileges: [
{
id: 'foo',
name: 'foo',
minimumLicense: 'platinum',
includeIn: 'all',
savedObject: {
all: [],
read: [],
},
ui: [],
},
{
id: 'bar',
name: 'Bar',
minimumLicense: 'platinum',
includeIn: 'all',
savedObject: {
all: [],
read: [],
},
ui: [],
},
],
},
],
},
],
};
const featureRegistry = new FeatureRegistry();
expect(() => {
featureRegistry.registerKibanaFeature(feature1);
}).toThrowErrorMatchingInlineSnapshot(
`"child \\"subFeatures\\" fails because [\\"subFeatures\\" at position 0 fails because [child \\"privilegeGroups\\" fails because [\\"privilegeGroups\\" at position 0 fails because [child \\"privileges\\" fails because [\\"privileges\\" at position 0 fails because [child \\"minimumLicense\\" fails because [\\"minimumLicense\\" is not allowed]]]]]]]"`
);
});
it('cannot register feature after getAll has been called', () => {
const feature1: KibanaFeatureConfig = {
id: 'test-feature',
@ -1305,6 +1423,89 @@ describe('FeatureRegistry', () => {
`"Features are locked, can't register new features. Attempt to register test-feature-2 failed."`
);
});
describe('#getAllKibanaFeatures', () => {
const features: KibanaFeatureConfig[] = [
{
id: 'gold-feature',
name: 'Test Feature',
app: [],
category: { id: 'foo', label: 'foo' },
minimumLicense: 'gold',
privileges: null,
},
{
id: 'unlicensed-feature',
name: 'Test Feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: null,
},
{
id: 'with-sub-feature',
name: 'Test Feature',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: {
all: { savedObject: { all: [], read: [] }, ui: [] },
read: { savedObject: { all: [], read: [] }, ui: [] },
},
minimumLicense: 'platinum',
subFeatures: [
{
name: 'licensed-sub-feature',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'sub-feature',
includeIn: 'all',
minimumLicense: 'enterprise',
name: 'sub feature',
savedObject: { all: [], read: [] },
ui: [],
},
],
},
],
},
],
},
];
const registry = new FeatureRegistry();
features.forEach((f) => registry.registerKibanaFeature(f));
it('returns all features and sub-feature privileges by default', () => {
const result = registry.getAllKibanaFeatures();
expect(result).toHaveLength(3);
const [, , withSubFeature] = result;
expect(withSubFeature.subFeatures).toHaveLength(1);
expect(withSubFeature.subFeatures[0].privilegeGroups).toHaveLength(1);
expect(withSubFeature.subFeatures[0].privilegeGroups[0].privileges).toHaveLength(1);
});
it('returns features which are satisfied by the current license', () => {
const license = licensingMock.createLicense({ license: { type: 'gold' } });
const result = registry.getAllKibanaFeatures(license);
expect(result).toHaveLength(2);
const ids = result.map((f) => f.id);
expect(ids).toEqual(['gold-feature', 'unlicensed-feature']);
});
it('filters out sub-feature privileges which do not match the current license', () => {
const license = licensingMock.createLicense({ license: { type: 'platinum' } });
const result = registry.getAllKibanaFeatures(license);
expect(result).toHaveLength(3);
const ids = result.map((f) => f.id);
expect(ids).toEqual(['gold-feature', 'unlicensed-feature', 'with-sub-feature']);
const [, , withSubFeature] = result;
expect(withSubFeature.subFeatures).toHaveLength(1);
expect(withSubFeature.subFeatures[0].privilegeGroups).toHaveLength(1);
expect(withSubFeature.subFeatures[0].privilegeGroups[0].privileges).toHaveLength(0);
});
});
});
describe('Elasticsearch Features', () => {

View file

@ -5,6 +5,7 @@
*/
import { cloneDeep, uniq } from 'lodash';
import { ILicense } from '../../licensing/server';
import {
KibanaFeatureConfig,
KibanaFeature,
@ -55,11 +56,30 @@ export class FeatureRegistry {
this.esFeatures[feature.id] = featureCopy;
}
public getAllKibanaFeatures(): KibanaFeature[] {
public getAllKibanaFeatures(license?: ILicense, ignoreLicense = false): KibanaFeature[] {
this.locked = true;
return Object.values(this.kibanaFeatures).map(
(featureConfig) => new KibanaFeature(featureConfig)
);
let features = Object.values(this.kibanaFeatures);
const performLicenseCheck = license && !ignoreLicense;
if (performLicenseCheck) {
features = features.filter((feature) => {
const filter = !feature.minimumLicense || license!.hasAtLeast(feature.minimumLicense);
if (!filter) return false;
feature.subFeatures?.forEach((subFeature) => {
subFeature.privilegeGroups.forEach((group) => {
group.privileges = group.privileges.filter(
(privilege) =>
!privilege.minimumLicense || license!.hasAtLeast(privilege.minimumLicense)
);
});
});
return true;
});
}
return features.map((featureConfig) => new KibanaFeature(featureConfig));
}
public getAllElasticsearchFeatures(): ElasticsearchFeature[] {

View file

@ -21,6 +21,11 @@ const managementSectionIdRegex = /^[a-zA-Z0-9_-]+$/;
const reservedFeaturePrrivilegePartRegex = /^(?!reserved_)[a-zA-Z0-9_-]+$/;
export const uiCapabilitiesRegex = /^[a-zA-Z0-9:_-]+$/;
const validLicenses = ['basic', 'standard', 'gold', 'platinum', 'enterprise', 'trial'];
// sub-feature privileges are only available with a `gold` license or better, so restricting sub-feature privileges
// for `gold` or below doesn't make a whole lot of sense.
const validSubFeaturePrivilegeLicenses = ['platinum', 'enterprise', 'trial'];
const managementSchema = Joi.object().pattern(
managementSectionIdRegex,
Joi.array().items(Joi.string().regex(uiCapabilitiesRegex))
@ -53,10 +58,11 @@ const kibanaPrivilegeSchema = Joi.object({
ui: Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)).required(),
});
const kibanaSubFeaturePrivilegeSchema = Joi.object({
const kibanaIndependentSubFeaturePrivilegeSchema = Joi.object({
id: Joi.string().regex(subFeaturePrivilegePartRegex).required(),
name: Joi.string().required(),
includeIn: Joi.string().allow('all', 'read', 'none').required(),
minimumLicense: Joi.string().valid(...validSubFeaturePrivilegeLicenses),
management: managementSchema,
catalogue: catalogueSchema,
alerting: Joi.object({
@ -72,12 +78,22 @@ const kibanaSubFeaturePrivilegeSchema = Joi.object({
ui: Joi.array().items(Joi.string().regex(uiCapabilitiesRegex)).required(),
});
const kibanaMutuallyExclusiveSubFeaturePrivilegeSchema = kibanaIndependentSubFeaturePrivilegeSchema.keys(
{
minimumLicense: Joi.forbidden(),
}
);
const kibanaSubFeatureSchema = Joi.object({
name: Joi.string().required(),
privilegeGroups: Joi.array().items(
Joi.object({
groupType: Joi.string().valid('mutually_exclusive', 'independent').required(),
privileges: Joi.array().items(kibanaSubFeaturePrivilegeSchema).min(1),
privileges: Joi.when('groupType', {
is: 'mutually_exclusive',
then: Joi.array().items(kibanaMutuallyExclusiveSubFeaturePrivilegeSchema).min(1),
otherwise: Joi.array().items(kibanaIndependentSubFeaturePrivilegeSchema).min(1),
}),
})
),
});
@ -91,14 +107,7 @@ const kibanaFeatureSchema = Joi.object({
category: appCategorySchema,
order: Joi.number(),
excludeFromBasePrivileges: Joi.boolean(),
minimumLicense: Joi.string().valid(
'basic',
'standard',
'gold',
'platinum',
'enterprise',
'trial'
),
minimumLicense: Joi.string().valid(...validLicenses),
app: Joi.array().items(Joi.string()).required(),
management: managementSchema,
catalogue: catalogueSchema,

View file

@ -7,6 +7,7 @@
import { buildOSSFeatures } from './oss_features';
import { featurePrivilegeIterator } from '../../security/server/authorization';
import { KibanaFeature } from '.';
import { LicenseType } from '../../licensing/server';
describe('buildOSSFeatures', () => {
it('returns features including timelion', () => {
@ -46,14 +47,22 @@ Array [
const features = buildOSSFeatures({ savedObjectTypes: ['foo', 'bar'], includeTimelion: true });
features.forEach((featureConfig) => {
it(`returns the ${featureConfig.id} feature augmented with appropriate sub feature privileges`, () => {
const privileges = [];
for (const featurePrivilege of featurePrivilegeIterator(new KibanaFeature(featureConfig), {
augmentWithSubFeaturePrivileges: true,
})) {
privileges.push(featurePrivilege);
}
expect(privileges).toMatchSnapshot();
(['enterprise', 'basic'] as LicenseType[]).forEach((licenseType) => {
describe(`with a ${licenseType} license`, () => {
it(`returns the ${featureConfig.id} feature augmented with appropriate sub feature privileges`, () => {
const privileges = [];
for (const featurePrivilege of featurePrivilegeIterator(
new KibanaFeature(featureConfig),
{
augmentWithSubFeaturePrivileges: true,
licenseType,
}
)) {
privileges.push(featurePrivilege);
}
expect(privileges).toMatchSnapshot();
});
});
});
});
});

View file

@ -11,15 +11,78 @@ import { httpServerMock, httpServiceMock, coreMock } from '../../../../../src/co
import { LicenseType } from '../../../licensing/server/';
import { licensingMock } from '../../../licensing/server/mocks';
import { RequestHandler } from '../../../../../src/core/server';
import { KibanaFeatureConfig } from '../../common';
import { FeatureKibanaPrivileges, KibanaFeatureConfig, SubFeatureConfig } from '../../common';
function createContextMock(licenseType: LicenseType = 'gold') {
function createContextMock(licenseType: LicenseType = 'platinum') {
return {
core: coreMock.createRequestHandlerContext(),
licensing: licensingMock.createRequestHandlerContext({ license: { type: licenseType } }),
};
}
function createPrivilege(partial: Partial<FeatureKibanaPrivileges> = {}): FeatureKibanaPrivileges {
return {
savedObject: {
all: [],
read: [],
},
ui: [],
...partial,
};
}
function getExpectedSubFeatures(licenseType: LicenseType = 'platinum'): SubFeatureConfig[] {
return [
{
name: 'basicFeature',
privilegeGroups: [
{
groupType: 'independent',
privileges: [
{
id: 'basicSub1',
name: 'basic sub 1',
includeIn: 'all',
...createPrivilege(),
},
],
},
],
},
{
name: 'platinumFeature',
privilegeGroups: [
{
groupType: 'independent',
privileges:
licenseType !== 'basic'
? [
{
id: 'platinumFeatureSub1',
name: 'platinum sub 1',
includeIn: 'all',
minimumLicense: 'platinum',
...createPrivilege(),
},
]
: [],
},
{
groupType: 'mutually_exclusive',
privileges: [
{
id: 'platinumFeatureMutExSub1',
name: 'platinum sub 1',
includeIn: 'all',
...createPrivilege(),
},
],
},
],
},
];
}
describe('GET /api/features', () => {
let routeHandler: RequestHandler<any, any, any>;
beforeEach(() => {
@ -29,7 +92,11 @@ describe('GET /api/features', () => {
name: 'Feature 1',
app: [],
category: { id: 'foo', label: 'foo' },
privileges: null,
privileges: {
all: createPrivilege(),
read: createPrivilege(),
},
subFeatures: getExpectedSubFeatures(),
});
featureRegistry.registerKibanaFeature({
@ -76,7 +143,12 @@ describe('GET /api/features', () => {
const [call] = mockResponse.ok.mock.calls;
const body = call[0]!.body as KibanaFeatureConfig[];
const features = body.map((feature) => ({ id: feature.id, order: feature.order }));
const features = body.map((feature) => ({
id: feature.id,
order: feature.order,
subFeatures: feature.subFeatures,
}));
expect(features).toEqual([
{
id: 'feature_3',
@ -89,6 +161,7 @@ describe('GET /api/features', () => {
{
id: 'feature_1',
order: undefined,
subFeatures: getExpectedSubFeatures(),
},
{
id: 'licensed_feature',
@ -105,7 +178,11 @@ describe('GET /api/features', () => {
const [call] = mockResponse.ok.mock.calls;
const body = call[0]!.body as KibanaFeatureConfig[];
const features = body.map((feature) => ({ id: feature.id, order: feature.order }));
const features = body.map((feature) => ({
id: feature.id,
order: feature.order,
subFeatures: feature.subFeatures,
}));
expect(features).toEqual([
{
@ -119,6 +196,7 @@ describe('GET /api/features', () => {
{
id: 'feature_1',
order: undefined,
subFeatures: getExpectedSubFeatures('basic'),
},
]);
});
@ -135,7 +213,11 @@ describe('GET /api/features', () => {
const [call] = mockResponse.ok.mock.calls;
const body = call[0]!.body as KibanaFeatureConfig[];
const features = body.map((feature) => ({ id: feature.id, order: feature.order }));
const features = body.map((feature) => ({
id: feature.id,
order: feature.order,
subFeatures: feature.subFeatures,
}));
expect(features).toEqual([
{
@ -149,6 +231,7 @@ describe('GET /api/features', () => {
{
id: 'feature_1',
order: undefined,
subFeatures: getExpectedSubFeatures('basic'),
},
]);
});
@ -165,7 +248,11 @@ describe('GET /api/features', () => {
const [call] = mockResponse.ok.mock.calls;
const body = call[0]!.body as KibanaFeatureConfig[];
const features = body.map((feature) => ({ id: feature.id, order: feature.order }));
const features = body.map((feature) => ({
id: feature.id,
order: feature.order,
subFeatures: feature.subFeatures,
}));
expect(features).toEqual([
{
@ -179,6 +266,7 @@ describe('GET /api/features', () => {
{
id: 'feature_1',
order: undefined,
subFeatures: getExpectedSubFeatures(),
},
{
id: 'licensed_feature',

View file

@ -26,17 +26,15 @@ export function defineRoutes({ router, featureRegistry }: RouteDefinitionParams)
},
},
(context, request, response) => {
const allFeatures = featureRegistry.getAllKibanaFeatures();
const currentLicense = context.licensing!.license;
const allFeatures = featureRegistry.getAllKibanaFeatures(
currentLicense,
request.query.ignoreValidLicenses
);
return response.ok({
body: allFeatures
.filter(
(feature) =>
request.query.ignoreValidLicenses ||
!feature.minimumLicense ||
(context.licensing!.license &&
context.licensing!.license.hasAtLeast(feature.minimumLicense))
)
.sort(
(f1, f2) =>
(f1.order ?? Number.MAX_SAFE_INTEGER) - (f2.order ?? Number.MAX_SAFE_INTEGER)