From eb3849c9e43bda9e4fb258a000b04eba986f6f27 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Fri, 15 Nov 2024 10:46:03 +0800 Subject: [PATCH 1/5] feat: add acl related test cases Signed-off-by: SuZhou-Joe --- .../mds_workspace_acl.spec.js | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js new file mode 100644 index 000000000..4cc76c4ec --- /dev/null +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js @@ -0,0 +1,230 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BASE_PATH } from '../../../../utils/base_constants'; +import { ADMIN_AUTH } from '../../../../utils/commands'; +import workspaceTestUser from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestUser.json'; +import workspaceTestRole from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestRole.json'; +import workspaceTestRoleMapping from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestRoleMapping.json'; +import { WORKSPACE_API_PREFIX } from '../../../../utils/dashboards/workspace-plugin/constants'; + +let noPermissionWorkspaceName = 'acl_no_permission_workspace'; +let readOnlyWorkspaceName = 'acl_readonly_workspace'; +let libraryWriteWorkspaceName = 'acl_library_write_workspace'; +let ownerWorkspaceName = 'acl_owner_workspace'; + +let noPermissionWorkspaceId = ''; +let readOnlyWorkspaceId = ''; +let libraryWriteWorkspaceId = ''; +let ownerWorkspaceId = ''; +let datasourceId = ''; + +const getPolicy = (permission, userName) => ({ + [permission]: { + users: [userName], + }, +}); + +const NONE_DASHBOARDS_ADMIN_USERNAME = 'workspace-acl-test'; +const WORKSPACE_TEST_ROLE_NAME = 'workspace-acl-test-role'; + +const ACLPolicyMap = { + [noPermissionWorkspaceName]: {}, + [readOnlyWorkspaceName]: { + ...getPolicy('read', NONE_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_read', NONE_DASHBOARDS_ADMIN_USERNAME), + }, + [libraryWriteWorkspaceName]: { + ...getPolicy('read', NONE_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_write', NONE_DASHBOARDS_ADMIN_USERNAME), + }, + [ownerWorkspaceName]: { + ...getPolicy('write', NONE_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_write', NONE_DASHBOARDS_ADMIN_USERNAME), + }, +}; + +const setupWorkspace = (workspaceName, datasourceId) => { + return cy + .createWorkspace({ + name: workspaceName, + settings: { + ...(datasourceId ? { dataSources: [datasourceId] } : {}), + permissions: ACLPolicyMap[workspaceName], + }, + }) + .then((value) => { + // load sample data + cy.loadSampleDataForWorkspace('ecommerce', value, datasourceId); + cy.wrap(value); + }); +}; + +if ( + Cypress.env('WORKSPACE_ENABLED') && + Cypress.env('SAVED_OBJECTS_PERMISSION_ENABLED') && + Cypress.env('SECURITY_ENABLED') +) { + describe('Workspace ACL', () => { + const originalUser = ADMIN_AUTH.username; + const originalPassword = ADMIN_AUTH.password; + before(() => { + cy.deleteWorkspaceByName(noPermissionWorkspaceName); + cy.deleteWorkspaceByName(readOnlyWorkspaceName); + cy.deleteWorkspaceByName(libraryWriteWorkspaceName); + cy.deleteWorkspaceByName(ownerWorkspaceName); + + cy.createInternalUser(NONE_DASHBOARDS_ADMIN_USERNAME, workspaceTestUser); + cy.createRole(WORKSPACE_TEST_ROLE_NAME, workspaceTestRole); + cy.createRoleMapping(WORKSPACE_TEST_ROLE_NAME, workspaceTestRoleMapping); + + if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { + cy.createDataSourceNoAuth().then((result) => { + datasourceId = result[0]; + expect(datasourceId).to.be.a('string').that.is.not.empty; + setupWorkspace(noPermissionWorkspaceName, datasourceId).then( + (value) => (noPermissionWorkspaceId = value) + ); + setupWorkspace(readOnlyWorkspaceName, datasourceId).then( + (value) => (readOnlyWorkspaceId = value) + ); + setupWorkspace(libraryWriteWorkspaceName, datasourceId).then( + (value) => (libraryWriteWorkspaceId = value) + ); + setupWorkspace(ownerWorkspaceName, datasourceId).then( + (value) => (ownerWorkspaceId = value) + ); + }); + } else { + setupWorkspace(noPermissionWorkspaceName, datasourceId).then( + (value) => (noPermissionWorkspaceId = value) + ); + setupWorkspace(readOnlyWorkspaceName, datasourceId).then( + (value) => (readOnlyWorkspaceId = value) + ); + setupWorkspace(libraryWriteWorkspaceName, datasourceId).then( + (value) => (libraryWriteWorkspaceId = value) + ); + setupWorkspace(ownerWorkspaceName, datasourceId).then( + (value) => (ownerWorkspaceId = value) + ); + } + }); + + after(() => { + cy.deleteWorkspaceByName(noPermissionWorkspaceName); + cy.deleteWorkspaceByName(readOnlyWorkspaceName); + cy.deleteWorkspaceByName(libraryWriteWorkspaceName); + cy.deleteWorkspaceByName(ownerWorkspaceName); + readOnlyWorkspaceId = ''; + libraryWriteWorkspaceId = ''; + + ADMIN_AUTH.newUser = originalUser; + ADMIN_AUTH.newPassword = originalPassword; + cy.deleteRoleMapping(WORKSPACE_TEST_ROLE_NAME); + cy.deleteInternalUser(NONE_DASHBOARDS_ADMIN_USERNAME); + cy.deleteRole(WORKSPACE_TEST_ROLE_NAME); + }); + + describe('Normal user', () => { + beforeEach(() => { + ADMIN_AUTH.newUser = NONE_DASHBOARDS_ADMIN_USERNAME; + ADMIN_AUTH.newPassword = workspaceTestUser.password; + }); + + it('Normal user should not be able to create workspace', () => { + cy.request({ + method: 'POST', + url: `${BASE_PATH}${WORKSPACE_API_PREFIX}`, + headers: { + 'osd-xsrf': true, + }, + body: { + attributes: { + name: 'test_workspace', + features: ['use-case-observability'], + description: 'test_description', + }, + }, + failOnStatusCode: false, + }).then((resp) => + cy + .wrap(resp.body.error) + .should('equal', 'Invalid permission, please contact OSD admin') + ); + }); + + it('Normal users should only see the workspaces they have permission with', () => { + cy.visit(`${BASE_PATH}/app/home`); + cy.contains(readOnlyWorkspaceName); + cy.contains(noPermissionWorkspaceName).should('not.exist'); + }); + + it('Readonly users should not be allowed to update dashboards/visualizations within the workspace', () => { + cy.visit(`${BASE_PATH}/w/${readOnlyWorkspaceId}/app/visualize`); + cy.contains(/\[eCommerce\] Markdown/).click(); + cy.getElementByTestId('visualizeSaveButton').click(); + cy.getElementByTestId('confirmSaveSavedObjectButton').click(); + cy.contains('Forbidden'); + }); + + it('Normal users should not be allowed to visit workspace he/she has no permission', () => { + cy.visit(`${BASE_PATH}/w/${noPermissionWorkspaceId}/app/objects`); + cy.contains('Invalid saved objects permission'); + }); + + it('Normal users should only see the workspaces he has library_write permission in the target workspaces list of duplicate modal', () => { + cy.visit(`${BASE_PATH}/w/${readOnlyWorkspaceId}/app/objects`); + cy.getElementByTestId('savedObjectsTableRowTitle').should('exist'); + cy.getElementByTestId('duplicateObjects') + .click() + .getElementByTestId('savedObjectsDuplicateModal') + .find('[data-test-subj="comboBoxInput"]') + .click(); + + cy.contains(libraryWriteWorkspaceName); + cy.contains(ownerWorkspaceName); + cy.contains(noPermissionWorkspaceName).should('not.exist'); + }); + + it('Users should not be able to update default index pattern / default data source if he/she is not the workspace owner', () => { + cy.visit(`${BASE_PATH}/w/${libraryWriteWorkspaceId}/app/indexPatterns`); + cy.contains('opensearch_dashboards_sample_data_ecommerce').click(); + cy.getElementByTestId('setDefaultIndexPatternButton').click(); + cy.contains('Unable to update UI setting'); + }); + + it('Normal users should not be able to find objects from other workspaces when inside a workspace', () => { + cy.visit(`${BASE_PATH}/w/${readOnlyWorkspaceId}/app/objects`); + cy.getElementByTestId('savedObjectsTableRowTitle').should('exist'); + cy.getElementByTestId( + 'savedObjectsTableColumn-workspace_column' + ).should('not.exist'); + cy.contains('opensearch_dashboards_sample_data_ecommerce'); + // Electron old version may not support search event, so we manually trigger a search event + cy.getElementByTestId('savedObjectSearchBar') + .type('opensearch_dashboards_sample_data_ecommerce{enter}') + .trigger('search'); + cy.getElementByTestId('savedObjectsTableRowTitle').should( + 'have.length', + 1 + ); + }); + + if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { + it('Normal users should not be able to associate / dissociate data sources from workspace.', () => { + cy.visit(`${BASE_PATH}/w/${ownerWorkspaceId}/app/dataSources`); + cy.contains('Data sources'); + cy.getElementByTestId('workspaceAssociateDataSourceButton').should( + 'not.exist' + ); + cy.getElementByTestId( + 'dataSourcesManagement-dataSourceTable-dissociateButton' + ).should('not.exist'); + }); + } + }); + }); +} From 3e5168bbd8029e23cea2c715aace0d7d1e59df22 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Fri, 15 Nov 2024 12:28:58 +0800 Subject: [PATCH 2/5] feat: update Signed-off-by: SuZhou-Joe --- .../workspace-plugin/mds_workspace_acl.spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js index 4cc76c4ec..2a07fe083 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js @@ -7,7 +7,6 @@ import { BASE_PATH } from '../../../../utils/base_constants'; import { ADMIN_AUTH } from '../../../../utils/commands'; import workspaceTestUser from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestUser.json'; import workspaceTestRole from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestRole.json'; -import workspaceTestRoleMapping from '../../../../fixtures/dashboard/opensearch_dashboards/workspace/workspaceTestRoleMapping.json'; import { WORKSPACE_API_PREFIX } from '../../../../utils/dashboards/workspace-plugin/constants'; let noPermissionWorkspaceName = 'acl_no_permission_workspace'; @@ -78,7 +77,9 @@ if ( cy.createInternalUser(NONE_DASHBOARDS_ADMIN_USERNAME, workspaceTestUser); cy.createRole(WORKSPACE_TEST_ROLE_NAME, workspaceTestRole); - cy.createRoleMapping(WORKSPACE_TEST_ROLE_NAME, workspaceTestRoleMapping); + cy.createRoleMapping(WORKSPACE_TEST_ROLE_NAME, { + users: [NONE_DASHBOARDS_ADMIN_USERNAME], + }); if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { cy.createDataSourceNoAuth().then((result) => { From 16c11a95b1750cfe2db627dfae4e21e9539fcb59 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 18 Nov 2024 09:52:07 +0800 Subject: [PATCH 3/5] feat: remove useless resources Signed-off-by: SuZhou-Joe --- .../workspace-plugin/mds_workspace_acl.spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js index 2a07fe083..bf5d3fb65 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js @@ -115,10 +115,18 @@ if ( }); after(() => { + cy.removeSampleDataForWorkspace( + 'ecommerce', + ownerWorkspaceId, + datasourceId + ); cy.deleteWorkspaceByName(noPermissionWorkspaceName); cy.deleteWorkspaceByName(readOnlyWorkspaceName); cy.deleteWorkspaceByName(libraryWriteWorkspaceName); cy.deleteWorkspaceByName(ownerWorkspaceName); + if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { + cy.deleteDataSource(datasourceId); + } readOnlyWorkspaceId = ''; libraryWriteWorkspaceId = ''; From f1da12294af37ba4b9bdaa151b6218aaa8c42f35 Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 18 Nov 2024 11:42:24 +0800 Subject: [PATCH 4/5] feat: update Signed-off-by: SuZhou-Joe --- .../mds_workspace_acl.spec.js | 63 +++++++++---------- 1 file changed, 28 insertions(+), 35 deletions(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js index bf5d3fb65..9600bdc89 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js @@ -26,22 +26,22 @@ const getPolicy = (permission, userName) => ({ }, }); -const NONE_DASHBOARDS_ADMIN_USERNAME = 'workspace-acl-test'; +const NON_DASHBOARDS_ADMIN_USERNAME = 'workspace-acl-test'; const WORKSPACE_TEST_ROLE_NAME = 'workspace-acl-test-role'; const ACLPolicyMap = { [noPermissionWorkspaceName]: {}, [readOnlyWorkspaceName]: { - ...getPolicy('read', NONE_DASHBOARDS_ADMIN_USERNAME), - ...getPolicy('library_read', NONE_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('read', NON_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_read', NON_DASHBOARDS_ADMIN_USERNAME), }, [libraryWriteWorkspaceName]: { - ...getPolicy('read', NONE_DASHBOARDS_ADMIN_USERNAME), - ...getPolicy('library_write', NONE_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('read', NON_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_write', NON_DASHBOARDS_ADMIN_USERNAME), }, [ownerWorkspaceName]: { - ...getPolicy('write', NONE_DASHBOARDS_ADMIN_USERNAME), - ...getPolicy('library_write', NONE_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('write', NON_DASHBOARDS_ADMIN_USERNAME), + ...getPolicy('library_write', NON_DASHBOARDS_ADMIN_USERNAME), }, }; @@ -61,6 +61,21 @@ const setupWorkspace = (workspaceName, datasourceId) => { }); }; +const setupAllTheWorkspaces = () => { + setupWorkspace(noPermissionWorkspaceName, datasourceId).then( + (value) => (noPermissionWorkspaceId = value) + ); + setupWorkspace(readOnlyWorkspaceName, datasourceId).then( + (value) => (readOnlyWorkspaceId = value) + ); + setupWorkspace(libraryWriteWorkspaceName, datasourceId).then( + (value) => (libraryWriteWorkspaceId = value) + ); + setupWorkspace(ownerWorkspaceName, datasourceId).then( + (value) => (ownerWorkspaceId = value) + ); +}; + if ( Cypress.env('WORKSPACE_ENABLED') && Cypress.env('SAVED_OBJECTS_PERMISSION_ENABLED') && @@ -75,42 +90,20 @@ if ( cy.deleteWorkspaceByName(libraryWriteWorkspaceName); cy.deleteWorkspaceByName(ownerWorkspaceName); - cy.createInternalUser(NONE_DASHBOARDS_ADMIN_USERNAME, workspaceTestUser); + cy.createInternalUser(NON_DASHBOARDS_ADMIN_USERNAME, workspaceTestUser); cy.createRole(WORKSPACE_TEST_ROLE_NAME, workspaceTestRole); cy.createRoleMapping(WORKSPACE_TEST_ROLE_NAME, { - users: [NONE_DASHBOARDS_ADMIN_USERNAME], + users: [NON_DASHBOARDS_ADMIN_USERNAME], }); if (Cypress.env('DATASOURCE_MANAGEMENT_ENABLED')) { cy.createDataSourceNoAuth().then((result) => { datasourceId = result[0]; expect(datasourceId).to.be.a('string').that.is.not.empty; - setupWorkspace(noPermissionWorkspaceName, datasourceId).then( - (value) => (noPermissionWorkspaceId = value) - ); - setupWorkspace(readOnlyWorkspaceName, datasourceId).then( - (value) => (readOnlyWorkspaceId = value) - ); - setupWorkspace(libraryWriteWorkspaceName, datasourceId).then( - (value) => (libraryWriteWorkspaceId = value) - ); - setupWorkspace(ownerWorkspaceName, datasourceId).then( - (value) => (ownerWorkspaceId = value) - ); + setupAllTheWorkspaces(datasourceId); }); } else { - setupWorkspace(noPermissionWorkspaceName, datasourceId).then( - (value) => (noPermissionWorkspaceId = value) - ); - setupWorkspace(readOnlyWorkspaceName, datasourceId).then( - (value) => (readOnlyWorkspaceId = value) - ); - setupWorkspace(libraryWriteWorkspaceName, datasourceId).then( - (value) => (libraryWriteWorkspaceId = value) - ); - setupWorkspace(ownerWorkspaceName, datasourceId).then( - (value) => (ownerWorkspaceId = value) - ); + setupAllTheWorkspaces(datasourceId); } }); @@ -133,13 +126,13 @@ if ( ADMIN_AUTH.newUser = originalUser; ADMIN_AUTH.newPassword = originalPassword; cy.deleteRoleMapping(WORKSPACE_TEST_ROLE_NAME); - cy.deleteInternalUser(NONE_DASHBOARDS_ADMIN_USERNAME); + cy.deleteInternalUser(NON_DASHBOARDS_ADMIN_USERNAME); cy.deleteRole(WORKSPACE_TEST_ROLE_NAME); }); describe('Normal user', () => { beforeEach(() => { - ADMIN_AUTH.newUser = NONE_DASHBOARDS_ADMIN_USERNAME; + ADMIN_AUTH.newUser = NON_DASHBOARDS_ADMIN_USERNAME; ADMIN_AUTH.newPassword = workspaceTestUser.password; }); From 7e8e76c6c09e295384b26b9cfbcb60c2be78023e Mon Sep 17 00:00:00 2001 From: SuZhou-Joe Date: Mon, 18 Nov 2024 11:45:27 +0800 Subject: [PATCH 5/5] feat: use admin user to clean up all the resources Signed-off-by: SuZhou-Joe --- .../workspace-plugin/mds_workspace_acl.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js index 9600bdc89..86c03c4e6 100644 --- a/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js +++ b/cypress/integration/core-opensearch-dashboards/opensearch-dashboards/workspace-plugin/mds_workspace_acl.spec.js @@ -108,6 +108,8 @@ if ( }); after(() => { + ADMIN_AUTH.newUser = originalUser; + ADMIN_AUTH.newPassword = originalPassword; cy.removeSampleDataForWorkspace( 'ecommerce', ownerWorkspaceId, @@ -123,8 +125,6 @@ if ( readOnlyWorkspaceId = ''; libraryWriteWorkspaceId = ''; - ADMIN_AUTH.newUser = originalUser; - ADMIN_AUTH.newPassword = originalPassword; cy.deleteRoleMapping(WORKSPACE_TEST_ROLE_NAME); cy.deleteInternalUser(NON_DASHBOARDS_ADMIN_USERNAME); cy.deleteRole(WORKSPACE_TEST_ROLE_NAME);