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

[Workspace] Integrate dashboard admin with dynamic config #8137

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/8137.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
refactor:
- [Workspace] Integrate dashboard admin with dynamic config ([#8137](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8137))
1 change: 1 addition & 0 deletions src/plugins/workspace/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ export enum AssociationDataSourceModalMode {
DirectQueryConnections = 'direction-query-connections',
}
export const USE_CASE_PREFIX = 'use-case-';
export const OPENSEARCHDASHBOARDS_CONFIG_PATH = 'opensearchDashboards';

// Workspace will handle both data source and data connection type saved object.
export const WORKSPACE_DATA_SOURCE_AND_CONNECTION_OBJECT_TYPES = [
Expand Down
57 changes: 49 additions & 8 deletions src/plugins/workspace/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
updateWorkspaceState,
} from '../../../core/server/utils';
import * as serverUtils from '../../../core/server/utils/auth_info';
import * as utilsExports from './utils';
import { SavedObjectsPermissionControl } from './permission_control/client';

describe('Workspace server plugin', () => {
Expand Down Expand Up @@ -109,6 +108,21 @@ describe('Workspace server plugin', () => {

describe('#setupPermission', () => {
const setupMock = coreMock.createSetup();
const getConfigMock = jest.fn().mockResolvedValue({
dashboardAdmin: {
users: ['dashboard-admin-user'],
groups: [],
},
});
jest.spyOn(setupMock.dynamicConfigService, 'getStartService').mockResolvedValue({
...setupMock.dynamicConfigService.getStartService(),
getAsyncLocalStore: jest.fn(),
getClient: () => ({
getConfig: getConfigMock,
bulkGetConfigs: jest.fn(),
listConfigs: jest.fn(),
}),
});
const initializerContextConfigMock = coreMock.createPluginInitializerContext({
enabled: true,
permission: {
Expand Down Expand Up @@ -137,13 +151,10 @@ describe('Workspace server plugin', () => {
expect(toolKitMock.next).toBeCalledTimes(1);
});

it('with configuring user as OSD admin', async () => {
it('with dynamic config and user is dashboard admin', async () => {
jest
.spyOn(serverUtils, 'getPrincipalsFromRequest')
.mockImplementation(() => ({ users: [`user1`] }));
jest
.spyOn(utilsExports, 'getOSDAdminConfigFromYMLConfig')
.mockResolvedValue([['group1'], ['user1']]);
.mockImplementation(() => ({ users: [`dashboard-admin-user`] }));

await workspacePlugin.setup(setupMock);
const toolKitMock = httpServerMock.createToolkit();
Expand All @@ -160,11 +171,36 @@ describe('Workspace server plugin', () => {
expect(toolKitMock.next).toBeCalledTimes(1);
});

it('with dynamic config and user is not dashboard admin', async () => {
jest
.spyOn(serverUtils, 'getPrincipalsFromRequest')
.mockImplementation(() => ({ users: [`none-dashboard-admin-user`] }));

await workspacePlugin.setup(setupMock);
const toolKitMock = httpServerMock.createToolkit();

await registerOnPostAuthFn(
requestWithWorkspaceInUrl,
httpServerMock.createResponseFactory(),
toolKitMock
);

expect(getWorkspaceState(requestWithWorkspaceInUrl)).toEqual({
isDashboardAdmin: false,
});
expect(toolKitMock.next).toBeCalledTimes(1);
});

it('with configuring wildcard * and anyone will be OSD admin', async () => {
jest
.spyOn(serverUtils, 'getPrincipalsFromRequest')
.mockImplementation(() => ({ users: [`user1`] }));
jest.spyOn(utilsExports, 'getOSDAdminConfigFromYMLConfig').mockResolvedValue([[], ['*']]);
getConfigMock.mockResolvedValueOnce({
dashboardAdmin: {
users: ['*'],
groups: [],
},
});

await workspacePlugin.setup(setupMock);
const toolKitMock = httpServerMock.createToolkit();
Expand All @@ -185,7 +221,12 @@ describe('Workspace server plugin', () => {
jest
.spyOn(serverUtils, 'getPrincipalsFromRequest')
.mockImplementation(() => ({ users: [`user1`] }));
jest.spyOn(utilsExports, 'getOSDAdminConfigFromYMLConfig').mockResolvedValue([[], []]);
getConfigMock.mockResolvedValueOnce({
dashboardAdmin: {
users: [],
groups: [],
},
});

await workspacePlugin.setup(setupMock);
const toolKitMock = httpServerMock.createToolkit();
Expand Down
15 changes: 13 additions & 2 deletions src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import { cloneDeep } from 'lodash';
import {
PluginInitializerContext,
CoreSetup,
Expand All @@ -26,6 +27,7 @@ import {
WORKSPACE_NAVIGATION_APP_ID,
DEFAULT_WORKSPACE,
PRIORITY_FOR_REPOSITORY_WRAPPER,
OPENSEARCHDASHBOARDS_CONFIG_PATH,
} from '../common/constants';
import { IWorkspaceClientImpl, WorkspacePluginSetup, WorkspacePluginStart } from './types';
import { WorkspaceClient } from './workspace_client';
Expand All @@ -47,7 +49,7 @@ import {
SavedObjectsPermissionControl,
SavedObjectsPermissionControlContract,
} from './permission_control/client';
import { getOSDAdminConfigFromYMLConfig, updateDashboardAdminStateForRequest } from './utils';
import { updateDashboardAdminStateForRequest } from './utils';
import { WorkspaceIdConsumerWrapper } from './saved_objects/workspace_id_consumer_wrapper';
import { WorkspaceUiSettingsClientWrapper } from './saved_objects/workspace_ui_settings_client_wrapper';
import { uiSettings } from './ui_settings';
Expand Down Expand Up @@ -97,8 +99,17 @@ export class WorkspacePlugin implements Plugin<WorkspacePluginSetup, WorkspacePl
} catch (e) {
return toolkit.next();
}
// Get config from dynamic service client.
const dynamicConfigServiceStart = await core.dynamicConfigService.getStartService();
const store = dynamicConfigServiceStart.getAsyncLocalStore();
const client = dynamicConfigServiceStart.getClient();
const config = await client.getConfig(
{ pluginConfigPath: OPENSEARCHDASHBOARDS_CONFIG_PATH },
{ asyncLocalStorageContext: store! }
);
const configUsers: string[] = cloneDeep(config.dashboardAdmin.users);
const configGroups: string[] = cloneDeep(config.dashboardAdmin.groups);

const [configGroups, configUsers] = await getOSDAdminConfigFromYMLConfig(this.globalConfig$);
updateDashboardAdminStateForRequest(request, groups, users, configGroups, configUsers);
return toolkit.next();
});
Expand Down
25 changes: 0 additions & 25 deletions src/plugins/workspace/server/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,19 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { AuthStatus } from '../../../core/server';
import {
httpServerMock,
httpServiceMock,
savedObjectsClientMock,
uiSettingsServiceMock,
} from '../../../core/server/mocks';
import {
generateRandomId,
getOSDAdminConfigFromYMLConfig,
updateDashboardAdminStateForRequest,
transferCurrentUserInPermissions,
getDataSourcesList,
checkAndSetDefaultDataSource,
} from './utils';
import { getWorkspaceState } from '../../../core/server/utils';
import { Observable, of } from 'rxjs';
import { DEFAULT_DATA_SOURCE_UI_SETTINGS_ID } from '../../data_source_management/common';
import { OSD_ADMIN_WILDCARD_MATCH_ALL } from '../common/constants';

Expand Down Expand Up @@ -97,27 +93,6 @@ describe('workspace utils', () => {
expect(getWorkspaceState(mockRequest)?.isDashboardAdmin).toBe(true);
});

it('should get correct admin config when admin config is enabled ', async () => {
const globalConfig$: Observable<any> = of({
opensearchDashboards: {
dashboardAdmin: {
groups: ['group1', 'group2'],
users: ['user1', 'user2'],
},
},
});
const [groups, users] = await getOSDAdminConfigFromYMLConfig(globalConfig$);
expect(groups).toEqual(['group1', 'group2']);
expect(users).toEqual(['user1', 'user2']);
});

it('should get [] when admin config is not enabled', async () => {
const globalConfig$: Observable<any> = of({});
const [groups, users] = await getOSDAdminConfigFromYMLConfig(globalConfig$);
expect(groups).toEqual([]);
expect(users).toEqual([]);
});

it('should transfer current user placeholder in permissions', () => {
expect(transferCurrentUserInPermissions('foo', undefined)).toBeUndefined();
expect(
Expand Down
14 changes: 0 additions & 14 deletions src/plugins/workspace/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,8 @@
*/

import crypto from 'crypto';
import { Observable } from 'rxjs';
import { first } from 'rxjs/operators';
import {
OpenSearchDashboardsRequest,
SharedGlobalConfig,
Permissions,
SavedObjectsClientContract,
IUiSettingsClient,
Expand Down Expand Up @@ -53,17 +50,6 @@ export const updateDashboardAdminStateForRequest = (
});
};

export const getOSDAdminConfigFromYMLConfig = async (
globalConfig$: Observable<SharedGlobalConfig>
) => {
const globalConfig = await globalConfig$.pipe(first()).toPromise();
const groupsResult = (globalConfig.opensearchDashboards?.dashboardAdmin?.groups ||
[]) as string[];
const usersResult = (globalConfig.opensearchDashboards?.dashboardAdmin?.users || []) as string[];

return [groupsResult, usersResult];
};

export const transferCurrentUserInPermissions = (
realUserId: string,
permissions: Permissions | undefined
Expand Down
Loading