Skip to content

Commit

Permalink
Permission control service for saved objects (opensearch-project#63)
Browse files Browse the repository at this point in the history
* feat: move permission control to saved objects directory

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: use bulkGetObjects and fix unit test

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: add http routes for validate & list

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: move permissionModes to common place

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: rename routes

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: some side effects

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

* feat: some side effects

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>

---------

Signed-off-by: SuZhou-Joe <suzhou@amazon.com>
  • Loading branch information
SuZhou-Joe authored and ruanyl committed Aug 3, 2023
1 parent 614cf9a commit cde45c5
Show file tree
Hide file tree
Showing 15 changed files with 273 additions and 62 deletions.
1 change: 1 addition & 0 deletions src/core/server/legacy/legacy_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,7 @@ 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: 1 addition & 0 deletions src/core/server/plugins/plugin_context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ 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
15 changes: 15 additions & 0 deletions src/core/server/saved_objects/permission_control/client.mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { SavedObjectsPermissionControlContract } from './client';

export const savedObjectsPermissionControlMock: SavedObjectsPermissionControlContract = {
setup: jest.fn(),
validate: jest.fn(),
addPrinciplesToObjects: jest.fn(),
removePrinciplesFromObjects: jest.fn(),
getPrinciplesOfObjects: jest.fn(),
getPermittedWorkspaceIds: jest.fn(),
};
83 changes: 83 additions & 0 deletions src/core/server/saved_objects/permission_control/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { OpenSearchDashboardsRequest } from '../../http';
import { SavedObjectsServiceStart } from '../saved_objects_service';
import { SavedObjectsBulkGetObject } from '../service';

export type SavedObjectsPermissionControlContract = Pick<
SavedObjectsPermissionControl,
keyof SavedObjectsPermissionControl
>;

export type SavedObjectsPermissionModes = string[];

export class SavedObjectsPermissionControl {
private getScopedClient?: SavedObjectsServiceStart['getScopedClient'];
private getScopedSavedObjectsClient(request: OpenSearchDashboardsRequest) {
return this.getScopedClient?.(request);
}
private async bulkGetSavedObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[]
) {
return (
(await this.getScopedSavedObjectsClient(request)?.bulkGet(savedObjects))?.saved_objects || []
);
}
public async setup(getScopedClient: SavedObjectsServiceStart['getScopedClient']) {
this.getScopedClient = getScopedClient;
}
public async validate(
request: OpenSearchDashboardsRequest,
savedObject: SavedObjectsBulkGetObject,
permissionModeOrModes: SavedObjectsPermissionModes
) {
const savedObjectsGet = await this.bulkGetSavedObjects(request, [savedObject]);
if (savedObjectsGet) {
return {
success: true,
result: true,
};
}

return {
success: true,
result: false,
};
}

public async addPrinciplesToObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[],
personas: string[],
permissionModeOrModes: SavedObjectsPermissionModes
): Promise<boolean> {
return true;
}

public async removePrinciplesFromObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[],
personas: string[],
permissionModeOrModes: SavedObjectsPermissionModes
): Promise<boolean> {
return true;
}

public async getPrinciplesOfObjects(
request: OpenSearchDashboardsRequest,
savedObjects: SavedObjectsBulkGetObject[]
): Promise<Record<string, unknown>> {
return {};
}

public async getPermittedWorkspaceIds(
request: OpenSearchDashboardsRequest,
permissionModeOrModes: SavedObjectsPermissionModes
) {
return [];
}
}
20 changes: 20 additions & 0 deletions src/core/server/saved_objects/permission_control/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { InternalHttpServiceSetup } from '../../../http';
import { SavedObjectsPermissionControlContract } from '../client';
import { registerValidateRoute } from './validate';

export function registerPermissionCheckRoutes({
http,
permissionControl,
}: {
http: InternalHttpServiceSetup;
permissionControl: SavedObjectsPermissionControlContract;
}) {
const router = http.createRouter('/api/saved_objects_permission_control/');

registerValidateRoute(router, permissionControl);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

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

export const registerListRoute = (
router: IRouter,
permissionControl: SavedObjectsPermissionControlContract
) => {
router.post(
{
path: '/principles',
validate: {
body: schema.object({
objects: schema.arrayOf(
schema.object({
type: schema.string(),
id: schema.string(),
})
),
}),
},
},
router.handleLegacyErrors(async (context, req, res) => {
const result = await permissionControl.getPrinciplesOfObjects(req, req.body.objects);
return res.ok({ body: result });
})
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

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

export const registerValidateRoute = (
router: IRouter,
permissionControl: SavedObjectsPermissionControlContract
) => {
router.post(
{
path: '/validate/{type}/{id}',
validate: {
params: schema.object({
type: schema.string(),
id: schema.string(),
}),
body: schema.object({
permissionModes: schema.arrayOf(schema.string()),
}),
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { type, id } = req.params;
const result = await permissionControl.validate(
req,
{
type,
id,
},
req.body.permissionModes
);
return res.ok({ body: result });
})
);
};
2 changes: 2 additions & 0 deletions src/core/server/saved_objects/saved_objects_service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ 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 @@ -80,6 +81,7 @@ const createSetupContractMock = () => {
registerType: jest.fn(),
getImportExportObjectLimit: jest.fn(),
setRepositoryFactoryProvider: jest.fn(),
permissionControl: savedObjectsPermissionControlMock,
};

setupContract.getImportExportObjectLimit.mockReturnValue(100);
Expand Down
21 changes: 20 additions & 1 deletion src/core/server/saved_objects/saved_objects_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ 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 @@ -175,6 +180,8 @@ export interface SavedObjectsServiceSetup {
setRepositoryFactoryProvider: (
respositoryFactoryProvider: SavedObjectRepositoryFactoryProvider
) => void;

permissionControl: SavedObjectsPermissionControlContract;
}

/**
Expand Down Expand Up @@ -301,6 +308,7 @@ 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 @@ -328,6 +336,13 @@ export class SavedObjectsService
migratorPromise: this.migrator$.pipe(first()).toPromise(),
});

this.permissionControl = new SavedObjectsPermissionControl();

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

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

Expand Down Expand Up @@ -483,8 +499,11 @@ export class SavedObjectsService

this.started = true;

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

return {
getScopedClient: clientProvider.getClient.bind(clientProvider),
getScopedClient,
createScopedRepository: repositoryFactory.createScopedRepository,
createInternalRepository: repositoryFactory.createInternalRepository,
createSerializer: () => new SavedObjectsSerializer(this.typeRegistry),
Expand Down
1 change: 0 additions & 1 deletion src/core/server/workspaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,5 @@ export {

export { WorkspaceAttribute, WorkspaceFindOptions } from './types';

export { WorkspacePermissionControl } from './workspace_permission_control';
export { workspacesValidator, formatWorkspaces } from './utils';
export { WORKSPACE_TYPE } from './constants';
Loading

0 comments on commit cde45c5

Please sign in to comment.