kibana/x-pack/platform/plugins/shared/streams/server/plugin.ts
Joe Reuter b1d85aa6a8
🌊 Streams: Add streams feature privilege (#218966)
This PR adds a new feature for streams to control whether the UI and API
is available.

Changes:
* Add the feature with two privileges (`show` and `manage_assets`)
* Can be configured with the classic `none`/`read`/`all` so it's
automatically aligned with serverless editor/viewer permissions
* None also means the app is not shown - to do this, the existing
`status$` observable also looks for at least the `streams.show`
capability
* Only guards changes to the linked dashboards - changes to the
Elasticsearch level are still delegated to the Elasticsearch-level
permissions of the user
* This happens on the UI level (disabled button and dashboard selection
on the dashboard page)
* and on the API level (all endpoints that can change linked dashboards
require the permission)


# Questions

* Not sure about the name `manage_assets` - maybe it should be something
else
* Not sure about how the queries stuff should work - @kdelemme anything
we should do in this area?

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
2025-04-28 11:50:05 +02:00

156 lines
4.9 KiB
TypeScript

/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import {
CoreSetup,
CoreStart,
DEFAULT_APP_CATEGORIES,
KibanaRequest,
Logger,
Plugin,
PluginConfigDescriptor,
PluginInitializerContext,
} from '@kbn/core/server';
import { registerRoutes } from '@kbn/server-route-repository';
import { KibanaFeatureScope } from '@kbn/features-plugin/common';
import { i18n } from '@kbn/i18n';
import { StreamsConfig, configSchema, exposeToBrowserConfig } from '../common/config';
import { streamsRouteRepository } from './routes';
import {
StreamsPluginSetupDependencies,
StreamsPluginStartDependencies,
StreamsServer,
} from './types';
import { AssetService } from './lib/streams/assets/asset_service';
import { RouteHandlerScopedClients } from './routes/types';
import { StreamsService } from './lib/streams/service';
import { StreamsTelemetryService } from './lib/telemetry/service';
import { STREAMS_API_PRIVILEGES, STREAMS_UI_PRIVILEGES } from '../common/constants';
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface StreamsPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface StreamsPluginStart {}
export const config: PluginConfigDescriptor<StreamsConfig> = {
schema: configSchema,
exposeToBrowser: exposeToBrowserConfig,
};
export class StreamsPlugin
implements
Plugin<
StreamsPluginSetup,
StreamsPluginStart,
StreamsPluginSetupDependencies,
StreamsPluginStartDependencies
>
{
public config: StreamsConfig;
public logger: Logger;
public server?: StreamsServer;
private isDev: boolean;
private telemtryService = new StreamsTelemetryService();
constructor(context: PluginInitializerContext<StreamsConfig>) {
this.isDev = context.env.mode.dev;
this.config = context.config.get();
this.logger = context.logger.get();
}
public setup(
core: CoreSetup<StreamsPluginStartDependencies>,
plugins: StreamsPluginSetupDependencies
): StreamsPluginSetup {
this.server = {
config: this.config,
logger: this.logger,
} as StreamsServer;
this.telemtryService.setup(core.analytics);
const assetService = new AssetService(core, this.logger);
const streamsService = new StreamsService(core, this.logger, this.isDev);
plugins.features.registerKibanaFeature({
id: 'streams',
name: i18n.translate('xpack.streams.featureRegistry.streamsFeatureName', {
defaultMessage: 'Streams',
}),
order: 600,
category: DEFAULT_APP_CATEGORIES.observability,
scope: [KibanaFeatureScope.Spaces, KibanaFeatureScope.Security],
app: ['streams'],
privileges: {
all: {
app: ['streams'],
savedObject: {
all: [],
read: [],
},
api: [STREAMS_API_PRIVILEGES.read, STREAMS_API_PRIVILEGES.manage],
ui: [STREAMS_UI_PRIVILEGES.show, STREAMS_UI_PRIVILEGES.manage],
},
read: {
app: ['streams'],
savedObject: {
all: [],
read: [],
},
api: [STREAMS_API_PRIVILEGES.read],
ui: [STREAMS_UI_PRIVILEGES.show],
},
},
});
registerRoutes({
repository: streamsRouteRepository,
dependencies: {
assets: assetService,
server: this.server,
telemetry: this.telemtryService.getClient(),
getScopedClients: async ({
request,
}: {
request: KibanaRequest;
}): Promise<RouteHandlerScopedClients> => {
const [[coreStart, pluginsStart], assetClient] = await Promise.all([
core.getStartServices(),
assetService.getClientWithRequest({ request }),
]);
const streamsClient = await streamsService.getClientWithRequest({ request, assetClient });
const scopedClusterClient = coreStart.elasticsearch.client.asScoped(request);
const soClient = coreStart.savedObjects.getScopedClient(request);
const inferenceClient = pluginsStart.inference.getClient({ request });
return { scopedClusterClient, soClient, assetClient, streamsClient, inferenceClient };
},
},
core,
logger: this.logger,
runDevModeChecks: this.isDev,
});
return {};
}
public start(core: CoreStart, plugins: StreamsPluginStartDependencies): StreamsPluginStart {
if (this.server) {
this.server.core = core;
this.server.isServerless = core.elasticsearch.getCapabilities().serverless;
this.server.security = plugins.security;
this.server.encryptedSavedObjects = plugins.encryptedSavedObjects;
this.server.taskManager = plugins.taskManager;
}
return {};
}
public stop() {}
}