mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Fix sub-feature privilege "minimumLicense" bug (#106008)
This commit is contained in:
parent
cc1dd52977
commit
ea062391bb
13 changed files with 178 additions and 297 deletions
|
@ -5,9 +5,24 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { LicenseType } from '../../../licensing/common/types';
|
||||
import { LICENSE_TYPE } from '../../../licensing/server';
|
||||
import { KibanaFeature } from '../';
|
||||
import { SubFeaturePrivilegeConfig } from '../../common';
|
||||
import type { FeaturePrivilegeIteratorOptions } from './feature_privilege_iterator';
|
||||
import { featurePrivilegeIterator } from './feature_privilege_iterator';
|
||||
|
||||
function getFeaturePrivilegeIterator(
|
||||
feature: KibanaFeature,
|
||||
options: Omit<FeaturePrivilegeIteratorOptions, 'licenseHasAtLeast'> & { licenseType: LicenseType }
|
||||
) {
|
||||
const { licenseType, ...otherOptions } = options;
|
||||
const licenseHasAtLeast = (licenseTypeToCheck: LicenseType) => {
|
||||
return LICENSE_TYPE[licenseTypeToCheck] <= LICENSE_TYPE[options.licenseType];
|
||||
};
|
||||
return featurePrivilegeIterator(feature, { licenseHasAtLeast, ...otherOptions });
|
||||
}
|
||||
|
||||
describe('featurePrivilegeIterator', () => {
|
||||
it('handles features with no privileges', () => {
|
||||
const feature = new KibanaFeature({
|
||||
|
@ -19,7 +34,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -90,7 +105,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -219,7 +234,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
predicate: (privilegeId) => privilegeId === 'all',
|
||||
|
@ -357,7 +372,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: false,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -519,7 +534,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -682,7 +697,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -852,7 +867,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -1020,7 +1035,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -1088,97 +1103,40 @@ describe('featurePrivilegeIterator', () => {
|
|||
]);
|
||||
});
|
||||
|
||||
it('excludes sub feature privileges when the minimum license is not met', () => {
|
||||
describe('excludes sub-feature privileges when the minimum license is not met', () => {
|
||||
function createSubFeaturePrivilegeConfig(licenseType: LicenseType): SubFeaturePrivilegeConfig {
|
||||
return {
|
||||
// This is not a realistic sub-feature privilege config, but we only need the "api" string for our test cases
|
||||
id: `${licenseType}-sub-feature`,
|
||||
name: '',
|
||||
includeIn: 'all',
|
||||
minimumLicense: licenseType,
|
||||
api: [`${licenseType}-api`],
|
||||
savedObject: { all: [], read: [] },
|
||||
ui: [],
|
||||
};
|
||||
}
|
||||
|
||||
const feature = new KibanaFeature({
|
||||
id: 'foo',
|
||||
name: 'foo',
|
||||
id: 'feature',
|
||||
name: 'feature-name',
|
||||
app: [],
|
||||
category: { id: 'foo', label: 'foo' },
|
||||
category: { id: 'category-id', label: 'category-label' },
|
||||
privileges: {
|
||||
all: {
|
||||
api: ['all-api', 'read-api'],
|
||||
app: ['foo'],
|
||||
catalogue: ['foo-catalogue'],
|
||||
management: {
|
||||
section: ['foo-management'],
|
||||
},
|
||||
savedObject: {
|
||||
all: ['all-type'],
|
||||
read: ['read-type'],
|
||||
},
|
||||
alerting: {
|
||||
rule: {
|
||||
all: ['alerting-all-type'],
|
||||
},
|
||||
alert: {
|
||||
read: ['alerting-another-read-type'],
|
||||
},
|
||||
},
|
||||
cases: {
|
||||
all: ['cases-all-type'],
|
||||
read: ['cases-read-type'],
|
||||
},
|
||||
ui: ['ui-action'],
|
||||
},
|
||||
read: {
|
||||
api: ['read-api'],
|
||||
app: ['foo'],
|
||||
catalogue: ['foo-catalogue'],
|
||||
management: {
|
||||
section: ['foo-management'],
|
||||
},
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['read-type'],
|
||||
},
|
||||
alerting: {
|
||||
rule: {
|
||||
read: ['alerting-read-type'],
|
||||
},
|
||||
alert: {
|
||||
read: ['alerting-read-type'],
|
||||
},
|
||||
},
|
||||
cases: {
|
||||
read: ['cases-read-type'],
|
||||
},
|
||||
ui: ['ui-action'],
|
||||
},
|
||||
all: { savedObject: { all: ['obj-type'], read: [] }, ui: [] },
|
||||
read: { savedObject: { all: [], read: ['obj-type'] }, ui: [] },
|
||||
},
|
||||
subFeatures: [
|
||||
{
|
||||
name: 'sub feature 1',
|
||||
name: `sub-feature-name`,
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'independent',
|
||||
privileges: [
|
||||
{
|
||||
id: 'sub-feature-priv-1',
|
||||
name: 'first sub feature privilege',
|
||||
includeIn: 'all',
|
||||
minimumLicense: 'gold',
|
||||
api: ['sub-feature-api'],
|
||||
app: ['sub-app'],
|
||||
catalogue: ['sub-catalogue'],
|
||||
management: {
|
||||
section: ['other-sub-management'],
|
||||
kibana: ['sub-management'],
|
||||
},
|
||||
savedObject: {
|
||||
all: ['all-sub-type'],
|
||||
read: ['read-sub-type'],
|
||||
},
|
||||
alerting: {
|
||||
alert: {
|
||||
all: ['alerting-all-sub-type'],
|
||||
},
|
||||
},
|
||||
cases: {
|
||||
all: ['cases-all-sub-type'],
|
||||
read: ['cases-read-sub-type'],
|
||||
},
|
||||
ui: ['ui-sub-type'],
|
||||
},
|
||||
createSubFeaturePrivilegeConfig('gold'),
|
||||
createSubFeaturePrivilegeConfig('platinum'),
|
||||
createSubFeaturePrivilegeConfig('enterprise'),
|
||||
// Note: we intentionally do not include a sub-feature privilege config for the "trial" license because that should never be used
|
||||
],
|
||||
},
|
||||
],
|
||||
|
@ -1186,70 +1144,64 @@ describe('featurePrivilegeIterator', () => {
|
|||
],
|
||||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
);
|
||||
// Each of the test cases below is a minimal check to make sure the correct sub-feature privileges are applied -- nothing more, nothing less
|
||||
// Note: we do not include a test case for the "basic" license, because sub-feature privileges are not enabled at that license level
|
||||
|
||||
expect(actualPrivileges).toEqual([
|
||||
{
|
||||
privilegeId: 'all',
|
||||
privilege: {
|
||||
api: ['all-api', 'read-api'],
|
||||
app: ['foo'],
|
||||
catalogue: ['foo-catalogue'],
|
||||
management: {
|
||||
section: ['foo-management'],
|
||||
},
|
||||
savedObject: {
|
||||
all: ['all-type'],
|
||||
read: ['read-type'],
|
||||
},
|
||||
alerting: {
|
||||
rule: {
|
||||
all: ['alerting-all-type'],
|
||||
},
|
||||
alert: {
|
||||
read: ['alerting-another-read-type'],
|
||||
},
|
||||
},
|
||||
cases: {
|
||||
all: ['cases-all-type'],
|
||||
read: ['cases-read-type'],
|
||||
},
|
||||
ui: ['ui-action'],
|
||||
},
|
||||
},
|
||||
{
|
||||
privilegeId: 'read',
|
||||
privilege: {
|
||||
api: ['read-api'],
|
||||
app: ['foo'],
|
||||
catalogue: ['foo-catalogue'],
|
||||
management: {
|
||||
section: ['foo-management'],
|
||||
},
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['read-type'],
|
||||
},
|
||||
alerting: {
|
||||
rule: {
|
||||
read: ['alerting-read-type'],
|
||||
},
|
||||
alert: {
|
||||
read: ['alerting-read-type'],
|
||||
},
|
||||
},
|
||||
cases: {
|
||||
read: ['cases-read-type'],
|
||||
},
|
||||
ui: ['ui-action'],
|
||||
},
|
||||
},
|
||||
]);
|
||||
it('with a gold license', () => {
|
||||
const actualPrivileges = Array.from(
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'gold',
|
||||
})
|
||||
);
|
||||
const expectedPrivilege = expect.objectContaining({ api: ['gold-api'] });
|
||||
expect(actualPrivileges).toEqual(
|
||||
expect.arrayContaining([{ privilegeId: 'all', privilege: expectedPrivilege }])
|
||||
);
|
||||
});
|
||||
|
||||
it('with a platinum license', () => {
|
||||
const actualPrivileges = Array.from(
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'platinum',
|
||||
})
|
||||
);
|
||||
const expectedPrivilege = expect.objectContaining({ api: ['gold-api', 'platinum-api'] });
|
||||
expect(actualPrivileges).toEqual(
|
||||
expect.arrayContaining([{ privilegeId: 'all', privilege: expectedPrivilege }])
|
||||
);
|
||||
});
|
||||
|
||||
it('with an enterprise license', () => {
|
||||
const actualPrivileges = Array.from(
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'enterprise',
|
||||
})
|
||||
);
|
||||
const expectedPrivilege = expect.objectContaining({
|
||||
api: ['gold-api', 'platinum-api', 'enterprise-api'],
|
||||
});
|
||||
expect(actualPrivileges).toEqual(
|
||||
expect.arrayContaining([{ privilegeId: 'all', privilege: expectedPrivilege }])
|
||||
);
|
||||
});
|
||||
|
||||
it('with a trial license', () => {
|
||||
const actualPrivileges = Array.from(
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'trial',
|
||||
})
|
||||
);
|
||||
const expectedPrivilege = expect.objectContaining({
|
||||
api: ['gold-api', 'platinum-api', 'enterprise-api'],
|
||||
});
|
||||
expect(actualPrivileges).toEqual(
|
||||
expect.arrayContaining([{ privilegeId: 'all', privilege: expectedPrivilege }])
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it(`can augment primary feature privileges even if they don't specify their own`, () => {
|
||||
|
@ -1316,7 +1268,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
@ -1470,7 +1422,7 @@ describe('featurePrivilegeIterator', () => {
|
|||
});
|
||||
|
||||
const actualPrivileges = Array.from(
|
||||
featurePrivilegeIterator(feature, {
|
||||
getFeaturePrivilegeIterator(feature, {
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType: 'basic',
|
||||
})
|
||||
|
|
|
@ -21,9 +21,10 @@ export interface FeaturePrivilegeIteratorOptions {
|
|||
augmentWithSubFeaturePrivileges: boolean;
|
||||
|
||||
/**
|
||||
* The current license type. Controls which sub-features are returned, as they may have different license terms than the overall feature.
|
||||
* Function that returns whether the current license is equal to or greater than the given license type.
|
||||
* Controls which sub-features are returned, as they may have different license terms than the overall feature.
|
||||
*/
|
||||
licenseType: LicenseType;
|
||||
licenseHasAtLeast: (licenseType: LicenseType) => boolean | undefined;
|
||||
|
||||
/**
|
||||
* Optional predicate to filter the returned set of privileges.
|
||||
|
@ -59,7 +60,7 @@ const featurePrivilegeIterator: FeaturePrivilegeIterator = function* featurePriv
|
|||
if (options.augmentWithSubFeaturePrivileges) {
|
||||
yield {
|
||||
privilegeId,
|
||||
privilege: mergeWithSubFeatures(privilegeId, privilege, feature, options.licenseType),
|
||||
privilege: mergeWithSubFeatures(privilegeId, privilege, feature, options.licenseHasAtLeast),
|
||||
};
|
||||
} else {
|
||||
yield { privilegeId, privilege };
|
||||
|
@ -71,10 +72,10 @@ function mergeWithSubFeatures(
|
|||
privilegeId: string,
|
||||
privilege: FeatureKibanaPrivileges,
|
||||
feature: KibanaFeature,
|
||||
licenseType: LicenseType
|
||||
licenseHasAtLeast: FeaturePrivilegeIteratorOptions['licenseHasAtLeast']
|
||||
) {
|
||||
const mergedConfig = _.cloneDeep(privilege);
|
||||
for (const subFeaturePrivilege of subFeaturePrivilegeIterator(feature, licenseType)) {
|
||||
for (const subFeaturePrivilege of subFeaturePrivilegeIterator(feature, licenseHasAtLeast)) {
|
||||
if (subFeaturePrivilege.includeIn !== 'read' && subFeaturePrivilege.includeIn !== privilegeId) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -16,17 +16,17 @@ import type { LicenseType } from '../../../licensing/server';
|
|||
*/
|
||||
export type SubFeaturePrivilegeIterator = (
|
||||
feature: KibanaFeature,
|
||||
licenseType: LicenseType
|
||||
licenseHasAtLeast: (licenseType: LicenseType) => boolean | undefined
|
||||
) => IterableIterator<SubFeaturePrivilegeConfig>;
|
||||
|
||||
const subFeaturePrivilegeIterator: SubFeaturePrivilegeIterator = function* subFeaturePrivilegeIterator(
|
||||
feature: KibanaFeature,
|
||||
licenseType: LicenseType
|
||||
licenseHasAtLeast: (licenseType: LicenseType) => boolean | undefined
|
||||
): IterableIterator<SubFeaturePrivilegeConfig> {
|
||||
for (const subFeature of feature.subFeatures) {
|
||||
for (const group of subFeature.privilegeGroups) {
|
||||
yield* group.privileges.filter(
|
||||
(privilege) => !privilege.minimumLicense || privilege.minimumLicense <= licenseType
|
||||
(privilege) => !privilege.minimumLicense || licenseHasAtLeast(privilege.minimumLicense)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { buildOSSFeatures } from './oss_features';
|
||||
import { featurePrivilegeIterator } from './feature_privilege_iterator';
|
||||
import { KibanaFeature } from '.';
|
||||
import { LicenseType } from '../../licensing/server';
|
||||
import { LicenseType, LICENSE_TYPE } from '../../licensing/server';
|
||||
|
||||
describe('buildOSSFeatures', () => {
|
||||
it('returns features including timelion', () => {
|
||||
|
@ -86,7 +86,8 @@ Array [
|
|||
new KibanaFeature(featureConfig),
|
||||
{
|
||||
augmentWithSubFeaturePrivileges: true,
|
||||
licenseType,
|
||||
licenseHasAtLeast: (licenseTypeToCheck: LicenseType) =>
|
||||
LICENSE_TYPE[licenseTypeToCheck] <= LICENSE_TYPE[licenseType],
|
||||
}
|
||||
)) {
|
||||
privileges.push(featurePrivilege);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue