Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[7.x] Support for sub-feature privileges (#60563) #61089

Merged
merged 1 commit into from Mar 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -890,7 +890,8 @@ export class DashboardAppController {
share.toggleShareContextMenu({
anchorElement,
allowEmbed: true,
allowShortUrl: !dashboardConfig.getHideWriteControls(),
allowShortUrl:
!dashboardConfig.getHideWriteControls() || dashboardCapabilities.createShortUrl,
shareableUrl: unhashUrl(window.location.href),
objectId: dash.id,
objectType: 'dashboard',
Expand Down
3 changes: 3 additions & 0 deletions x-pack/legacy/plugins/apm/index.ts
Expand Up @@ -87,13 +87,15 @@ export const apm: LegacyPluginInitializer = kibana => {
name: i18n.translate('xpack.apm.featureRegistry.apmFeatureName', {
defaultMessage: 'APM'
}),
order: 900,
icon: 'apmApp',
navLinkId: 'apm',
app: ['apm', 'kibana'],
catalogue: ['apm'],
// see x-pack/plugins/features/common/feature_kibana_privileges.ts
privileges: {
all: {
app: ['apm', 'kibana'],
api: ['apm', 'apm_write', 'actions-read', 'alerting-read'],
catalogue: ['apm'],
savedObject: {
Expand All @@ -112,6 +114,7 @@ export const apm: LegacyPluginInitializer = kibana => {
]
},
read: {
app: ['apm', 'kibana'],
api: ['apm', 'actions-read', 'alerting-read'],
catalogue: ['apm'],
savedObject: {
Expand Down
5 changes: 5 additions & 0 deletions x-pack/legacy/plugins/graph/index.ts
Expand Up @@ -37,20 +37,25 @@ export const graph: LegacyPluginInitializer = kibana => {
name: i18n.translate('xpack.graph.featureRegistry.graphFeatureName', {
defaultMessage: 'Graph',
}),
order: 1200,
icon: 'graphApp',
navLinkId: 'graph',
app: ['graph', 'kibana'],
catalogue: ['graph'],
validLicenses: ['platinum', 'enterprise', 'trial'],
privileges: {
all: {
app: ['graph', 'kibana'],
catalogue: ['graph'],
savedObject: {
all: ['graph-workspace'],
read: ['index-pattern'],
},
ui: ['save', 'delete'],
},
read: {
app: ['graph', 'kibana'],
catalogue: ['graph'],
savedObject: {
all: [],
read: ['index-pattern', 'graph-workspace'],
Expand Down
5 changes: 5 additions & 0 deletions x-pack/legacy/plugins/maps/server/plugin.js
Expand Up @@ -23,19 +23,24 @@ export class MapPlugin {
name: i18n.translate('xpack.maps.featureRegistry.mapsFeatureName', {
defaultMessage: 'Maps',
}),
order: 600,
icon: APP_ICON,
navLinkId: APP_ID,
app: [APP_ID, 'kibana'],
catalogue: [APP_ID],
privileges: {
all: {
app: [APP_ID, 'kibana'],
catalogue: [APP_ID],
savedObject: {
all: [MAP_SAVED_OBJECT_TYPE, 'query'],
read: ['index-pattern'],
},
ui: ['save', 'show', 'saveQuery'],
},
read: {
app: [APP_ID, 'kibana'],
catalogue: [APP_ID],
savedObject: {
all: [],
read: [MAP_SAVED_OBJECT_TYPE, 'index-pattern', 'query'],
Expand Down
5 changes: 5 additions & 0 deletions x-pack/legacy/plugins/siem/server/plugin.ts
Expand Up @@ -95,12 +95,15 @@ export class Plugin {
name: i18n.translate('xpack.siem.featureRegistry.linkSiemTitle', {
defaultMessage: 'SIEM',
}),
order: 1100,
icon: 'securityAnalyticsApp',
navLinkId: 'siem',
app: ['siem', 'kibana'],
catalogue: ['siem'],
privileges: {
all: {
app: ['siem', 'kibana'],
catalogue: ['siem'],
api: ['siem', 'actions-read', 'actions-all', 'alerting-read', 'alerting-all'],
savedObject: {
all: [
Expand All @@ -126,6 +129,8 @@ export class Plugin {
],
},
read: {
app: ['siem', 'kibana'],
catalogue: ['siem'],
api: ['siem', 'actions-read', 'actions-all', 'alerting-read', 'alerting-all'],
savedObject: {
all: ['alert', 'action', 'action_task_params'],
Expand Down
4 changes: 2 additions & 2 deletions x-pack/legacy/plugins/xpack_main/server/xpack_main.d.ts
Expand Up @@ -5,12 +5,12 @@
*/

import KbnServer from 'src/legacy/server/kbn_server';
import { Feature, FeatureWithAllOrReadPrivileges } from '../../../../plugins/features/server';
import { Feature, FeatureConfig } from '../../../../plugins/features/server';
import { XPackInfo, XPackInfoOptions } from './lib/xpack_info';
export { XPackFeature } from './lib/xpack_info';

export interface XPackMainPlugin {
info: XPackInfo;
getFeatures(): Feature[];
registerFeature(feature: FeatureWithAllOrReadPrivileges): void;
registerFeature(feature: FeatureConfig): void;
}
5 changes: 5 additions & 0 deletions x-pack/plugins/canvas/server/plugin.ts
Expand Up @@ -32,19 +32,24 @@ export class CanvasPlugin implements Plugin {
plugins.features.registerFeature({
id: 'canvas',
name: 'Canvas',
order: 400,
icon: 'canvasApp',
navLinkId: 'canvas',
app: ['canvas', 'kibana'],
catalogue: ['canvas'],
privileges: {
all: {
app: ['canvas', 'kibana'],
catalogue: ['canvas'],
savedObject: {
all: ['canvas-workpad', 'canvas-element'],
read: ['index-pattern'],
},
ui: ['save', 'show'],
},
read: {
app: ['canvas', 'kibana'],
catalogue: ['canvas'],
savedObject: {
all: [],
read: ['index-pattern', 'canvas-workpad', 'canvas-element'],
Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/endpoint/server/plugin.ts
Expand Up @@ -43,6 +43,7 @@ export class EndpointPlugin
app: ['endpoint', 'kibana'],
privileges: {
all: {
app: ['endpoint', 'kibana'],
api: ['resolver'],
savedObject: {
all: [],
Expand All @@ -51,6 +52,7 @@ export class EndpointPlugin
ui: ['save'],
},
read: {
app: ['endpoint', 'kibana'],
api: [],
savedObject: {
all: [],
Expand Down
88 changes: 79 additions & 9 deletions x-pack/plugins/features/common/feature.ts
Expand Up @@ -4,16 +4,16 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { FeatureKibanaPrivileges, FeatureKibanaPrivilegesSet } from './feature_kibana_privileges';
import { RecursiveReadonly } from '@kbn/utility-types';
import { FeatureKibanaPrivileges } from './feature_kibana_privileges';
import { SubFeatureConfig, SubFeature } from './sub_feature';

/**
* Interface for registering a feature.
* Feature registration allows plugins to hide their applications with spaces,
* and secure access when configured for security.
*/
export interface Feature<
TPrivileges extends Partial<FeatureKibanaPrivilegesSet> = FeatureKibanaPrivilegesSet
> {
export interface FeatureConfig {
/**
* Unique identifier for this feature.
* This identifier is also used when generating UI Capabilities.
Expand All @@ -28,6 +28,11 @@ export interface Feature<
*/
name: string;

/**
* An ordinal used to sort features relative to one another for display.
*/
order?: number;

/**
* Whether or not this feature should be excluded from the base privileges.
* This is primarily helpful when migrating applications with a "legacy" privileges model
Expand Down Expand Up @@ -98,7 +103,15 @@ export interface Feature<
* ```
* @see FeatureKibanaPrivileges
*/
privileges: TPrivileges;
privileges: {
all: FeatureKibanaPrivileges;
read: FeatureKibanaPrivileges;
} | null;

/**
* Optional sub-feature privilege definitions. This can only be specified if `privileges` are are also defined.
*/
subFeatures?: SubFeatureConfig[];

/**
* Optional message to display on the Role Management screen when configuring permissions for this feature.
Expand All @@ -114,7 +127,64 @@ export interface Feature<
};
}

export type FeatureWithAllOrReadPrivileges = Feature<{
all?: FeatureKibanaPrivileges;
read?: FeatureKibanaPrivileges;
}>;
export class Feature {
public readonly subFeatures: SubFeature[];

constructor(protected readonly config: RecursiveReadonly<FeatureConfig>) {
this.subFeatures = (config.subFeatures ?? []).map(
subFeatureConfig => new SubFeature(subFeatureConfig)
);
}

public get id() {
return this.config.id;
}

public get name() {
return this.config.name;
}

public get order() {
return this.config.order;
}

public get navLinkId() {
return this.config.navLinkId;
}

public get app() {
return this.config.app;
}

public get catalogue() {
return this.config.catalogue;
}

public get management() {
return this.config.management;
}

public get icon() {
return this.config.icon;
}

public get validLicenses() {
return this.config.validLicenses;
}

public get privileges() {
return this.config.privileges;
}

public get excludeFromBasePrivileges() {
return this.config.excludeFromBasePrivileges ?? false;
}

public get reserved() {
return this.config.reserved;
}

public toRaw() {
return { ...this.config } as FeatureConfig;
}
}
2 changes: 0 additions & 2 deletions x-pack/plugins/features/common/feature_kibana_privileges.ts
Expand Up @@ -123,5 +123,3 @@ export interface FeatureKibanaPrivileges {
*/
ui: string[];
}

export type FeatureKibanaPrivilegesSet = Record<string, FeatureKibanaPrivileges>;
9 changes: 8 additions & 1 deletion x-pack/plugins/features/common/index.ts
Expand Up @@ -5,4 +5,11 @@
*/

export { FeatureKibanaPrivileges } from './feature_kibana_privileges';
export * from './feature';
export { Feature, FeatureConfig } from './feature';
export {
SubFeature,
SubFeatureConfig,
SubFeaturePrivilegeConfig,
SubFeaturePrivilegeGroupConfig,
SubFeaturePrivilegeGroupType,
} from './sub_feature';
87 changes: 87 additions & 0 deletions x-pack/plugins/features/common/sub_feature.ts
@@ -0,0 +1,87 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { RecursiveReadonly } from '@kbn/utility-types';
import { FeatureKibanaPrivileges } from './feature_kibana_privileges';

/**
* Configuration for a sub-feature.
*/
export interface SubFeatureConfig {
/** Display name for this sub-feature */
name: string;

/** Collection of privilege groups */
privilegeGroups: SubFeaturePrivilegeGroupConfig[];
}

/**
* The type of privilege group.
* - `mutually_exclusive`::
* Users will be able to select at most one privilege within this group.
* Privileges must be specified in descending order of permissiveness (e.g. `All`, `Read`, not `Read`, `All)
* - `independent`::
* Users will be able to select any combination of privileges within this group.
*/
export type SubFeaturePrivilegeGroupType = 'mutually_exclusive' | 'independent';

/**
* Configuration for a sub-feature privilege group.
*/
export interface SubFeaturePrivilegeGroupConfig {
/**
* The type of privilege group.
* - `mutually_exclusive`::
* Users will be able to select at most one privilege within this group.
* Privileges must be specified in descending order of permissiveness (e.g. `All`, `Read`, not `Read`, `All)
* - `independent`::
* Users will be able to select any combination of privileges within this group.
*/
groupType: SubFeaturePrivilegeGroupType;

/**
* The privileges which belong to this group.
*/
privileges: SubFeaturePrivilegeConfig[];
}

/**
* Configuration for a sub-feature privilege.
*/
export interface SubFeaturePrivilegeConfig
extends Omit<FeatureKibanaPrivileges, 'excludeFromBasePrivileges'> {
/**
* Identifier for this privilege. Must be unique across all other privileges within a feature.
*/
id: string;

/**
* The display name for this privilege.
*/
name: string;

/**
* Denotes which Primary Feature Privilege this sub-feature privilege should be included in.
* `read` is also included in `all` automatically.
*/
includeIn: 'all' | 'read' | 'none';
}

export class SubFeature {
constructor(protected readonly config: RecursiveReadonly<SubFeatureConfig>) {}

public get name() {
return this.config.name;
}

public get privilegeGroups() {
return this.config.privilegeGroups;
}

public toRaw() {
return { ...this.config };
}
}
2 changes: 1 addition & 1 deletion x-pack/plugins/features/kibana.json
Expand Up @@ -4,5 +4,5 @@
"kibanaVersion": "kibana",
"optionalPlugins": ["timelion"],
"server": true,
"ui": false
"ui": true
}