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

Feat: setup reserved workspaces when calling list workspaces API #178

Merged
merged 8 commits into from
Sep 20, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export const savedObjectsPermissionControlMock: SavedObjectsPermissionControlCon
batchValidate: jest.fn(),
getPrincipalsOfObjects: jest.fn(),
getPermittedWorkspaceIds: jest.fn(),
getPrincipalsFromRequest: jest.fn(),
setup: jest.fn(),
};
31 changes: 16 additions & 15 deletions src/plugins/workspace/server/permission_control/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,19 +29,7 @@ export interface AuthInfo {
}

export class SavedObjectsPermissionControl {
private readonly logger: Logger;
private _getScopedClient?: SavedObjectsServiceStart['getScopedClient'];
private getScopedClient(request: OpenSearchDashboardsRequest) {
return this._getScopedClient?.(request, {
excludedWrappers: [WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID],
});
}

constructor(logger: Logger) {
this.logger = logger;
}

public getPrincipalsFromRequest(request: OpenSearchDashboardsRequest): Principals {
static getPrincipalsFromRequest(request: OpenSearchDashboardsRequest): Principals {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I might just move this out of the class and make it a pure util function

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, changed that.

const rawRequest = ensureRawRequest(request);
const authInfo = rawRequest?.auth?.credentials?.authInfo as AuthInfo | null;
const payload: Principals = {};
Expand All @@ -68,6 +56,19 @@ export class SavedObjectsPermissionControl {
}
return payload;
}

private readonly logger: Logger;
private _getScopedClient?: SavedObjectsServiceStart['getScopedClient'];
private getScopedClient(request: OpenSearchDashboardsRequest) {
return this._getScopedClient?.(request, {
excludedWrappers: [WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID],
});
}

constructor(logger: Logger) {
this.logger = logger;
}

private async bulkGetSavedObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[]
Expand Down Expand Up @@ -114,7 +115,7 @@ export class SavedObjectsPermissionControl {
};
}

const principals = this.getPrincipalsFromRequest(request);
const principals = SavedObjectsPermissionControl.getPrincipalsFromRequest(request);
let savedObjectsBasicInfo: any[] = [];
const hasAllPermission = savedObjectsGet.every((item) => {
// for object that doesn't contain ACL like config, return true
Expand Down Expand Up @@ -168,7 +169,7 @@ export class SavedObjectsPermissionControl {
request: OpenSearchDashboardsRequest,
permissionModes: SavedObjectsPermissionModes
) {
const principals = this.getPrincipalsFromRequest(request);
const principals = SavedObjectsPermissionControl.getPrincipalsFromRequest(request);
const savedObjectClient = this.getScopedClient?.(request);
try {
const result = await savedObjectClient?.find({
Expand Down
102 changes: 3 additions & 99 deletions src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,19 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { i18n } from '@osd/i18n';
import { Observable } from 'rxjs';
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
ISavedObjectsRepository,
WORKSPACE_TYPE,
ACL,
PUBLIC_WORKSPACE_ID,
MANAGEMENT_WORKSPACE_ID,
Permissions,
WorkspacePermissionMode,
SavedObjectsClient,
WorkspaceAttribute,
DEFAULT_APP_CATEGORIES,
} from '../../../core/server';
import { IWorkspaceDBImpl } from './types';
import { WorkspaceClientWithSavedObject } from './workspace_client';
import { WorkspaceSavedObjectsClientWrapper } from './saved_objects';
import { registerRoutes } from './routes';
import {
WORKSPACE_OVERVIEW_APP_ID,
WORKSPACE_UPDATE_APP_ID,
WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID,
} from '../common/constants';
import { ConfigSchema } from '../config';
import { WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID } from '../common/constants';
import {
SavedObjectsPermissionControl,
SavedObjectsPermissionControlContract,
Expand All @@ -40,7 +24,6 @@ import { registerPermissionCheckRoutes } from './permission_control/routes';
export class WorkspacePlugin implements Plugin<{}, {}> {
private readonly logger: Logger;
private client?: IWorkspaceDBImpl;
private config$: Observable<ConfigSchema>;
private permissionControl?: SavedObjectsPermissionControlContract;

private proxyWorkspaceTrafficToRealHandler(setupDeps: CoreSetup) {
Expand All @@ -62,13 +45,12 @@ export class WorkspacePlugin implements Plugin<{}, {}> {

constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get('plugins', 'workspace');
this.config$ = initializerContext.config.create<ConfigSchema>();
}

public async setup(core: CoreSetup) {
this.logger.debug('Setting up Workspaces service');

this.client = new WorkspaceClientWithSavedObject(core);
this.client = new WorkspaceClientWithSavedObject(core, this.logger);

await this.client.setup(core);
this.permissionControl = new SavedObjectsPermissionControl(this.logger);
Expand All @@ -79,10 +61,7 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
});

const workspaceSavedObjectsClientWrapper = new WorkspaceSavedObjectsClientWrapper(
this.permissionControl,
{
config$: this.config$,
}
this.permissionControl
);

core.savedObjects.addClientWrapper(
Expand All @@ -108,85 +87,10 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
};
}

private async checkAndCreateWorkspace(
internalRepository: ISavedObjectsRepository,
workspaceId: string,
workspaceAttribute: Omit<WorkspaceAttribute, 'id' | 'permissions'>,
permissions?: Permissions
) {
/**
* Internal repository is attached to global tenant.
*/
try {
await internalRepository.get(WORKSPACE_TYPE, workspaceId);
} catch (error) {
this.logger.debug(error?.toString() || '');
this.logger.info(`Workspace ${workspaceId} is not found, create it by using internal user`);
try {
const createResult = await internalRepository.create(WORKSPACE_TYPE, workspaceAttribute, {
id: workspaceId,
permissions,
});
if (createResult.id) {
this.logger.info(`Created workspace ${createResult.id} in global tenant.`);
}
} catch (e) {
this.logger.error(`Create ${workspaceId} workspace error: ${e?.toString() || ''}`);
}
}
}

private async setupWorkspaces(startDeps: CoreStart) {
const internalRepository = startDeps.savedObjects.createInternalRepository();
const publicWorkspaceACL = new ACL().addPermission(
[WorkspacePermissionMode.LibraryRead, WorkspacePermissionMode.LibraryWrite],
{
users: ['*'],
}
);
const managementWorkspaceACL = new ACL().addPermission([WorkspacePermissionMode.LibraryRead], {
users: ['*'],
});
const DSM_APP_ID = 'dataSources';
const DEV_TOOLS_APP_ID = 'dev_tools';

await Promise.all([
this.checkAndCreateWorkspace(
internalRepository,
PUBLIC_WORKSPACE_ID,
{
name: i18n.translate('workspaces.public.workspace.default.name', {
defaultMessage: 'public',
}),
features: ['*', `!@${DEFAULT_APP_CATEGORIES.management.id}`],
},
publicWorkspaceACL.getPermissions()
),
this.checkAndCreateWorkspace(
internalRepository,
MANAGEMENT_WORKSPACE_ID,
{
name: i18n.translate('workspaces.management.workspace.default.name', {
defaultMessage: 'Management',
}),
features: [
`@${DEFAULT_APP_CATEGORIES.management.id}`,
WORKSPACE_OVERVIEW_APP_ID,
WORKSPACE_UPDATE_APP_ID,
DSM_APP_ID,
DEV_TOOLS_APP_ID,
],
},
managementWorkspaceACL.getPermissions()
),
]);
}

public start(core: CoreStart) {
this.logger.debug('Starting SavedObjects service');
this.permissionControl?.setup(core.savedObjects.getScopedClient);
this.client?.setSavedObjects(core.savedObjects);
this.setupWorkspaces(core);

return {
client: this.client as IWorkspaceDBImpl,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ import {
SavedObjectsDeleteByWorkspaceOptions,
SavedObjectsErrorHelpers,
} from '../../../../core/server';
import { SavedObjectsPermissionControlContract } from '../permission_control/client';
import {
SavedObjectsPermissionControl,
SavedObjectsPermissionControlContract,
} from '../permission_control/client';
import { WorkspaceFindOptions } from '../types';

// Can't throw unauthorized for now, the page will be refreshed if unauthorized
Expand Down Expand Up @@ -355,7 +358,9 @@ export class WorkspaceSavedObjectsClientWrapper {
const findWithWorkspacePermissionControl = async <T = unknown>(
options: SavedObjectsFindOptions & Pick<WorkspaceFindOptions, 'permissionModes'>
) => {
const principals = this.permissionControl.getPrincipalsFromRequest(wrapperOptions.request);
const principals = SavedObjectsPermissionControl.getPrincipalsFromRequest(
wrapperOptions.request
);
if (!options.ACLSearchParams) {
options.ACLSearchParams = {};
}
Expand Down
Loading
Loading