Skip to content

Commit

Permalink
dashboard admin(groups/users) implementation and add unit/integration…
Browse files Browse the repository at this point in the history
… test

This reverts commit 47e10e4.
  • Loading branch information
yubonluo committed Mar 20, 2024
1 parent 431fcdb commit 72fc938
Show file tree
Hide file tree
Showing 6 changed files with 410 additions and 6 deletions.
5 changes: 5 additions & 0 deletions config/opensearch_dashboards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -290,3 +290,8 @@

# Set the value to true to enable workspace feature
# workspace.enabled: false

# Set the backend roles in groups or users, whoever has the backend roles defined in this config will be regard as dashboard admin.
# Dashboard admin will have the access to all the workspaces and objects inside OpenSearch Dashboards.
# workspace.dashboardAdmin.groups: ["dashboard_admin"]
# workspace.dashboardAdmin.users: ["dashboard_admin"]
8 changes: 8 additions & 0 deletions src/plugins/workspace/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ export const configSchema = schema.object({
permission: schema.object({
enabled: schema.boolean({ defaultValue: true }),
}),
dashboardAdmin: schema.object({
groups: schema.arrayOf(schema.string(), {
defaultValue: [],
}),
users: schema.arrayOf(schema.string(), {
defaultValue: [],
}),
}),
});

export type WorkspacePluginConfigType = TypeOf<typeof configSchema>;
3 changes: 2 additions & 1 deletion src/plugins/workspace/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export class WorkspacePlugin implements Plugin<{}, {}> {
this.permissionControl = new SavedObjectsPermissionControl(this.logger);

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

core.savedObjects.addClientWrapper(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const repositoryKit = (() => {

const permittedRequest = httpServerMock.createOpenSearchDashboardsRequest();
const notPermittedRequest = httpServerMock.createOpenSearchDashboardsRequest();
const dashboardAdminRequest = httpServerMock.createOpenSearchDashboardsRequest();

describe('WorkspaceSavedObjectsClientWrapper', () => {
let internalSavedObjectsRepository: ISavedObjectsRepository;
Expand All @@ -59,6 +60,7 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
let osd: TestOpenSearchDashboardsUtils;
let permittedSavedObjectedClient: SavedObjectsClientContract;
let notPermittedSavedObjectedClient: SavedObjectsClientContract;
let dashboardAdminSavedObjectedClient: SavedObjectsClientContract;

beforeAll(async function () {
servers = createTestServers({
Expand All @@ -69,6 +71,10 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
osd: {
workspace: {
enabled: true,
dashboardAdmin: {
groups: ['dashboard_admin'],
users: ['dashboard_admin'],
},
},
savedObjects: {
permission: {
Expand Down Expand Up @@ -125,14 +131,19 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
jest.spyOn(utilsExports, 'getPrincipalsFromRequest').mockImplementation((request) => {
if (request === notPermittedRequest) {
return { users: ['bar'] };
} else if (request === permittedRequest) {
return { users: ['foo'] };
}
return { users: ['foo'] };
return { groups: ['dashboard_admin'] };
});

permittedSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient(permittedRequest);
notPermittedSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient(
notPermittedRequest
);
dashboardAdminSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient(
dashboardAdminRequest
);
});

afterAll(async () => {
Expand Down Expand Up @@ -172,6 +183,17 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
(await permittedSavedObjectedClient.get('dashboard', 'acl-controlled-dashboard-2')).error
).toBeUndefined();
});

it('should return consistent dashboard when user is dashboard admin', async () => {
expect(
(await dashboardAdminSavedObjectedClient.get('dashboard', 'inner-workspace-dashboard-1'))
.error
).toBeUndefined();
expect(
(await dashboardAdminSavedObjectedClient.get('dashboard', 'acl-controlled-dashboard-2'))
.error
).toBeUndefined();
});
});

describe('bulkGet', () => {
Expand Down Expand Up @@ -215,6 +237,23 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
).saved_objects.length
).toEqual(1);
});

it('should return consistent dashboard when user is dashboard admin', async () => {
expect(
(
await dashboardAdminSavedObjectedClient.bulkGet([
{ type: 'dashboard', id: 'inner-workspace-dashboard-1' },
])
).saved_objects.length
).toEqual(1);
expect(
(
await dashboardAdminSavedObjectedClient.bulkGet([
{ type: 'dashboard', id: 'acl-controlled-dashboard-2' },
])
).saved_objects.length
).toEqual(1);
});
});

describe('find', () => {
Expand Down Expand Up @@ -246,6 +285,19 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
true
);
});

it('should return consistent inner workspace data when user is dashboard admin', async () => {
const result = await dashboardAdminSavedObjectedClient.find({
type: 'dashboard',
workspaces: ['workspace-1'],
perPage: 999,
page: 1,
});

expect(result.saved_objects.some((item) => item.id === 'inner-workspace-dashboard-1')).toBe(
true
);
});
});

describe('create', () => {
Expand Down Expand Up @@ -278,6 +330,18 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
await permittedSavedObjectedClient.delete('dashboard', createResult.id);
});

it('should able to create saved objects into any workspaces after create called when user is dashboard admin', async () => {
const createResult = await dashboardAdminSavedObjectedClient.create(
'dashboard',
{},
{
workspaces: ['workspace-1'],
}
);
expect(createResult.error).toBeUndefined();
await dashboardAdminSavedObjectedClient.delete('dashboard', createResult.id);
});

it('should throw forbidden error when create with override', async () => {
let error;
try {
Expand Down Expand Up @@ -309,6 +373,20 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {

expect(createResult.error).toBeUndefined();
});

it('should able to create with override when user is dashboard admin', async () => {
const createResult = await dashboardAdminSavedObjectedClient.create(
'dashboard',
{},
{
id: 'inner-workspace-dashboard-1',
overwrite: true,
workspaces: ['workspace-1'],
}
);

expect(createResult.error).toBeUndefined();
});
});

describe('bulkCreate', () => {
Expand Down Expand Up @@ -337,6 +415,18 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
await permittedSavedObjectedClient.delete('dashboard', objectId);
});

it('should able to create saved objects into any workspaces after bulkCreate called when user is dashboard damin', async () => {
const objectId = new Date().getTime().toString(16).toUpperCase();
const result = await dashboardAdminSavedObjectedClient.bulkCreate(
[{ type: 'dashboard', attributes: {}, id: objectId }],
{
workspaces: ['workspace-1'],
}
);
expect(result.saved_objects.length).toEqual(1);
await dashboardAdminSavedObjectedClient.delete('dashboard', objectId);
});

it('should throw forbidden error when create with override', async () => {
let error;
try {
Expand Down Expand Up @@ -377,6 +467,24 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {

expect(createResult.saved_objects).toHaveLength(1);
});

it('should able to bulk create with override when user is dashboard admin', async () => {
const createResult = await dashboardAdminSavedObjectedClient.bulkCreate(
[
{
id: 'inner-workspace-dashboard-1',
type: 'dashboard',
attributes: {},
},
],
{
overwrite: true,
workspaces: ['workspace-1'],
}
);

expect(createResult.saved_objects).toHaveLength(1);
});
});

describe('update', () => {
Expand Down Expand Up @@ -414,6 +522,27 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
.error
).toBeUndefined();
});

it('should update saved objects for any workspaces when user is dashboard admin', async () => {
expect(
(
await dashboardAdminSavedObjectedClient.update(
'dashboard',
'inner-workspace-dashboard-1',
{}
)
).error
).toBeUndefined();
expect(
(
await dashboardAdminSavedObjectedClient.update(
'dashboard',
'acl-controlled-dashboard-2',
{}
)
).error
).toBeUndefined();
});
});

describe('bulkUpdate', () => {
Expand Down Expand Up @@ -459,6 +588,23 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
).saved_objects.length
).toEqual(1);
});

it('should bulk update saved objects for any workspaces when user is dashboard admin', async () => {
expect(
(
await dashboardAdminSavedObjectedClient.bulkUpdate([
{ type: 'dashboard', id: 'inner-workspace-dashboard-1', attributes: {} },
])
).saved_objects.length
).toEqual(1);
expect(
(
await dashboardAdminSavedObjectedClient.bulkUpdate([
{ type: 'dashboard', id: 'inner-workspace-dashboard-1', attributes: {} },
])
).saved_objects.length
).toEqual(1);
});
});

describe('delete', () => {
Expand Down Expand Up @@ -526,5 +672,48 @@ describe('WorkspaceSavedObjectsClientWrapper', () => {
}
expect(SavedObjectsErrorHelpers.isNotFoundError(error)).toBe(true);
});

it('should be able to delete any data when user is dashboard admin', async () => {
const createResultOne = await repositoryKit.create(
internalSavedObjectsRepository,
'dashboard',
{},
{
permissions: {
read: { users: ['foo'] },
write: { users: ['foo'] },
},
}
);

await dashboardAdminSavedObjectedClient.delete('dashboard', createResultOne.id);

let errorOne;
try {
errorOne = await dashboardAdminSavedObjectedClient.get('dashboard', createResultOne.id);
} catch (e) {
errorOne = e;
}
expect(SavedObjectsErrorHelpers.isNotFoundError(errorOne)).toBe(true);

const createResultTwo = await repositoryKit.create(
internalSavedObjectsRepository,
'dashboard',
{},
{
workspaces: ['workspace-1'],
}
);

await dashboardAdminSavedObjectedClient.delete('dashboard', createResultTwo.id);

let errorTwo;
try {
errorTwo = await dashboardAdminSavedObjectedClient.get('dashboard', createResultTwo.id);
} catch (e) {
errorTwo = e;
}
expect(SavedObjectsErrorHelpers.isNotFoundError(errorTwo)).toBe(true);
});
});
});
Loading

0 comments on commit 72fc938

Please sign in to comment.