Developer documentation for designing feature privileges (#166716)

## Summary

Closes #162263

Introduces a new `Feature Privileges` section to the developer
documentation. The documentation includes a tutorial on how to control
access to features of plugin being developed. Introduces a few sections:

- Controlling access to UI features
- Controlling access to server side APIs
- Documentation for configuration options

## Testing
To build this locally, run ./scripts/dev_docs from a local checkout of
this PR. A server will eventually start on http://localhost:3000 where
you can preview the changes.

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Sid 2023-09-27 13:43:55 +02:00 committed by GitHub
parent 22fca89861
commit de0e1eb0d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 730 additions and 0 deletions

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export const FEATURE_PRIVILEGES_PLUGIN_ID = 'featurePrivilegesPluginExample';

View file

@ -0,0 +1,13 @@
{
"type": "plugin",
"id": "@kbn/feature-controls-examples-plugin",
"owner": "@elastic/kibana-security",
"description": "Demo of how to implement feature controls",
"plugin": {
"id": "featureControlsExamples",
"server": true,
"browser": true,
"requiredBundles": ["kibanaReact"],
"requiredPlugins": ["developerExamples", "security", "spaces", "features"]
}
}

View file

@ -0,0 +1,62 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { EuiButton, EuiHealth, EuiPageTemplate, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui';
import type { CoreStart } from '@kbn/core/public';
import { useKibana } from '@kbn/kibana-react-plugin/public';
import React, { useEffect, useState } from 'react';
import { FEATURE_PRIVILEGES_PLUGIN_ID } from '../common';
export const MyPluginComponent: React.FC = () => {
const [time, setTime] = useState('');
const kibana = useKibana<CoreStart>();
const fetchData = async () => {
const response = await fetch('/internal/my_plugin/read');
const data = await response.json();
// console.log(data2);
setTime(data.time);
};
useEffect(() => {
fetchData();
}, []);
return (
<EuiPageTemplate>
<EuiPageTemplate.Section grow={false} color="subdued" bottomBorder="extended">
<EuiTitle size="l">
<h1>Feature Privileges Example</h1>
</EuiTitle>
</EuiPageTemplate.Section>
<EuiPageTemplate.Section grow={false} color="subdued" bottomBorder="extended">
<EuiText>
<p>Server Time: {time}</p>
</EuiText>
<EuiButton onClick={fetchData}>Refresh (Super user only)</EuiButton>
</EuiPageTemplate.Section>
<EuiPageTemplate.Section grow={false} color="subdued" bottomBorder="extended">
<EuiText>
<p>Your privileges</p>
</EuiText>
<EuiSpacer />
{Object.entries(
kibana.services.application!.capabilities[FEATURE_PRIVILEGES_PLUGIN_ID]
).map(([capability, value]) => {
return value === true ? (
<div key={capability}>
<EuiHealth color="success">{capability}</EuiHealth>
<EuiSpacer />
</div>
) : null;
})}
</EuiPageTemplate.Section>
</EuiPageTemplate>
);
};

View file

@ -0,0 +1,59 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import React from 'react';
import ReactDOM from 'react-dom';
import { AppMountParameters, CoreSetup, CoreStart, Plugin } from '@kbn/core/public';
import { DeveloperExamplesSetup } from '@kbn/developer-examples-plugin/public';
import { SecurityPluginSetup, SecurityPluginStart } from '@kbn/security-plugin/public';
import { KibanaPageTemplate } from '@kbn/shared-ux-page-kibana-template';
import type { FeaturesPluginSetup } from '@kbn/features-plugin/public';
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
import { MyPluginComponent } from './app';
interface SetupDeps {
developerExamples: DeveloperExamplesSetup;
security: SecurityPluginSetup;
features: FeaturesPluginSetup;
}
interface StartDeps {
security: SecurityPluginStart;
}
export class FeatureControlsPluginExample implements Plugin<void, void, SetupDeps, StartDeps> {
public setup(coreSetup: CoreSetup<StartDeps>, deps: SetupDeps) {
coreSetup.application.register({
id: 'featureControlsExamples',
title: 'FeatureControlExamples',
async mount({ element }: AppMountParameters) {
const [coreStart] = await coreSetup.getStartServices();
ReactDOM.render(
<KibanaPageTemplate>
<KibanaContextProvider services={{ ...coreStart, ...deps }}>
<MyPluginComponent />
</KibanaContextProvider>
</KibanaPageTemplate>,
element
);
return () => ReactDOM.unmountComponentAtNode(element);
},
});
deps.developerExamples.register({
appId: 'featureControlsExamples',
title: 'Feature Control Examples',
description: 'Demo of how to implement Feature Controls',
});
}
public start(core: CoreStart, deps: StartDeps) {
return {};
}
public stop() {}
}
export const plugin = () => new FeatureControlsPluginExample();

View file

@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { PluginInitializer } from '@kbn/core/server';
import { FeatureControlsPluginExample } from './plugin';
export const plugin: PluginInitializer<void, void> = () => new FeatureControlsPluginExample();

View file

@ -0,0 +1,90 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { CoreSetup, DEFAULT_APP_CATEGORIES, Plugin } from '@kbn/core/server';
import {
PluginSetupContract as FeaturesPluginSetup,
// PluginStartContract as FeaturesPluginStart,
} from '@kbn/features-plugin/server';
import { FEATURE_PRIVILEGES_PLUGIN_ID } from '../common';
export interface FeatureControlExampleDeps {
features: FeaturesPluginSetup;
}
export class FeatureControlsPluginExample
implements Plugin<void, void, any, FeatureControlExampleDeps>
{
public setup(core: CoreSetup, { features }: FeatureControlExampleDeps) {
features.registerKibanaFeature({
id: FEATURE_PRIVILEGES_PLUGIN_ID,
name: 'Feature Plugin Examples',
category: DEFAULT_APP_CATEGORIES.management,
app: ['FeaturePluginExample'],
privileges: {
all: {
app: ['FeaturePluginExample'],
savedObject: {
all: [],
read: [],
},
api: ['my_closed_example_api'],
ui: ['view', 'create', 'edit', 'delete', 'assign'],
},
read: {
app: ['FeaturePluginExample'],
savedObject: {
all: [],
read: ['tag'],
},
api: [],
ui: ['view'],
},
},
});
const router = core.http.createRouter();
router.get(
{
path: '/internal/my_plugin/read',
validate: false,
},
async (context, request, response) => {
return response.ok({
body: {
time: new Date().toISOString(),
},
});
}
);
router.get(
{
path: '/internal/my_plugin/sensitive_action',
validate: false,
options: {
tags: ['access:my_closed_example_api'],
},
},
async (context, request, response) => {
return response.ok({
body: {
time: new Date().toISOString(),
},
});
}
);
}
start() {
return {};
}
stop() {
return {};
}
}

View file

@ -0,0 +1,23 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "target/types"
},
"include": [
"index.ts",
"common/**/*.ts",
"public/**/*.ts",
"public/**/*.tsx",
"server/**/*.ts",
"../../typings/**/*"
],
"exclude": ["target/**/*"],
"kbn_references": [
"@kbn/core",
"@kbn/security-plugin",
"@kbn/developer-examples-plugin",
"@kbn/shared-ux-page-kibana-template",
"@kbn/features-plugin",
"@kbn/kibana-react-plugin"
]
}