Skip to content

Commit

Permalink
add permissions fields to workspace CRUD APIs
Browse files Browse the repository at this point in the history
Signed-off-by: Lin Wang <[email protected]>
  • Loading branch information
wanglam committed Feb 28, 2024
1 parent 7db84d9 commit b0be203
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/core/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export {
StringValidationRegex,
StringValidationRegexString,
WorkspaceObject,
WorkspaceAttribute,
} from '../types';

export {
Expand Down
12 changes: 6 additions & 6 deletions src/core/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ export const WORKSPACE_TYPE = 'workspace';

export const WORKSPACE_PATH_PREFIX = '/w';

export const PUBLIC_WORKSPACE_ID = 'public';

export const MANAGEMENT_WORKSPACE_ID = 'management';

export const PERSONAL_WORKSPACE_ID_PREFIX = 'personal';

export enum WorkspacePermissionMode {
Read = 'read',
Write = 'write',
LibraryRead = 'library_read',
LibraryWrite = 'library_write',
}

export const PUBLIC_WORKSPACE_ID = 'public';

export const MANAGEMENT_WORKSPACE_ID = 'management';

export const PERSONAL_WORKSPACE_ID_PREFIX = 'personal';
69 changes: 69 additions & 0 deletions src/plugins/workspace/public/workspace_client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { WorkspacePermissionMode } from '../../../core/public';
import { httpServiceMock, workspacesServiceMock } from '../../../core/public/mocks';
import { WorkspaceClient } from './workspace_client';

Expand Down Expand Up @@ -104,6 +105,40 @@ describe('#WorkspaceClient', () => {
});
});

it('#create with permissions', async () => {
const { workspaceClient, httpSetupMock } = getWorkspaceClient();
httpSetupMock.fetch.mockResolvedValue({
success: true,
result: {
name: 'foo',
workspaces: [],
},
});
await workspaceClient.create(
{
name: 'foo',
},
[{ type: 'user', userId: 'foo', modes: [WorkspacePermissionMode.LibraryWrite] }]
);
expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces', {
method: 'POST',
body: JSON.stringify({
attributes: {
name: 'foo',
},
permissions: [
{ type: 'user', userId: 'foo', modes: [WorkspacePermissionMode.LibraryWrite] },
],
}),
});
expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', {
method: 'POST',
body: JSON.stringify({
perPage: 999,
}),
});
});

it('#delete', async () => {
const { workspaceClient, httpSetupMock } = getWorkspaceClient();
httpSetupMock.fetch.mockResolvedValue({
Expand Down Expand Up @@ -178,4 +213,38 @@ describe('#WorkspaceClient', () => {
}),
});
});

it('#update with permissions', async () => {
const { workspaceClient, httpSetupMock } = getWorkspaceClient();
httpSetupMock.fetch.mockResolvedValue({
success: true,
result: {
workspaces: [],
},
});
await workspaceClient.update(
'foo',
{
name: 'foo',
},
[{ type: 'user', userId: 'foo', modes: [WorkspacePermissionMode.LibraryWrite] }]
);
expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/foo', {
method: 'PUT',
body: JSON.stringify({
attributes: {
name: 'foo',
},
permissions: [
{ type: 'user', userId: 'foo', modes: [WorkspacePermissionMode.LibraryWrite] },
],
}),
});
expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', {
method: 'POST',
body: JSON.stringify({
perPage: 999,
}),
});
});
});
18 changes: 16 additions & 2 deletions src/plugins/workspace/public/workspace_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
HttpSetup,
WorkspaceAttribute,
WorkspacesSetup,
WorkspacePermissionMode,
} from '../../../core/public';

const WORKSPACES_API_BASE_URL = '/api/workspaces';
Expand All @@ -29,6 +30,15 @@ type IResponse<T> =
error?: string;
};

type WorkspacePermissionItem = {
modes: Array<
| WorkspacePermissionMode.LibraryRead
| WorkspacePermissionMode.LibraryWrite
| WorkspacePermissionMode.Read
| WorkspacePermissionMode.Write
>;
} & ({ type: 'user'; userId: string } | { type: 'group'; group: string });

interface WorkspaceFindOptions {
page?: number;
perPage?: number;
Expand Down Expand Up @@ -151,14 +161,16 @@ export class WorkspaceClient {
* @returns
*/
public async create(
attributes: Omit<WorkspaceAttribute, 'id'>
attributes: Omit<WorkspaceAttribute, 'id'>,
permissions?: WorkspacePermissionItem[]
): Promise<IResponse<WorkspaceAttribute>> {
const path = this.getPath();

const result = await this.safeFetch<WorkspaceAttribute>(path, {
method: 'POST',
body: JSON.stringify({
attributes,
permissions,
}),
});

Expand Down Expand Up @@ -236,11 +248,13 @@ export class WorkspaceClient {
*/
public async update(
id: string,
attributes: Partial<WorkspaceAttribute>
attributes: Partial<WorkspaceAttribute>,
permissions?: WorkspacePermissionItem[]
): Promise<IResponse<boolean>> {
const path = this.getPath(id);
const body = {
attributes,
permissions,
};

const result = await this.safeFetch(path, {
Expand Down
67 changes: 67 additions & 0 deletions src/plugins/workspace/server/integration_tests/routes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import { WorkspaceAttribute } from 'src/core/types';
import * as osdTestServer from '../../../../core/test_helpers/osd_server';
import { WORKSPACE_TYPE } from '../../../../core/server';
import { WorkspacePermissionItem } from '../types';

const omitId = <T extends { id?: string }>(object: T): Omit<T, 'id'> => {
const { id, ...others } = object;
Expand Down Expand Up @@ -83,6 +84,39 @@ describe('workspace service', () => {
expect(result.body.success).toEqual(true);
expect(typeof result.body.result.id).toBe('string');
});
it('create with permissions', async () => {
await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omitId(testWorkspace),
permissions: [{ type: 'invalid-type', userId: 'foo', modes: ['read'] }],
})
.expect(400);

const result: any = await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omitId(testWorkspace),
permissions: [{ type: 'user', userId: 'foo', modes: ['read'] }],
})
.expect(200);

expect(result.body.success).toEqual(true);
expect(typeof result.body.result.id).toBe('string');
expect(
(
await osd.coreStart.savedObjects
.createInternalRepository([WORKSPACE_TYPE])
.get<{ permissions: WorkspacePermissionItem[] }>(WORKSPACE_TYPE, result.body.result.id)
).attributes.permissions
).toEqual([
{
modes: ['read'],
type: 'user',
userId: 'foo',
},
]);
});
it('get', async () => {
const result = await osdTestServer.request
.post(root, `/api/workspaces`)
Expand Down Expand Up @@ -123,6 +157,39 @@ describe('workspace service', () => {
expect(getResult.body.success).toEqual(true);
expect(getResult.body.result.name).toEqual('updated');
});
it('update with permissions', async () => {
const result: any = await osdTestServer.request
.post(root, `/api/workspaces`)
.send({
attributes: omitId(testWorkspace),
permissions: [{ type: 'user', userId: 'foo', modes: ['read'] }],
})
.expect(200);

await osdTestServer.request
.put(root, `/api/workspaces/${result.body.result.id}`)
.send({
attributes: {
...omitId(testWorkspace),
},
permissions: [{ type: 'user', userId: 'foo', modes: ['write'] }],
})
.expect(200);

expect(
(
await osd.coreStart.savedObjects
.createInternalRepository([WORKSPACE_TYPE])
.get<{ permissions: WorkspacePermissionItem[] }>(WORKSPACE_TYPE, result.body.result.id)
).attributes.permissions
).toEqual([
{
modes: ['write'],
type: 'user',
userId: 'foo',
},
]);
});
it('delete', async () => {
const result: any = await osdTestServer.request
.post(root, `/api/workspaces`)
Expand Down
63 changes: 59 additions & 4 deletions src/plugins/workspace/server/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,36 @@
*/

import { schema } from '@osd/config-schema';
import { CoreSetup, Logger } from '../../../../core/server';
import { IWorkspaceClientImpl } from '../types';
import {
CoreSetup,
Logger,
WorkspacePermissionMode,
ensureRawRequest,
} from '../../../../core/server';
import { IWorkspaceClientImpl, WorkspacePermissionItem } from '../types';

const WORKSPACES_API_BASE_URL = '/api/workspaces';

const workspacePermissionMode = schema.oneOf([
schema.literal(WorkspacePermissionMode.Read),
schema.literal(WorkspacePermissionMode.Write),
schema.literal(WorkspacePermissionMode.LibraryRead),
schema.literal(WorkspacePermissionMode.LibraryWrite),
]);

const workspacePermission = schema.oneOf([
schema.object({
type: schema.literal('user'),
userId: schema.string(),
modes: schema.arrayOf(workspacePermissionMode),
}),
schema.object({
type: schema.literal('group'),
group: schema.string(),
modes: schema.arrayOf(workspacePermissionMode),
}),
]);

const workspaceAttributesSchema = schema.object({
description: schema.maybe(schema.string()),
name: schema.string(),
Expand Down Expand Up @@ -40,6 +65,7 @@ export function registerRoutes({
page: schema.number({ min: 0, defaultValue: 1 }),
sortField: schema.maybe(schema.string()),
searchFields: schema.maybe(schema.arrayOf(schema.string())),
permissionModes: schema.maybe(schema.arrayOf(workspacePermissionMode)),
}),
},
},
Expand Down Expand Up @@ -94,11 +120,31 @@ export function registerRoutes({
validate: {
body: schema.object({
attributes: workspaceAttributesSchema,
permissions: schema.maybe(
schema.oneOf([workspacePermission, schema.arrayOf(workspacePermission)])
),
}),
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { attributes } = req.body;
const { attributes, permissions: permissionsInRequest } = req.body;
const rawRequest = ensureRawRequest(req);
const authInfo = rawRequest?.auth?.credentials?.authInfo as { user_name?: string } | null;
let permissions: WorkspacePermissionItem[] = [];
if (permissionsInRequest) {
permissions = Array.isArray(permissionsInRequest)
? permissionsInRequest
: [permissionsInRequest];
}

// Assign workspace owner to current user
if (!!authInfo?.user_name) {
permissions.push({
type: 'user',
userId: authInfo.user_name,
modes: [WorkspacePermissionMode.LibraryWrite, WorkspacePermissionMode.Write],
});
}

const result = await client.create(
{
Expand All @@ -108,6 +154,7 @@ export function registerRoutes({
},
{
...attributes,
...(permissions.length ? { permissions } : {}),
}
);
return res.ok({ body: result });
Expand All @@ -122,12 +169,19 @@ export function registerRoutes({
}),
body: schema.object({
attributes: workspaceAttributesSchema,
permissions: schema.maybe(
schema.oneOf([workspacePermission, schema.arrayOf(workspacePermission)])
),
}),
},
},
router.handleLegacyErrors(async (context, req, res) => {
const { id } = req.params;
const { attributes } = req.body;
const { attributes, permissions } = req.body;
let finalPermissions: WorkspacePermissionItem[] = [];
if (permissions) {
finalPermissions = Array.isArray(permissions) ? permissions : [permissions];
}

const result = await client.update(
{
Expand All @@ -138,6 +192,7 @@ export function registerRoutes({
id,
{
...attributes,
...(finalPermissions.length ? { permissions: finalPermissions } : {}),
}
);
return res.ok({ body: result });
Expand Down
Loading

0 comments on commit b0be203

Please sign in to comment.