diff --git a/src/plugins/workspace/server/plugin.ts b/src/plugins/workspace/server/plugin.ts index 69c61dee79a4..5c351eec1e47 100644 --- a/src/plugins/workspace/server/plugin.ts +++ b/src/plugins/workspace/server/plugin.ts @@ -11,6 +11,7 @@ import { Plugin, Logger, CoreStart, + OpenSearchDashboardsRequest, } from '../../../core/server'; import { WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID, @@ -31,7 +32,6 @@ import { SavedObjectsPermissionControlContract, } from './permission_control/client'; import { WorkspacePluginConfigType } from '../config'; -import { isRequestByDashboardAdmin } from './saved_objects/workspace_saved_objects_client_wrapper'; export class WorkspacePlugin implements Plugin<{}, {}> { private readonly logger: Logger; @@ -60,6 +60,26 @@ export class WorkspacePlugin implements Plugin<{}, {}> { }); } + private isRequestByDashboardAdmin( + request: OpenSearchDashboardsRequest, + groups: string[], + users: string[], + configGroups: string[], + configUsers: string[] + ) { + if (configGroups.length === 0 && configUsers.length === 0) { + updateWorkspaceState(request, { + isDashboardAdmin: false, + }); + return; + } + const groupMatchAny = groups.some((group) => configGroups.includes(group)) || false; + const userMatchAny = users.some((user) => configUsers.includes(user)) || false; + updateWorkspaceState(request, { + isDashboardAdmin: groupMatchAny || userMatchAny, + }); + } + constructor(initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('plugins', 'workspace'); this.config$ = initializerContext.config.create(); @@ -88,35 +108,56 @@ export class WorkspacePlugin implements Plugin<{}, {}> { if (isPermissionControlEnabled) { this.permissionControl = new SavedObjectsPermissionControl(this.logger); - this.logger.info('Dynamic application configuration enabled:' + !!applicationConfig); - if (!!applicationConfig) { - core.http.registerOnPostAuth(async (request, response, toolkit) => { + core.http.registerOnPostAuth(async (request, response, toolkit) => { + let groups: string[]; + let users: string[]; + + // There may be calls to saved objects client before user get authenticated, need to add a try catch here as `getPrincipalsFromRequest` will throw error when user is not authenticated. + try { + ({ groups = [], users = [] } = this.permissionControl!.getPrincipalsFromRequest(request)); + } catch (e) { + return toolkit.next(); + } + if (groups.length === 0 && users.length === 0) { + updateWorkspaceState(request, { + isDashboardAdmin: false, + }); + return toolkit.next(); + } + + if (!!applicationConfig) { + this.logger.info('Dynamic application configuration enabled:' + !!applicationConfig); const [coreStart] = await core.getStartServices(); const scopeClient = coreStart.opensearch.client.asScoped(request); - const configClient = applicationConfig.getConfigurationClient(scopeClient); - - const [adminGroups, adminUsers] = await Promise.all([ - configClient.getEntityConfig('workspace.dashboardAdmin.groups').catch(() => undefined), - configClient.getEntityConfig('workspace.dashboardAdmin.users').catch(() => undefined), + const applicationConfigClient = applicationConfig.getConfigurationClient(scopeClient); + + const [configGroups, configUsers] = await Promise.all([ + applicationConfigClient + .getEntityConfig('workspace.dashboardAdmin.groups') + .catch(() => undefined), + applicationConfigClient + .getEntityConfig('workspace.dashboardAdmin.users') + .catch(() => undefined), ]); - const isDashboardAdmin = isRequestByDashboardAdmin( + this.isRequestByDashboardAdmin( request, - adminGroups ? [adminGroups] : [], - adminUsers ? [adminUsers] : [], - this.permissionControl! + groups, + users, + configGroups ? [configGroups] : [], + configUsers ? [configUsers] : [] ); - updateWorkspaceState(request, { - isDashboardAdmin, - }); return toolkit.next(); - }); - } + } + + const configGroups = config.dashboardAdmin.groups || []; + const configUsers = config.dashboardAdmin.users || []; + this.isRequestByDashboardAdmin(request, groups, users, configGroups, configUsers); + return toolkit.next(); + }); this.workspaceSavedObjectsClientWrapper = new WorkspaceSavedObjectsClientWrapper( - this.permissionControl, - { config$: this.config$ }, - !!applicationConfig + this.permissionControl ); core.savedObjects.addClientWrapper( diff --git a/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts b/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts index fd240e064dea..6ce411b743c8 100644 --- a/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts +++ b/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts @@ -17,6 +17,7 @@ import { } from '../../../../../core/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import * as utilsExports from '../../utils'; +import { updateWorkspaceState } from '../../../../../core/server/utils'; const repositoryKit = (() => { const savedObjects: Array<{ type: string; id: string }> = []; @@ -51,8 +52,7 @@ const repositoryKit = (() => { const permittedRequest = httpServerMock.createOpenSearchDashboardsRequest(); const notPermittedRequest = httpServerMock.createOpenSearchDashboardsRequest(); -const groupIsDashboardAdminRequest = httpServerMock.createOpenSearchDashboardsRequest(); -const UserIdIsDashboardAdminRequest = httpServerMock.createOpenSearchDashboardsRequest(); +const dashboardAdminRequest = httpServerMock.createOpenSearchDashboardsRequest(); describe('WorkspaceSavedObjectsClientWrapper', () => { let internalSavedObjectsRepository: ISavedObjectsRepository; @@ -61,8 +61,7 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { let osd: TestOpenSearchDashboardsUtils; let permittedSavedObjectedClient: SavedObjectsClientContract; let notPermittedSavedObjectedClient: SavedObjectsClientContract; - let groupIsDashboardAdminSavedObjectedClient: SavedObjectsClientContract; - let UserIdIsdashboardAdminSavedObjectedClient: SavedObjectsClientContract; + let dashboardAdminSavedObjectedClient: SavedObjectsClientContract; beforeAll(async function () { servers = createTestServers({ @@ -132,20 +131,16 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { jest.spyOn(utilsExports, 'getPrincipalsFromRequest').mockImplementation((request) => { if (request === notPermittedRequest) return { users: ['bar'] }; - else if (request === permittedRequest) return { users: ['foo'] }; - else if (request === groupIsDashboardAdminRequest) return { groups: ['dashboard_admin'] }; - return { users: ['dashboard_admin'] }; + else return { users: ['foo'] }; }); permittedSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient(permittedRequest); notPermittedSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient( notPermittedRequest ); - groupIsDashboardAdminSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient( - groupIsDashboardAdminRequest - ); - UserIdIsdashboardAdminSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient( - UserIdIsDashboardAdminRequest + updateWorkspaceState(dashboardAdminRequest, { isDashboardAdmin: true }); + dashboardAdminSavedObjectedClient = osd.coreStart.savedObjects.getScopedClient( + dashboardAdminRequest ); }); @@ -187,41 +182,14 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { ).toBeUndefined(); }); - it('should return consistent dashboard when groups match dashboard admin', async () => { - expect( - ( - await groupIsDashboardAdminSavedObjectedClient.get( - 'dashboard', - 'inner-workspace-dashboard-1' - ) - ).error - ).toBeUndefined(); - expect( - ( - await groupIsDashboardAdminSavedObjectedClient.get( - 'dashboard', - 'acl-controlled-dashboard-2' - ) - ).error - ).toBeUndefined(); - }); - - it('should return consistent dashboard when user ids match dashboard admin', async () => { + it('should return consistent dashboard when groups/users is dashboard admin', async () => { expect( - ( - await UserIdIsdashboardAdminSavedObjectedClient.get( - 'dashboard', - 'inner-workspace-dashboard-1' - ) - ).error + (await dashboardAdminSavedObjectedClient.get('dashboard', 'inner-workspace-dashboard-1')) + .error ).toBeUndefined(); expect( - ( - await UserIdIsdashboardAdminSavedObjectedClient.get( - 'dashboard', - 'acl-controlled-dashboard-2' - ) - ).error + (await dashboardAdminSavedObjectedClient.get('dashboard', 'acl-controlled-dashboard-2')) + .error ).toBeUndefined(); }); }); @@ -268,34 +236,17 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { ).toEqual(1); }); - it('should return consistent dashboard when groups match dashboard admin', async () => { - expect( - ( - await groupIsDashboardAdminSavedObjectedClient.bulkGet([ - { type: 'dashboard', id: 'inner-workspace-dashboard-1' }, - ]) - ).saved_objects.length - ).toEqual(1); - expect( - ( - await groupIsDashboardAdminSavedObjectedClient.bulkGet([ - { type: 'dashboard', id: 'acl-controlled-dashboard-2' }, - ]) - ).saved_objects.length - ).toEqual(1); - }); - - it('should return consistent dashboard when user ids match dashboard admin', async () => { + it('should return consistent dashboard when groups/users is dashboard admin', async () => { expect( ( - await UserIdIsdashboardAdminSavedObjectedClient.bulkGet([ + await dashboardAdminSavedObjectedClient.bulkGet([ { type: 'dashboard', id: 'inner-workspace-dashboard-1' }, ]) ).saved_objects.length ).toEqual(1); expect( ( - await UserIdIsdashboardAdminSavedObjectedClient.bulkGet([ + await dashboardAdminSavedObjectedClient.bulkGet([ { type: 'dashboard', id: 'acl-controlled-dashboard-2' }, ]) ).saved_objects.length @@ -333,21 +284,8 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { ); }); - it('should return consistent inner workspace data when groups match dashboard admin', async () => { - const result = await groupIsDashboardAdminSavedObjectedClient.find({ - type: 'dashboard', - workspaces: ['workspace-1'], - perPage: 999, - page: 1, - }); - - expect(result.saved_objects.some((item) => item.id === 'inner-workspace-dashboard-1')).toBe( - true - ); - }); - - it('should return consistent inner workspace data when user ids match dashboard admin', async () => { - const result = await UserIdIsdashboardAdminSavedObjectedClient.find({ + it('should return consistent inner workspace data when groups/users is dashboard admin', async () => { + const result = await dashboardAdminSavedObjectedClient.find({ type: 'dashboard', workspaces: ['workspace-1'], perPage: 999, @@ -390,20 +328,8 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { await permittedSavedObjectedClient.delete('dashboard', createResult.id); }); - it('should able to create saved objects into any workspaces after create called when groups match dashboard admin', async () => { - const createResult = await groupIsDashboardAdminSavedObjectedClient.create( - 'dashboard', - {}, - { - workspaces: ['workspace-1'], - } - ); - expect(createResult.error).toBeUndefined(); - await groupIsDashboardAdminSavedObjectedClient.delete('dashboard', createResult.id); - }); - - it('should able to create saved objects into any workspaces after create called when user ids match dashboard admin', async () => { - const createResult = await UserIdIsdashboardAdminSavedObjectedClient.create( + it('should able to create saved objects into any workspaces after create called when groups/users is dashboard admin', async () => { + const createResult = await dashboardAdminSavedObjectedClient.create( 'dashboard', {}, { @@ -411,7 +337,7 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { } ); expect(createResult.error).toBeUndefined(); - await groupIsDashboardAdminSavedObjectedClient.delete('dashboard', createResult.id); + await dashboardAdminSavedObjectedClient.delete('dashboard', createResult.id); }); it('should throw forbidden error when create with override', async () => { @@ -446,22 +372,8 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { expect(createResult.error).toBeUndefined(); }); - it('should able to create with override when groups match dashboard admin', async () => { - const createResult = await groupIsDashboardAdminSavedObjectedClient.create( - 'dashboard', - {}, - { - id: 'inner-workspace-dashboard-1', - overwrite: true, - workspaces: ['workspace-1'], - } - ); - - expect(createResult.error).toBeUndefined(); - }); - - it('should able to create with override when uesr ids match dashboard admin', async () => { - const createResult = await UserIdIsdashboardAdminSavedObjectedClient.create( + it('should able to create with override when groups/users is dashboard admin', async () => { + const createResult = await dashboardAdminSavedObjectedClient.create( 'dashboard', {}, { @@ -501,28 +413,16 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { await permittedSavedObjectedClient.delete('dashboard', objectId); }); - it('should able to create saved objects into any workspaces after bulkCreate called when groups match dashboard damin', async () => { + it('should able to create saved objects into any workspaces after bulkCreate called when groups/users is dashboard damin', async () => { const objectId = new Date().getTime().toString(16).toUpperCase(); - const result = await groupIsDashboardAdminSavedObjectedClient.bulkCreate( + const result = await dashboardAdminSavedObjectedClient.bulkCreate( [{ type: 'dashboard', attributes: {}, id: objectId }], { workspaces: ['workspace-1'], } ); expect(result.saved_objects.length).toEqual(1); - await groupIsDashboardAdminSavedObjectedClient.delete('dashboard', objectId); - }); - - it('should able to create saved objects into any workspaces after bulkCreate called when user ids match dashboard damin', async () => { - const objectId = new Date().getTime().toString(16).toUpperCase(); - const result = await UserIdIsdashboardAdminSavedObjectedClient.bulkCreate( - [{ type: 'dashboard', attributes: {}, id: objectId }], - { - workspaces: ['workspace-1'], - } - ); - expect(result.saved_objects.length).toEqual(1); - await UserIdIsdashboardAdminSavedObjectedClient.delete('dashboard', objectId); + await dashboardAdminSavedObjectedClient.delete('dashboard', objectId); }); it('should throw forbidden error when create with override', async () => { @@ -566,26 +466,8 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { expect(createResult.saved_objects).toHaveLength(1); }); - it('should able to bulk create with override when groups match dashboard admin', async () => { - const createResult = await groupIsDashboardAdminSavedObjectedClient.bulkCreate( - [ - { - id: 'inner-workspace-dashboard-1', - type: 'dashboard', - attributes: {}, - }, - ], - { - overwrite: true, - workspaces: ['workspace-1'], - } - ); - - expect(createResult.saved_objects).toHaveLength(1); - }); - - it('should able to bulk create with override when user ids match dashboard admin', async () => { - const createResult = await UserIdIsdashboardAdminSavedObjectedClient.bulkCreate( + it('should able to bulk create with override when groups/users is dashboard admin', async () => { + const createResult = await dashboardAdminSavedObjectedClient.bulkCreate( [ { id: 'inner-workspace-dashboard-1', @@ -639,10 +521,10 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { ).toBeUndefined(); }); - it('should update saved objects for any workspaces when groups match dashboard admin', async () => { + it('should update saved objects for any workspaces when groups/users is dashboard admin', async () => { expect( ( - await groupIsDashboardAdminSavedObjectedClient.update( + await dashboardAdminSavedObjectedClient.update( 'dashboard', 'inner-workspace-dashboard-1', {} @@ -651,28 +533,7 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { ).toBeUndefined(); expect( ( - await groupIsDashboardAdminSavedObjectedClient.update( - 'dashboard', - 'acl-controlled-dashboard-2', - {} - ) - ).error - ).toBeUndefined(); - }); - - it('should update saved objects for any workspaces when user ids match dashboard admin', async () => { - expect( - ( - await UserIdIsdashboardAdminSavedObjectedClient.update( - 'dashboard', - 'inner-workspace-dashboard-1', - {} - ) - ).error - ).toBeUndefined(); - expect( - ( - await UserIdIsdashboardAdminSavedObjectedClient.update( + await dashboardAdminSavedObjectedClient.update( 'dashboard', 'acl-controlled-dashboard-2', {} @@ -726,34 +587,17 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { ).toEqual(1); }); - it('should bulk update saved objects for any workspaces when groups match dashboard admin', async () => { + it('should bulk update saved objects for any workspaces when groups/users is dashboard admin', async () => { expect( ( - await groupIsDashboardAdminSavedObjectedClient.bulkUpdate([ + await dashboardAdminSavedObjectedClient.bulkUpdate([ { type: 'dashboard', id: 'inner-workspace-dashboard-1', attributes: {} }, ]) ).saved_objects.length ).toEqual(1); expect( ( - await groupIsDashboardAdminSavedObjectedClient.bulkUpdate([ - { type: 'dashboard', id: 'inner-workspace-dashboard-1', attributes: {} }, - ]) - ).saved_objects.length - ).toEqual(1); - }); - - it('should bulk update saved objects for any workspaces when user ids match dashboard admin', async () => { - expect( - ( - await UserIdIsdashboardAdminSavedObjectedClient.bulkUpdate([ - { type: 'dashboard', id: 'inner-workspace-dashboard-1', attributes: {} }, - ]) - ).saved_objects.length - ).toEqual(1); - expect( - ( - await UserIdIsdashboardAdminSavedObjectedClient.bulkUpdate([ + await dashboardAdminSavedObjectedClient.bulkUpdate([ { type: 'dashboard', id: 'inner-workspace-dashboard-1', attributes: {} }, ]) ).saved_objects.length @@ -827,56 +671,7 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { expect(SavedObjectsErrorHelpers.isNotFoundError(error)).toBe(true); }); - it('should be able to delete any data when groups match dashboard admin', async () => { - const createPermittedResult = await repositoryKit.create( - internalSavedObjectsRepository, - 'dashboard', - {}, - { - permissions: { - read: { users: ['foo'] }, - write: { users: ['foo'] }, - }, - } - ); - - await groupIsDashboardAdminSavedObjectedClient.delete('dashboard', createPermittedResult.id); - - let permittedError; - try { - permittedError = await groupIsDashboardAdminSavedObjectedClient.get( - 'dashboard', - createPermittedResult.id - ); - } catch (e) { - permittedError = e; - } - expect(SavedObjectsErrorHelpers.isNotFoundError(permittedError)).toBe(true); - - const createACLResult = await repositoryKit.create( - internalSavedObjectsRepository, - 'dashboard', - {}, - { - workspaces: ['workspace-1'], - } - ); - - await groupIsDashboardAdminSavedObjectedClient.delete('dashboard', createACLResult.id); - - let ACLError; - try { - ACLError = await groupIsDashboardAdminSavedObjectedClient.get( - 'dashboard', - createACLResult.id - ); - } catch (e) { - ACLError = e; - } - expect(SavedObjectsErrorHelpers.isNotFoundError(ACLError)).toBe(true); - }); - - it('should be able to delete any data when user ids match dashboard admin', async () => { + it('should be able to delete any data when groups/users is dashboard admin', async () => { const createPermittedResult = await repositoryKit.create( internalSavedObjectsRepository, 'dashboard', @@ -889,11 +684,11 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { } ); - await UserIdIsdashboardAdminSavedObjectedClient.delete('dashboard', createPermittedResult.id); + await dashboardAdminSavedObjectedClient.delete('dashboard', createPermittedResult.id); let permittedError; try { - permittedError = await UserIdIsdashboardAdminSavedObjectedClient.get( + permittedError = await dashboardAdminSavedObjectedClient.get( 'dashboard', createPermittedResult.id ); @@ -911,14 +706,11 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { } ); - await UserIdIsdashboardAdminSavedObjectedClient.delete('dashboard', createACLResult.id); + await dashboardAdminSavedObjectedClient.delete('dashboard', createACLResult.id); let ACLError; try { - ACLError = await UserIdIsdashboardAdminSavedObjectedClient.get( - 'dashboard', - createACLResult.id - ); + ACLError = await dashboardAdminSavedObjectedClient.get('dashboard', createACLResult.id); } catch (e) { ACLError = e; } diff --git a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts index a8d0e1f9d05c..eccc70f6f991 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts @@ -3,13 +3,13 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { of } from 'rxjs'; +import { getWorkspaceState, updateWorkspaceState } from '../../../../core/server/utils'; import { SavedObjectsErrorHelpers } from '../../../../core/server'; import { WorkspaceSavedObjectsClientWrapper } from './workspace_saved_objects_client_wrapper'; +import { httpServerMock } from '../../../../core/server/mocks'; -const GROUP_MATCH_DASHBOARD_ADMIN = 'match_group'; -const USER_MATCH_DASHBOARD_ADMIN = 'match_user_id'; -const NO_DASHBOARD_ADMIN = 'math_none'; +const DASHBOARD_ADMIN = 'dashnoard_admin'; +const NO_DASHBOARD_ADMIN = 'no_dashnoard_admin'; const generateWorkspaceSavedObjectsClientWrapper = (role = NO_DASHBOARD_ADMIN) => { const savedObjectsStore = [ @@ -68,7 +68,8 @@ const generateWorkspaceSavedObjectsClientWrapper = (role = NO_DASHBOARD_ADMIN) = find: jest.fn(), deleteByWorkspace: jest.fn(), }; - const requestMock = {}; + const requestMock = httpServerMock.createOpenSearchDashboardsRequest(); + if (role === DASHBOARD_ADMIN) updateWorkspaceState(requestMock, { isDashboardAdmin: true }); const wrapperOptions = { client: clientMock, request: requestMock, @@ -85,25 +86,11 @@ const generateWorkspaceSavedObjectsClientWrapper = (role = NO_DASHBOARD_ADMIN) = validateSavedObjectsACL: jest.fn(), batchValidate: jest.fn(), getPrincipalsFromRequest: jest.fn().mockImplementation(() => { - if (role === GROUP_MATCH_DASHBOARD_ADMIN) return { groups: ['dashboard_admin'] }; - else if (role === USER_MATCH_DASHBOARD_ADMIN) return { users: ['dashboard_admin'] }; return { users: ['user-1'] }; }), }; - const configMock = { - enabled: true, - permission: { - enabled: true, - }, - dashboardAdmin: { - groups: ['dashboard_admin'], - users: ['dashboard_admin'], - }, - }; - const optionsMock = { - config$: of(configMock), - }; - const wrapper = new WorkspaceSavedObjectsClientWrapper(permissionControlMock, optionsMock); + + const wrapper = new WorkspaceSavedObjectsClientWrapper(permissionControlMock); wrapper.setScopedClient(() => ({ find: jest.fn().mockImplementation(async () => ({ saved_objects: [{ id: 'workspace-1', type: 'workspace' }], @@ -150,23 +137,16 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { await wrapper.delete(...deleteArgs); expect(clientMock.delete).toHaveBeenCalledWith(...deleteArgs); }); - it('should call client.delete if groups match dashboard admin', async () => { + it('should call client.delete if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - const deleteArgs = ['dashboard', 'not-permitted-dashboard'] as const; - await wrapper.delete(...deleteArgs); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - expect(clientMock.delete).toHaveBeenCalledWith(...deleteArgs); - }); - it('should call client.delete if user ids match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + requestMock, + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); + expect(getWorkspaceState(requestMock)).toEqual({ + isDashboardAdmin: true, + }); const deleteArgs = ['dashboard', 'not-permitted-dashboard'] as const; await wrapper.delete(...deleteArgs); expect(permissionControlMock.validate).not.toHaveBeenCalled(); @@ -214,29 +194,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { await wrapper.update(...updateArgs); expect(clientMock.update).toHaveBeenCalledWith(...updateArgs); }); - it('should call client.update if groups match dashboard admin', async () => { + it('should call client.update if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - const updateArgs = [ - 'dashboard', - 'not-permitted-dashboard', - { - bar: 'for', - }, - ] as const; - await wrapper.update(...updateArgs); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - expect(clientMock.update).toHaveBeenCalledWith(...updateArgs); - }); - it('should call client.update if user ids match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); const updateArgs = [ 'dashboard', 'not-permitted-dashboard', @@ -283,25 +246,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { await wrapper.bulkUpdate(objectsToUpdate, {}); expect(clientMock.bulkUpdate).toHaveBeenCalledWith(objectsToUpdate, {}); }); - it('should call client.bulkUpdate if group match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - const bulkUpdateArgs = [ - { type: 'dashboard', id: 'not-permitted-dashboard', attributes: { bar: 'baz' } }, - ]; - await wrapper.bulkUpdate(bulkUpdateArgs); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - expect(clientMock.bulkUpdate).toHaveBeenCalledWith(bulkUpdateArgs); - }); - it('should call client.bulkUpdate if user ids match dashboard admin', async () => { + it('should call client.bulkUpdate if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); const bulkUpdateArgs = [ { type: 'dashboard', id: 'not-permitted-dashboard', attributes: { bar: 'baz' } }, ]; @@ -393,31 +343,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { workspaces: ['workspace-1'], }); }); - it('should call client.bulkCreate if groups match dashboard admin', async () => { + it('should call client.bulkCreate if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - const objectsToBulkCreate = [ - { type: 'dashboard', id: 'not-permitted-dashboard', attributes: { bar: 'baz' } }, - ]; - await wrapper.bulkCreate(objectsToBulkCreate, { - overwrite: true, - workspaces: ['not-permitted-workspace'], - }); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - expect(clientMock.bulkCreate).toHaveBeenCalledWith(objectsToBulkCreate, { - overwrite: true, - workspaces: ['not-permitted-workspace'], - }); - }); - it('should call client.bulkCreate if user ids match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); const objectsToBulkCreate = [ { type: 'dashboard', id: 'not-permitted-dashboard', attributes: { bar: 'baz' } }, ]; @@ -505,36 +436,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { } ); }); - it('should call client.create if groups match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - await wrapper.create( - 'dashboard', - { foo: 'bar' }, - { - id: 'not-permitted-dashboard', - overwrite: true, - } - ); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - expect(clientMock.create).toHaveBeenCalledWith( - 'dashboard', - { foo: 'bar' }, - { - id: 'not-permitted-dashboard', - overwrite: true, - } - ); - }); - it('should call client.create if user ids match dashboard admin', async () => { + it('should call client.create if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); await wrapper.create( 'dashboard', { foo: 'bar' }, @@ -614,24 +521,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { expect(clientMock.get).toHaveBeenCalledWith(...getArgs); expect(result).toMatchInlineSnapshot(`[Error: Not Found]`); }); - it('should call client.get and return result with arguments if groups match dashboard admin', async () => { + it('should call client.get and return result with arguments if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - const getArgs = ['dashboard', 'not-permitted-dashboard'] as const; - const result = await wrapper.get(...getArgs); - expect(clientMock.get).toHaveBeenCalledWith(...getArgs); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - expect(result.id).toBe('not-permitted-dashboard'); - }); - it('should call client.get and return result with arguments if user ids match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); const getArgs = ['dashboard', 'not-permitted-dashboard'] as const; const result = await wrapper.get(...getArgs); expect(clientMock.get).toHaveBeenCalledWith(...getArgs); @@ -703,33 +598,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { {} ); }); - it('should call client.bulkGet and return result with arguments if groups match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - const bulkGetArgs = [ - { - type: 'dashboard', - id: 'foo', - }, - { - type: 'dashboard', - id: 'not-permitted-dashboard', - }, - ]; - const result = await wrapper.bulkGet(bulkGetArgs); - expect(clientMock.bulkGet).toHaveBeenCalledWith(bulkGetArgs); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - expect(result.saved_objects.length).toBe(2); - }); - it('should call client.bulkGet and return result with arguments if user ids match dashboard admin', async () => { + it('should call client.bulkGet and return result with arguments if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); const bulkGetArgs = [ { type: 'dashboard', @@ -820,28 +694,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { }, }); }); - it('should call client.find with arguments if groups match dashboard admin', async () => { + it('should call client.find with arguments if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - await wrapper.find({ - type: 'dashboard', - workspaces: ['workspace-1', 'not-permitted-workspace'], - }); - expect(clientMock.find).toHaveBeenCalledWith({ - type: 'dashboard', - workspaces: ['workspace-1', 'not-permitted-workspace'], - }); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - }); - it('should call client.find with arguments if user ids match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); await wrapper.find({ type: 'dashboard', workspaces: ['workspace-1', 'not-permitted-workspace'], @@ -880,22 +738,12 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { await wrapper.deleteByWorkspace('workspace-1', {}); expect(clientMock.deleteByWorkspace).toHaveBeenCalledWith('workspace-1', {}); }); - it('should call client.deleteByWorkspace if groups match dashboard admin', async () => { - const { - wrapper, - clientMock, - permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(GROUP_MATCH_DASHBOARD_ADMIN); - await wrapper.deleteByWorkspace('not-permitted-workspace'); - expect(clientMock.deleteByWorkspace).toHaveBeenCalledWith('not-permitted-workspace'); - expect(permissionControlMock.validate).not.toHaveBeenCalled(); - }); - it('should call client.deleteByWorkspace if user ids match dashboard admin', async () => { + it('should call client.deleteByWorkspace if user/groups is dashboard admin', async () => { const { wrapper, clientMock, permissionControlMock, - } = generateWorkspaceSavedObjectsClientWrapper(USER_MATCH_DASHBOARD_ADMIN); + } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); await wrapper.deleteByWorkspace('not-permitted-workspace'); expect(clientMock.deleteByWorkspace).toHaveBeenCalledWith('not-permitted-workspace'); expect(permissionControlMock.validate).not.toHaveBeenCalled(); diff --git a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts index 84b3d14341db..523869321a22 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts @@ -4,8 +4,6 @@ */ import { i18n } from '@osd/i18n'; -import { Observable } from 'rxjs'; -import { first } from 'rxjs/operators'; import { getWorkspaceState } from '../../../../core/server/utils'; import { @@ -35,7 +33,6 @@ import { WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID, WorkspacePermissionMode, } from '../../common/constants'; -import { WorkspacePluginConfigType } from '../../config'; // Can't throw unauthorized for now, the page will be refreshed if unauthorized const generateWorkspacePermissionError = () => @@ -70,32 +67,8 @@ const getDefaultValuesForEmpty = (values: T[] | undefined, defaultValues: T[] return !values || values.length === 0 ? defaultValues : values; }; -export const isRequestByDashboardAdmin = ( - request: OpenSearchDashboardsRequest, - adminGroups: string[], - adminUsers: string[], - permissionControl: SavedObjectsPermissionControlContract -): boolean => { - if (adminGroups.length === 0 && adminUsers.length === 0) return false; - - let groups: string[]; - let users: string[]; - - // There may be calls to saved objects client before user get authenticated, need to add a try catch here as `getPrincipalsFromRequest` will throw error when user is not authenticated. - try { - ({ groups = [], users = [] } = permissionControl.getPrincipalsFromRequest(request)); - } catch (e) { - return false; - } - - const groupMatchAny = groups.some((group) => adminGroups.includes(group)) || false; - const userMatchAny = users.some((user) => adminUsers.includes(user)) || false; - return groupMatchAny || userMatchAny; -}; - export class WorkspaceSavedObjectsClientWrapper { private getScopedClient?: SavedObjectsServiceStart['getScopedClient']; - private config?: WorkspacePluginConfigType; private formatWorkspacePermissionModeToStringArray( permission: WorkspacePermissionMode | WorkspacePermissionMode[] ): string[] { @@ -555,22 +528,7 @@ export class WorkspaceSavedObjectsClientWrapper { return await wrapperOptions.client.deleteByWorkspace(workspace, options); }; - let isDashboardAdmin: boolean = false; - if (this.applicationConfig) { - const workspaceState = getWorkspaceState(wrapperOptions.request); - isDashboardAdmin = workspaceState?.isDashboardAdmin || false; - } else { - const config = this.config || ({} as WorkspacePluginConfigType); - const adminGroups = config.dashboardAdmin?.groups || []; - const adminUsers = config.dashboardAdmin?.users || []; - isDashboardAdmin = isRequestByDashboardAdmin( - wrapperOptions.request, - adminGroups, - adminUsers, - this.permissionControl - ); - } - + const isDashboardAdmin = getWorkspaceState(wrapperOptions.request)?.isDashboardAdmin; if (isDashboardAdmin) { return wrapperOptions.client; } @@ -593,21 +551,5 @@ export class WorkspaceSavedObjectsClientWrapper { }; }; - constructor( - private readonly permissionControl: SavedObjectsPermissionControlContract, - private readonly options: { - config$: Observable; - }, - private readonly applicationConfig: boolean - ) { - this.options?.config$.subscribe((config) => { - this.config = config; - }); - this.options?.config$ - .pipe(first()) - .toPromise() - .then((config) => { - this.config = config; - }); - } + constructor(private readonly permissionControl: SavedObjectsPermissionControlContract) {} }