From 866e122f86229c054a413700449a41794b03f94e Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Tue, 22 Aug 2023 13:28:41 +0800 Subject: [PATCH] feat: accomplish dashboard_admin (#95) * feat: accomplish dashboard_admin Signed-off-by: SuZhou-Joe * feat: add yml default config and comment Signed-off-by: SuZhou-Joe * feat: update Signed-off-by: SuZhou-Joe --------- Signed-off-by: SuZhou-Joe --- config/opensearch_dashboards.yml | 3 ++ src/plugins/workspace/config.ts | 24 +++++++++++++ src/plugins/workspace/server/index.ts | 10 ++---- src/plugins/workspace/server/plugin.ts | 9 ++++- .../workspace_saved_objects_client_wrapper.ts | 35 ++++++++++++++++++- 5 files changed, 71 insertions(+), 10 deletions(-) create mode 100644 src/plugins/workspace/config.ts diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 73f31233a783..8c43da0d2004 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -268,3 +268,6 @@ # Set the value of this setting to true to enable plugin augmentation on Dashboard # vis_augmenter.pluginAugmentationEnabled: true +# Set the backend roles, whoever has the backend roles defined in this config will be regard as dashboard admin. +# Dashboard admin will have the access to all the workspaces and objects inside OpenSearch Dashboards. +# workspace.dashboardAdmin.backendRoles: ["dashboard_admin"] \ No newline at end of file diff --git a/src/plugins/workspace/config.ts b/src/plugins/workspace/config.ts new file mode 100644 index 000000000000..6fc163b67e45 --- /dev/null +++ b/src/plugins/workspace/config.ts @@ -0,0 +1,24 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { schema, TypeOf } from '@osd/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + dashboardAdmin: schema.object( + { + backendRoles: schema.arrayOf(schema.string(), { + defaultValue: ['dashboard_admin'], + }), + }, + { + defaultValue: { + backendRoles: ['dashboard_admin'], + }, + } + ), +}); + +export type ConfigSchema = TypeOf; diff --git a/src/plugins/workspace/server/index.ts b/src/plugins/workspace/server/index.ts index 4e11dc50dab9..230b53b6f663 100644 --- a/src/plugins/workspace/server/index.ts +++ b/src/plugins/workspace/server/index.ts @@ -2,11 +2,9 @@ * Copyright OpenSearch Contributors * SPDX-License-Identifier: Apache-2.0 */ - -import { schema } from '@osd/config-schema'; - import { PluginConfigDescriptor, PluginInitializerContext } from '../../../core/server'; import { WorkspacePlugin } from './plugin'; +import { configSchema } from '../config'; // This exports static code and TypeScript types, // as well as, OpenSearch Dashboards Platform `plugin()` initializer. @@ -15,10 +13,6 @@ export function plugin(initializerContext: PluginInitializerContext) { return new WorkspacePlugin(initializerContext); } -export { MlCommonsPluginSetup, MlCommonsPluginStart } from './types'; - export const config: PluginConfigDescriptor = { - schema: schema.object({ - enabled: schema.boolean({ defaultValue: false }), - }), + schema: configSchema, }; diff --git a/src/plugins/workspace/server/plugin.ts b/src/plugins/workspace/server/plugin.ts index dfddcd372c17..1d3cdf3ad19d 100644 --- a/src/plugins/workspace/server/plugin.ts +++ b/src/plugins/workspace/server/plugin.ts @@ -3,6 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ import { i18n } from '@osd/i18n'; +import { Observable } from 'rxjs'; import { PluginInitializerContext, @@ -23,10 +24,12 @@ import { IWorkspaceDBImpl, WorkspaceAttribute } from './types'; import { WorkspaceClientWithSavedObject } from './workspace_client'; import { WorkspaceSavedObjectsClientWrapper } from './saved_objects'; import { registerRoutes } from './routes'; +import { ConfigSchema } from '../config'; export class WorkspacePlugin implements Plugin<{}, {}> { private readonly logger: Logger; private client?: IWorkspaceDBImpl; + private config$: Observable; private proxyWorkspaceTrafficToRealHandler(setupDeps: CoreSetup) { /** @@ -47,6 +50,7 @@ export class WorkspacePlugin implements Plugin<{}, {}> { constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'workspace'); + this.config$ = initializerContext.config.create(); } public async setup(core: CoreSetup) { @@ -56,7 +60,10 @@ export class WorkspacePlugin implements Plugin<{}, {}> { await this.client.setup(core); const workspaceSavedObjectsClientWrapper = new WorkspaceSavedObjectsClientWrapper( - core.savedObjects.permissionControl + core.savedObjects.permissionControl, + { + config$: this.config$, + } ); core.savedObjects.addClientWrapper( diff --git a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts index dbde03f91150..16543ca9be2b 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts @@ -5,6 +5,8 @@ import { i18n } from '@osd/i18n'; import Boom from '@hapi/boom'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; import { OpenSearchDashboardsRequest, @@ -29,6 +31,7 @@ import { ACL, WorkspacePermissionMode, } from '../../../../core/server'; +import { ConfigSchema } from '../../config'; // Can't throw unauthorized for now, the page will be refreshed if unauthorized const generateWorkspacePermissionError = () => @@ -56,6 +59,7 @@ const isWorkspacesLikeAttributes = (attributes: unknown): attributes is Attribut Array.isArray((attributes as { workspaces: unknown }).workspaces); export class WorkspaceSavedObjectsClientWrapper { + private config?: ConfigSchema; private formatWorkspacePermissionModeToStringArray( permission: WorkspacePermissionMode | WorkspacePermissionMode[] ): string[] { @@ -137,6 +141,14 @@ export class WorkspaceSavedObjectsClientWrapper { } } + private isDashboardAdmin(request: OpenSearchDashboardsRequest): boolean { + const config = this.config || ({} as ConfigSchema); + const principals = this.permissionControl.getPrincipalsFromRequest(request); + const adminBackendRoles = config?.dashboardAdmin?.backendRoles || []; + const matchAny = principals?.groups?.some((item) => adminBackendRoles.includes(item)) || false; + return matchAny; + } + /** * check if the type include workspace * Workspace permission check is totally different from object permission check. @@ -374,6 +386,12 @@ export class WorkspaceSavedObjectsClientWrapper { return await wrapperOptions.client.addToWorkspaces(objects, targetWorkspaces, options); }; + const isDashboardAdmin = this.isDashboardAdmin(wrapperOptions.request); + + if (isDashboardAdmin) { + return wrapperOptions.client; + } + return { ...wrapperOptions.client, get: getWithWorkspacePermissionControl, @@ -392,5 +410,20 @@ export class WorkspaceSavedObjectsClientWrapper { }; }; - constructor(private readonly permissionControl: SavedObjectsPermissionControlContract) {} + constructor( + private readonly permissionControl: SavedObjectsPermissionControlContract, + private readonly options: { + config$: Observable; + } + ) { + this.options.config$.subscribe((config) => { + this.config = config; + }); + this.options.config$ + .pipe(first()) + .toPromise() + .then((config) => { + this.config = config; + }); + } }