diff --git a/changelogs/fragments/8043.yml b/changelogs/fragments/8043.yml new file mode 100644 index 00000000000..241367a9560 --- /dev/null +++ b/changelogs/fragments/8043.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Deny get or bulkGet for global data source ([#8043](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/8043)) \ No newline at end of file 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 ea105cc21b6..6e3b7f24e62 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 @@ -53,6 +53,12 @@ const generateWorkspaceSavedObjectsClientWrapper = (role = NO_DASHBOARD_ADMIN) = id: 'global-data-source', attributes: { title: 'Global data source' }, }, + { + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + id: 'global-data-source-empty-workspaces', + attributes: { title: 'Global data source empty workspaces' }, + workspaces: [], + }, { type: DATA_SOURCE_SAVED_OBJECT_TYPE, id: 'workspace-1-data-source', @@ -620,6 +626,33 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { workspaces: ['workspace-1'], }); }); + + it('should throw permission error when tried to access a global data source', async () => { + const { wrapper } = generateWorkspaceSavedObjectsClientWrapper(); + let errorCatched; + try { + await wrapper.get('data-source', 'global-data-source'); + } catch (e) { + errorCatched = e; + } + expect(errorCatched?.message).toEqual( + 'Invalid data source permission, please associate it to current workspace' + ); + }); + + it('should throw permission error when tried to access a empty workspaces global data source', async () => { + const { wrapper, requestMock } = generateWorkspaceSavedObjectsClientWrapper(); + updateWorkspaceState(requestMock, { requestWorkspaceId: undefined }); + let errorCatched; + try { + await wrapper.get('data-source', 'global-data-source-empty-workspaces'); + } catch (e) { + errorCatched = e; + } + expect(errorCatched?.message).toEqual( + 'Invalid data source permission, please associate it to current workspace' + ); + }); }); describe('bulk get', () => { it("should call permission validate with object's workspace and throw permission error", async () => { @@ -744,6 +777,36 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { ], }); }); + + it('should throw permission error when tried to bulk get global data source', async () => { + const { wrapper, requestMock } = generateWorkspaceSavedObjectsClientWrapper(); + updateWorkspaceState(requestMock, { requestWorkspaceId: undefined }); + let errorCatched; + try { + await wrapper.bulkGet([{ type: 'data-source', id: 'global-data-source' }]); + } catch (e) { + errorCatched = e; + } + expect(errorCatched?.message).toEqual( + 'Invalid data source permission, please associate it to current workspace' + ); + }); + + it('should throw permission error when tried to bulk get a empty workspace global data source', async () => { + const { wrapper, requestMock } = generateWorkspaceSavedObjectsClientWrapper(); + updateWorkspaceState(requestMock, { requestWorkspaceId: undefined }); + let errorCatched; + try { + await wrapper.bulkGet([ + { type: 'data-source', id: 'global-data-source-empty-workspaces' }, + ]); + } catch (e) { + errorCatched = e; + } + expect(errorCatched?.message).toEqual( + 'Invalid data source permission, please associate it to current workspace' + ); + }); }); describe('find', () => { it('should call client.find with consistent params when ACLSearchParams and workspaceOperator not provided', async () => { 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 1c366f22fff..6cf972a8a9a 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 @@ -204,7 +204,24 @@ export class WorkspaceSavedObjectsClientWrapper { request: OpenSearchDashboardsRequest ) => { const requestWorkspaceId = getWorkspaceState(request).requestWorkspaceId; - return !requestWorkspaceId || !!object.workspaces?.includes(requestWorkspaceId); + // Deny access if the object is a global data source (no workspaces assigned) + if (!object.workspaces || object.workspaces.length === 0) { + return false; + } + /** + * Allow access if no specific workspace is requested. + * This typically occurs when retrieving data sources or performing operations + * that don't require a specific workspace, such as pages within the + * Data Administration navigation group that include a data source picker. + */ + if (!requestWorkspaceId) { + return true; + } + /* + * Allow access if the requested workspace matches one of the object's assigned workspaces + * This ensures that the user can only access data sources within their current workspace + */ + return object.workspaces.includes(requestWorkspaceId); }; private getWorkspaceTypeEnabledClient(request: OpenSearchDashboardsRequest) {