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

Refractor: move permission control service to workspace directory and keep saved objects as clean as possible #166

Merged
merged 2 commits into from
Sep 15, 2023
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
4 changes: 3 additions & 1 deletion src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,9 @@ export {
SavedObjectsDeleteByWorkspaceOptions,
Permissions,
ACL,
SavedObjectsPermissionControlContract,
Principals,
TransformedPermission,
PrincipalType,
} from './saved_objects';

export {
Expand Down
1 change: 0 additions & 1 deletion src/core/server/legacy/legacy_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,6 @@ export class LegacyService implements CoreService {
registerType: setupDeps.core.savedObjects.registerType,
getImportExportObjectLimit: setupDeps.core.savedObjects.getImportExportObjectLimit,
setRepositoryFactoryProvider: setupDeps.core.savedObjects.setRepositoryFactoryProvider,
permissionControl: setupDeps.core.savedObjects.permissionControl,
},
status: {
isStatusPageAnonymous: setupDeps.core.status.isStatusPageAnonymous,
Expand Down
1 change: 0 additions & 1 deletion src/core/server/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,6 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>(
registerType: deps.savedObjects.registerType,
getImportExportObjectLimit: deps.savedObjects.getImportExportObjectLimit,
setRepositoryFactoryProvider: deps.savedObjects.setRepositoryFactoryProvider,
permissionControl: deps.savedObjects.permissionControl,
},
status: {
core$: deps.status.core$,
Expand Down
9 changes: 7 additions & 2 deletions src/core/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,10 @@ export {
export { savedObjectsConfig, savedObjectsMigrationConfig } from './saved_objects_config';
export { SavedObjectTypeRegistry, ISavedObjectTypeRegistry } from './saved_objects_type_registry';

export { Permissions, ACL } from './permission_control/acl';
export { SavedObjectsPermissionControlContract } from './permission_control/client';
export {
Permissions,
ACL,
Principals,
TransformedPermission,
PrincipalType,
} from './permission_control/acl';
2 changes: 0 additions & 2 deletions src/core/server/saved_objects/saved_objects_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ import { typeRegistryMock } from './saved_objects_type_registry.mock';
import { migrationMocks } from './migrations/mocks';
import { ServiceStatusLevels } from '../status';
import { ISavedObjectTypeRegistry } from './saved_objects_type_registry';
import { savedObjectsPermissionControlMock } from './permission_control/client.mock';

type SavedObjectsServiceContract = PublicMethodsOf<SavedObjectsService>;

Expand Down Expand Up @@ -81,7 +80,6 @@ const createSetupContractMock = () => {
registerType: jest.fn(),
getImportExportObjectLimit: jest.fn(),
setRepositoryFactoryProvider: jest.fn(),
permissionControl: savedObjectsPermissionControlMock,
};

setupContract.getImportExportObjectLimit.mockReturnValue(100);
Expand Down
17 changes: 0 additions & 17 deletions src/core/server/saved_objects/saved_objects_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,6 @@ import { registerRoutes } from './routes';
import { ServiceStatus } from '../status';
import { calculateStatus$ } from './status';
import { createMigrationOpenSearchClient } from './migrations/core/';
import {
SavedObjectsPermissionControl,
SavedObjectsPermissionControlContract,
} from './permission_control/client';
import { registerPermissionCheckRoutes } from './permission_control/routes';
/**
* Saved Objects is OpenSearchDashboards's data persistence mechanism allowing plugins to
* use OpenSearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods
Expand Down Expand Up @@ -180,8 +175,6 @@ export interface SavedObjectsServiceSetup {
setRepositoryFactoryProvider: (
respositoryFactoryProvider: SavedObjectRepositoryFactoryProvider
) => void;

permissionControl: SavedObjectsPermissionControlContract;
}

/**
Expand Down Expand Up @@ -308,7 +301,6 @@ export class SavedObjectsService
private started = false;

private respositoryFactoryProvider?: SavedObjectRepositoryFactoryProvider;
private permissionControl?: SavedObjectsPermissionControlContract;

constructor(private readonly coreContext: CoreContext) {
this.logger = coreContext.logger.get('savedobjects-service');
Expand Down Expand Up @@ -336,13 +328,6 @@ export class SavedObjectsService
migratorPromise: this.migrator$.pipe(first()).toPromise(),
});

this.permissionControl = new SavedObjectsPermissionControl(this.logger);

registerPermissionCheckRoutes({
http: setupDeps.http,
permissionControl: this.permissionControl,
});

return {
status$: calculateStatus$(
this.migrator$.pipe(switchMap((migrator) => migrator.getStatus$())),
Expand Down Expand Up @@ -383,7 +368,6 @@ export class SavedObjectsService
}
this.respositoryFactoryProvider = repositoryProvider;
},
permissionControl: this.permissionControl,
};
}

Expand Down Expand Up @@ -500,7 +484,6 @@ export class SavedObjectsService
this.started = true;

const getScopedClient = clientProvider.getClient.bind(clientProvider);
this.permissionControl?.setup(repositoryFactory.createInternalRepository);

return {
getScopedClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import { SavedObjectsPermissionControlContract } from './client';

export const savedObjectsPermissionControlMock: SavedObjectsPermissionControlContract = {
setup: jest.fn(),
validate: jest.fn(),
batchValidate: jest.fn(),
getPrincipalsOfObjects: jest.fn(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { i18n } from '@osd/i18n';
import { OpenSearchDashboardsRequest } from '../../http';
import { ensureRawRequest } from '../../http/router';
import { SavedObjectsServiceStart } from '../saved_objects_service';
import { SavedObjectsBulkGetObject } from '../service';
import { ACL, Principals, TransformedPermission, PrincipalType } from './acl';
import { Logger } from '../../logging';
import { WORKSPACE_TYPE } from '../../../utils';
import { ensureRawRequest, OpenSearchDashboardsRequest } from '../../../../core/server';
import {
ACL,
Principals,
TransformedPermission,
PrincipalType,
SavedObjectsBulkGetObject,
SavedObjectsServiceStart,
Logger,
WORKSPACE_TYPE,
} from '../../../../core/server';
import { WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID } from '../../common/constants';

export type SavedObjectsPermissionControlContract = Pick<
SavedObjectsPermissionControl,
Expand All @@ -25,9 +30,11 @@ export interface AuthInfo {

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

constructor(logger: Logger) {
Expand Down Expand Up @@ -65,12 +72,10 @@ export class SavedObjectsPermissionControl {
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[]
) {
return (await this.getInternalRepository()?.bulkGet(savedObjects))?.saved_objects || [];
return (await this.getScopedClient?.(request)?.bulkGet(savedObjects))?.saved_objects || [];
}
public async setup(
createInternalRepository: SavedObjectsServiceStart['createInternalRepository']
) {
this.createInternalRepository = createInternalRepository;
public async setup(getScopedClient: SavedObjectsServiceStart['getScopedClient']) {
this._getScopedClient = getScopedClient;
}
public async validate(
request: OpenSearchDashboardsRequest,
Expand Down Expand Up @@ -164,9 +169,9 @@ export class SavedObjectsPermissionControl {
permissionModes: SavedObjectsPermissionModes
) {
const principals = this.getPrincipalsFromRequest(request);
const repository = this.getInternalRepository();
const savedObjectClient = this.getScopedClient?.(request);
try {
const result = await repository?.find({
const result = await savedObjectClient?.find({
type: [WORKSPACE_TYPE],
ACLSearchParams: {
permissionModes,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { InternalHttpServiceSetup } from '../../../http';
import { HttpServiceSetup } from '../../../../../core/server';
import { SavedObjectsPermissionControlContract } from '../client';
import { registerListRoute } from './principals';
import { registerValidateRoute } from './validate';
Expand All @@ -12,10 +12,10 @@ export function registerPermissionCheckRoutes({
http,
permissionControl,
}: {
http: InternalHttpServiceSetup;
http: HttpServiceSetup;
permissionControl: SavedObjectsPermissionControlContract;
}) {
const router = http.createRouter('/api/saved_objects_permission_control/');
const router = http.createRouter();

registerValidateRoute(router, permissionControl);
registerListRoute(router, permissionControl);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
*/

import { schema } from '@osd/config-schema';
import { IRouter } from '../../../http';
import { IRouter } from '../../../../../core/server';
import { SavedObjectsPermissionControlContract } from '../client';
import { WORKSPACES_API_BASE_URL } from '../../routes';

export const registerListRoute = (
router: IRouter,
permissionControl: SavedObjectsPermissionControlContract
) => {
router.post(
{
path: '/principals',
path: `${WORKSPACES_API_BASE_URL}/principals`,
validate: {
body: schema.object({
objects: schema.arrayOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,17 @@
*/

import { schema } from '@osd/config-schema';
import { IRouter } from '../../../http';
import { IRouter } from '../../../../../core/server';
import { SavedObjectsPermissionControlContract } from '../client';
import { WORKSPACES_API_BASE_URL } from '../../routes';

export const registerValidateRoute = (
router: IRouter,
permissionControl: SavedObjectsPermissionControlContract
) => {
router.post(
{
path: '/validate/{type}/{id}',
path: `${WORKSPACES_API_BASE_URL}/validate/{type}/{id}`,
validate: {
params: schema.object({
type: schema.string(),
Expand Down
16 changes: 15 additions & 1 deletion src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ import {
WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID,
} from '../common/constants';
import { ConfigSchema } from '../config';
import {
SavedObjectsPermissionControl,
SavedObjectsPermissionControlContract,
} from './permission_control/client';
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 Down Expand Up @@ -66,8 +72,15 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
this.client = new WorkspaceClientWithSavedObject(core);

await this.client.setup(core);
this.permissionControl = new SavedObjectsPermissionControl(this.logger);

registerPermissionCheckRoutes({
http: core.http,
permissionControl: this.permissionControl,
});

const workspaceSavedObjectsClientWrapper = new WorkspaceSavedObjectsClientWrapper(
core.savedObjects.permissionControl,
this.permissionControl,
{
config$: this.config$,
}
Expand Down Expand Up @@ -168,6 +181,7 @@ export class WorkspacePlugin implements Plugin<{}, {}> {

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

Expand Down
2 changes: 1 addition & 1 deletion src/plugins/workspace/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from '../../../../core/server';
import { IWorkspaceDBImpl, WorkspaceRoutePermissionItem } from '../types';

const WORKSPACES_API_BASE_URL = '/api/workspaces';
export const WORKSPACES_API_BASE_URL = '/api/workspaces';

const workspacePermissionMode = schema.oneOf([
schema.literal(WorkspacePermissionMode.LibraryRead),
Expand Down