diff --git a/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceIntegrationTest.kt b/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceIntegrationTest.kt index 6e71aa30b..aa21bceca 100644 --- a/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceIntegrationTest.kt +++ b/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceIntegrationTest.kt @@ -1019,6 +1019,59 @@ class DatasetServiceIntegrationTest : CsmRedisTestBase() { assertEquals(dataset1.connector!!.id, dataset2.connector!!.id) } + @Test + fun `As viewer, I can only see my information in security property for findDatasetById`() { + dataset = makeDatasetWithRole(role = ROLE_VIEWER) + datasetSaved = datasetApiService.createDataset(organizationSaved.id!!, dataset) + + datasetSaved = datasetApiService.findDatasetById(organizationSaved.id!!, datasetSaved.id!!) + assertEquals( + DatasetSecurity( + default = ROLE_NONE, mutableListOf(DatasetAccessControl(TEST_USER_MAIL, ROLE_VIEWER))), + datasetSaved.security) + assertEquals(1, datasetSaved.security!!.accessControlList.size) + } + + @Test + fun `As viewer, I can only see my information in security property for findAllDatasets`() { + every { getCurrentAccountIdentifier(any()) } returns CONNECTED_ADMIN_USER + datasetApiService.deleteDataset(organizationSaved.id!!, datasetSaved.id!!) + every { getCurrentAccountIdentifier(any()) } returns TEST_USER_MAIL + dataset = makeDatasetWithRole(role = ROLE_VIEWER) + datasetSaved = datasetApiService.createDataset(organizationSaved.id!!, dataset) + + val datasets = datasetApiService.findAllDatasets(organizationSaved.id!!, null, null) + datasets.forEach { + assertEquals( + DatasetSecurity( + default = ROLE_NONE, + mutableListOf(DatasetAccessControl(TEST_USER_MAIL, ROLE_VIEWER))), + it.security) + assertEquals(1, it.security!!.accessControlList.size) + } + } + + @Test + fun `As viewer, I can only see my information in security property for searchDatasets`() { + every { getCurrentAccountIdentifier(any()) } returns CONNECTED_ADMIN_USER + datasetApiService.deleteDataset(organizationSaved.id!!, datasetSaved.id!!) + every { getCurrentAccountIdentifier(any()) } returns TEST_USER_MAIL + dataset = makeDatasetWithRole(role = ROLE_VIEWER) + datasetSaved = datasetApiService.createDataset(organizationSaved.id!!, dataset) + + val datasets = + datasetApiService.searchDatasets( + organizationSaved.id!!, DatasetSearch(mutableListOf("dataset")), 0, 10) + datasets.forEach { + assertEquals( + DatasetSecurity( + default = ROLE_NONE, + mutableListOf(DatasetAccessControl(TEST_USER_MAIL, ROLE_VIEWER))), + it.security) + assertEquals(1, it.security!!.accessControlList.size) + } + } + fun makeConnector(): Connector { return Connector( key = "connector", diff --git a/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceRBACTest.kt b/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceRBACTest.kt index e7ec7497c..9ce759909 100644 --- a/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceRBACTest.kt +++ b/dataset/src/integrationTest/kotlin/com/cosmotech/dataset/service/DatasetServiceRBACTest.kt @@ -1858,7 +1858,7 @@ class DatasetServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Dataset RBAC getDatasetAccessControl`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, @@ -2104,7 +2104,7 @@ class DatasetServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Dataset RBAC getDatasetSecurityUsers`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, @@ -2180,7 +2180,7 @@ class DatasetServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Dataset RBAC getDatasetSecurity`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, diff --git a/dataset/src/main/kotlin/com/cosmotech/dataset/service/DatasetServiceImpl.kt b/dataset/src/main/kotlin/com/cosmotech/dataset/service/DatasetServiceImpl.kt index 34b13970b..19ad2133e 100644 --- a/dataset/src/main/kotlin/com/cosmotech/dataset/service/DatasetServiceImpl.kt +++ b/dataset/src/main/kotlin/com/cosmotech/dataset/service/DatasetServiceImpl.kt @@ -167,12 +167,12 @@ class DatasetServiceImpl( datasetRepository.findAll(pageable).toList() } } - + result.forEach { it.security = updateSecurityVisibility(it).security } return result } override fun findDatasetById(organizationId: String, datasetId: String): Dataset { - return getVerifiedDataset(organizationId, datasetId) + return updateSecurityVisibility(getVerifiedDataset(organizationId, datasetId)) } override fun removeAllDatasetCompatibilityElements(organizationId: String, datasetId: String) { @@ -237,7 +237,6 @@ class DatasetServiceImpl( version = existingConnector.version } } - return datasetRepository.save(createdDataset) } @@ -865,12 +864,15 @@ class DatasetServiceImpl( datasetId: String, workspaceId: String ): Dataset { + this.getVerifiedDataset(organizationId, datasetId, PERMISSION_WRITE) sendAddDatasetToWorkspaceEvent(organizationId, workspaceId, datasetId) return addWorkspaceToLinkedWorkspaceIdList(organizationId, datasetId, workspaceId) } @EventListener(AddWorkspaceToDataset::class) fun processEventAddWorkspace(addWorkspaceToDataset: AddWorkspaceToDataset) { + this.getVerifiedDataset( + addWorkspaceToDataset.organizationId, addWorkspaceToDataset.datasetId, PERMISSION_WRITE) addWorkspaceToLinkedWorkspaceIdList( addWorkspaceToDataset.organizationId, addWorkspaceToDataset.datasetId, @@ -901,14 +903,17 @@ class DatasetServiceImpl( datasetId: String, workspaceId: String ): Dataset { - + this.getVerifiedDataset(organizationId, datasetId, PERMISSION_WRITE) sendRemoveDatasetFromWorkspaceEvent(organizationId, workspaceId, datasetId) - return removeWorkspaceFromLinkedWorkspaceIdList(organizationId, datasetId, workspaceId) } @EventListener(RemoveWorkspaceFromDataset::class) fun processEventRemoveWorkspace(removeWorkspaceFromDataset: RemoveWorkspaceFromDataset) { + this.getVerifiedDataset( + removeWorkspaceFromDataset.organizationId, + removeWorkspaceFromDataset.datasetId, + PERMISSION_WRITE) removeWorkspaceFromLinkedWorkspaceIdList( removeWorkspaceFromDataset.organizationId, removeWorkspaceFromDataset.datasetId, @@ -1028,16 +1033,21 @@ class DatasetServiceImpl( val defaultPageSize = csmPlatformProperties.twincache.dataset.defaultPageSize val pageable = constructPageRequest(page, size, defaultPageSize) + var datasetList = listOf() if (pageable != null) { - return datasetRepository - .findDatasetByTags(organizationId, datasetSearch.datasetTags.toSet(), pageable) - .toList() - } - return findAllPaginated(defaultPageSize) { - datasetRepository - .findDatasetByTags(organizationId, datasetSearch.datasetTags.toSet(), it) - .toList() + datasetList = + datasetRepository + .findDatasetByTags(organizationId, datasetSearch.datasetTags.toSet(), pageable) + .toList() } + datasetList = + findAllPaginated(defaultPageSize) { + datasetRepository + .findDatasetByTags(organizationId, datasetSearch.datasetTags.toSet(), it) + .toList() + } + datasetList.forEach { it.security = updateSecurityVisibility(it).security } + return datasetList } override fun getDatasetSecurity(organizationId: String, datasetId: String): DatasetSecurity { @@ -1208,6 +1218,7 @@ class DatasetServiceImpl( } } } + private fun sendTwingraphImportJobInfoRequestEvent( dataset: Dataset, organizationId: String @@ -1275,6 +1286,26 @@ class DatasetServiceImpl( csmRbac.verify(dataset.getRbac(), requiredPermission) return dataset } + + fun updateSecurityVisibility(dataset: Dataset): Dataset { + if (csmRbac.check(dataset.getRbac(), PERMISSION_READ_SECURITY).not()) { + val username = getCurrentAccountIdentifier(csmPlatformProperties) + val retrievedAC = dataset.security!!.accessControlList.firstOrNull { it.id == username } + if (retrievedAC != null) { + return dataset.copy( + security = + DatasetSecurity( + default = dataset.security!!.default, + accessControlList = mutableListOf(retrievedAC))) + } else { + return dataset.copy( + security = + DatasetSecurity( + default = dataset.security!!.default, accessControlList = mutableListOf())) + } + } + return dataset + } } fun Dataset.getRbac(): RbacSecurity { diff --git a/dataset/src/test/kotlin/com/cosmotech/dataset/service/DatasetServiceImplTests.kt b/dataset/src/test/kotlin/com/cosmotech/dataset/service/DatasetServiceImplTests.kt index 58ab2525e..1fe6a7acc 100644 --- a/dataset/src/test/kotlin/com/cosmotech/dataset/service/DatasetServiceImplTests.kt +++ b/dataset/src/test/kotlin/com/cosmotech/dataset/service/DatasetServiceImplTests.kt @@ -12,6 +12,7 @@ import com.cosmotech.api.id.CsmIdGenerator import com.cosmotech.api.rbac.CsmAdmin import com.cosmotech.api.rbac.CsmRbac import com.cosmotech.api.rbac.PERMISSION_CREATE_CHILDREN +import com.cosmotech.api.rbac.ROLE_NONE import com.cosmotech.api.security.ROLE_PLATFORM_ADMIN import com.cosmotech.api.utils.ResourceScanner import com.cosmotech.api.utils.getCurrentAccountIdentifier @@ -59,7 +60,10 @@ fun baseDataset() = name = "My Dataset", description = "My Dataset description", organizationId = ORGANIZATION_ID, - ) + security = + DatasetSecurity( + default = ROLE_NONE, + accessControlList = mutableListOf(DatasetAccessControl(USER_ID, ROLE_NONE)))) @ExtendWith(MockKExtension::class) class DatasetServiceImplTests { @@ -427,11 +431,7 @@ class DatasetServiceImplTests { @Test fun `deleteDataset should delete Dataset and its twingraph`() { - val dataset = - baseDataset() - .copy( - twingraphId = "twingraphId", - ) + val dataset = baseDataset().copy(twingraphId = "twingraphId") every { organizationService.getVerifiedOrganization(ORGANIZATION_ID) } returns Organization() every { datasetRepository.findBy(ORGANIZATION_ID, DATASET_ID) } returns Optional.of(dataset) every { getCurrentAuthenticatedRoles(csmPlatformProperties) } returns diff --git a/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceIntegrationTest.kt b/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceIntegrationTest.kt index 4c667cd75..5f2c73fb1 100644 --- a/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceIntegrationTest.kt +++ b/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceIntegrationTest.kt @@ -364,7 +364,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { component = "organization", roles = mutableMapOf( - ROLE_VIEWER to mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), + ROLE_VIEWER to mutableListOf(PERMISSION_READ), ROLE_USER to mutableListOf( PERMISSION_READ, @@ -389,7 +389,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { component = "workspace", roles = mutableMapOf( - ROLE_VIEWER to mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), + ROLE_VIEWER to mutableListOf(PERMISSION_READ), ROLE_USER to mutableListOf( PERMISSION_READ, @@ -414,7 +414,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { component = "runner", roles = mutableMapOf( - ROLE_VIEWER to mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), + ROLE_VIEWER to mutableListOf(PERMISSION_READ), ROLE_EDITOR to mutableListOf( PERMISSION_READ, @@ -450,8 +450,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { var organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_VIEWER) - assertEquals( - mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), organizationUserPermissions) + assertEquals(mutableListOf(PERMISSION_READ), organizationUserPermissions) organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_USER) @@ -494,14 +493,12 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { runAsDifferentOrganizationUser() val organizationRegistered = organizationApiService.registerOrganization( - createTestOrganizationWithSimpleSecurity( - name, TEST_USER_ID, ROLE_NONE, ROLE_VIEWER)) + createTestOrganizationWithSimpleSecurity(name, TEST_USER_ID, ROLE_NONE, ROLE_USER)) runAsOrganizationUser() var organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_VIEWER) - assertEquals( - mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), organizationUserPermissions) + assertEquals(mutableListOf(PERMISSION_READ), organizationUserPermissions) organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_USER) @@ -573,8 +570,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { runAsDifferentOrganizationUser() val organizationRegistered = organizationApiService.registerOrganization( - createTestOrganizationWithSimpleSecurity( - name, TEST_USER_ID, ROLE_NONE, ROLE_VIEWER)) + createTestOrganizationWithSimpleSecurity(name, TEST_USER_ID, ROLE_NONE, ROLE_USER)) runAsOrganizationUser() assertNotNull(organizationApiService.getOrganizationSecurity(organizationRegistered.id!!)) } @@ -685,14 +681,13 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { runAsDifferentOrganizationUser() val organizationRegistered = organizationApiService.registerOrganization( - createTestOrganizationWithSimpleSecurity( - name, TEST_USER_ID, ROLE_NONE, ROLE_VIEWER)) + createTestOrganizationWithSimpleSecurity(name, TEST_USER_ID, ROLE_NONE, ROLE_USER)) runAsOrganizationUser() val organizationRole = organizationApiService.getOrganizationAccessControl( organizationRegistered.id!!, TEST_USER_ID) assertNotNull(organizationRole) - assertEquals(ROLE_VIEWER, organizationRole.role) + assertEquals(ROLE_USER, organizationRole.role) assertEquals(TEST_USER_ID, organizationRole.id) } } @@ -704,8 +699,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { runAsDifferentOrganizationUser() val organizationRegistered = organizationApiService.registerOrganization( - createTestOrganizationWithSimpleSecurity( - name, TEST_USER_ID, ROLE_NONE, ROLE_VIEWER)) + createTestOrganizationWithSimpleSecurity(name, TEST_USER_ID, ROLE_NONE, ROLE_USER)) runAsOrganizationUser() organizationApiService.getOrganizationAccessControl( organizationRegistered.id!!, UNKNOWN_IDENTIFIER) @@ -793,8 +787,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { runAsDifferentOrganizationUser() val organizationRegistered = organizationApiService.registerOrganization( - createTestOrganizationWithSimpleSecurity( - name, TEST_USER_ID, ROLE_NONE, ROLE_VIEWER)) + createTestOrganizationWithSimpleSecurity(name, TEST_USER_ID, ROLE_NONE, ROLE_USER)) runAsOrganizationUser() assertThrows { organizationApiService.getOrganizationAccessControl( @@ -1013,7 +1006,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { runAsDifferentOrganizationUser() val organizationRegistered = organizationApiService.registerOrganization( - createTestOrganizationWithSimpleSecurity(name, TEST_USER_ID, ROLE_NONE, ROLE_VIEWER)) + createTestOrganizationWithSimpleSecurity(name, TEST_USER_ID, ROLE_NONE, ROLE_USER)) runAsOrganizationUser() val orgaUsers = organizationApiService.getOrganizationSecurityUsers(organizationRegistered.id!!) @@ -1064,6 +1057,36 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { organizationApiService.getVerifiedOrganization("wrong_orga_id") } } + + @Test + fun `As a viewer, I can only see my information in security property for findOrganizationById`() { + val organization = makeOrganization(role = ROLE_VIEWER) + var organizationSaved = organizationApiService.registerOrganization(organization) + + organizationSaved = organizationApiService.findOrganizationById(organizationSaved.id!!) + assertEquals( + OrganizationSecurity( + default = ROLE_NONE, + mutableListOf(OrganizationAccessControl(TEST_USER_ID, ROLE_VIEWER))), + organizationSaved.security) + assertEquals(1, organizationSaved.security!!.accessControlList.size) + } + + @Test + fun `As a viewer, I can only see my information in security property for findAllOrganizations`() { + val organization = makeOrganization(role = ROLE_VIEWER) + organizationApiService.registerOrganization(organization) + + val organizations = organizationApiService.findAllOrganizations(null, null) + organizations.forEach { + assertEquals( + OrganizationSecurity( + default = ROLE_NONE, + mutableListOf(OrganizationAccessControl(TEST_USER_ID, ROLE_VIEWER))), + it.security) + assertEquals(1, it.security!!.accessControlList.size) + } + } } @Nested inner class AsPlatformAdmin { @@ -1292,7 +1315,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { component = "organization", roles = mutableMapOf( - ROLE_VIEWER to mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), + ROLE_VIEWER to mutableListOf(PERMISSION_READ), ROLE_USER to mutableListOf( PERMISSION_READ, @@ -1317,7 +1340,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { component = "workspace", roles = mutableMapOf( - ROLE_VIEWER to mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), + ROLE_VIEWER to mutableListOf(PERMISSION_READ), ROLE_USER to mutableListOf( PERMISSION_READ, @@ -1342,7 +1365,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { component = "runner", roles = mutableMapOf( - ROLE_VIEWER to mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), + ROLE_VIEWER to mutableListOf(PERMISSION_READ), ROLE_EDITOR to mutableListOf( PERMISSION_READ, @@ -1378,8 +1401,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { var organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_VIEWER) - assertEquals( - mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), organizationUserPermissions) + assertEquals(mutableListOf(PERMISSION_READ), organizationUserPermissions) organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_USER) @@ -1428,8 +1450,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { var organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_VIEWER) - assertEquals( - mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), organizationUserPermissions) + assertEquals(mutableListOf(PERMISSION_READ), organizationUserPermissions) organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_USER) @@ -1478,8 +1499,7 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { var organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_VIEWER) - assertEquals( - mutableListOf(PERMISSION_READ, PERMISSION_READ_SECURITY), organizationUserPermissions) + assertEquals(mutableListOf(PERMISSION_READ), organizationUserPermissions) organizationUserPermissions = organizationApiService.getOrganizationPermissions( organizationRegistered.id!!, ROLE_USER) @@ -2256,6 +2276,24 @@ class OrganizationServiceIntegrationTest : CsmRedisTestBase() { accessControlList = mutableListOf(OrganizationAccessControl(userName, role)))) } + fun makeOrganization( + id: String = "organization_id", + userName: String = TEST_USER_ID, + role: String = ROLE_ADMIN + ): Organization { + return Organization( + id = id, + name = "Organization Name", + ownerId = "my.account-tester@cosmotech.com", + security = + OrganizationSecurity( + default = ROLE_NONE, + accessControlList = + mutableListOf( + OrganizationAccessControl(id = TEST_ADMIN_USER_ID, role = "admin"), + OrganizationAccessControl(id = userName, role = role)))) + } + internal fun testFindAllOrganizations(page: Int?, size: Int?, expectedResultSize: Int) { val organizationList = organizationApiService.findAllOrganizations(page, size) logger.info("Organization list retrieved contains : ${organizationList.size} elements") diff --git a/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceRBACTest.kt b/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceRBACTest.kt index fac6663f2..a9a748c51 100644 --- a/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceRBACTest.kt +++ b/organization/src/integrationTest/kotlin/com/cosmotech/organization/service/OrganizationServiceRBACTest.kt @@ -196,7 +196,7 @@ class OrganizationServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test RBAC getOrganizationPermissions`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to true, ROLE_USER to false, @@ -227,7 +227,7 @@ class OrganizationServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test RBAC getOrganizationSecurity`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to true, ROLE_USER to false, @@ -327,7 +327,7 @@ class OrganizationServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test RBAC getOrganizationAccessControl`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to true, ROLE_USER to false, @@ -429,7 +429,7 @@ class OrganizationServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test RBAC getOrganizationSecurityUsers`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to true, ROLE_USER to false, diff --git a/organization/src/main/kotlin/com/cosmotech/organization/service/OrganizationServiceImpl.kt b/organization/src/main/kotlin/com/cosmotech/organization/service/OrganizationServiceImpl.kt index 398d60820..9d84e3b7d 100644 --- a/organization/src/main/kotlin/com/cosmotech/organization/service/OrganizationServiceImpl.kt +++ b/organization/src/main/kotlin/com/cosmotech/organization/service/OrganizationServiceImpl.kt @@ -67,12 +67,12 @@ class OrganizationServiceImpl( organizationRepository.findAll(pageable).toList() } } - + result.forEach { it.security = updateSecurityVisibility(it).security } return result } override fun findOrganizationById(organizationId: String): Organization { - return getVerifiedOrganization(organizationId, PERMISSION_READ) + return updateSecurityVisibility(getVerifiedOrganization(organizationId, PERMISSION_READ)) } override fun registerOrganization(organization: Organization): Organization { @@ -227,6 +227,26 @@ class OrganizationServiceImpl( requiredPermissions.forEach { csmRbac.verify(organization.getRbac(), it) } return organization } + + fun updateSecurityVisibility(organization: Organization): Organization { + if (csmRbac.check(organization.getRbac(), PERMISSION_READ_SECURITY).not()) { + val username = getCurrentAccountIdentifier(csmPlatformProperties) + val retrievedAC = organization.security!!.accessControlList.firstOrNull { it.id == username } + return if (retrievedAC != null) { + organization.copy( + security = + OrganizationSecurity( + default = organization.security!!.default, + accessControlList = mutableListOf(retrievedAC))) + } else { + organization.copy( + security = + OrganizationSecurity( + default = organization.security!!.default, accessControlList = mutableListOf())) + } + } + return organization + } } fun Organization.getRbac(): RbacSecurity { diff --git a/organization/src/test/kotlin/com/cosmotech/organization/service/OrganizationServiceImplTests.kt b/organization/src/test/kotlin/com/cosmotech/organization/service/OrganizationServiceImplTests.kt index 7e501e288..1da3b9921 100644 --- a/organization/src/test/kotlin/com/cosmotech/organization/service/OrganizationServiceImplTests.kt +++ b/organization/src/test/kotlin/com/cosmotech/organization/service/OrganizationServiceImplTests.kt @@ -210,7 +210,7 @@ class OrganizationServiceImplTests { @TestFactory fun `test RBAC getOrganizationSecurity organization`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_ADMIN to false, ROLE_VALIDATOR to true, @@ -243,7 +243,7 @@ class OrganizationServiceImplTests { @TestFactory fun `test RBAC getOrganizationAccessControl organization`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_ADMIN to false, ROLE_VALIDATOR to true, @@ -312,7 +312,7 @@ class OrganizationServiceImplTests { @TestFactory fun `test getOrganizationSecurityUsers`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_ADMIN to false, ROLE_VALIDATOR to true, diff --git a/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceIntegrationTest.kt b/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceIntegrationTest.kt index ba0a28007..89ddb5fe1 100644 --- a/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceIntegrationTest.kt +++ b/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceIntegrationTest.kt @@ -1026,6 +1026,44 @@ class RunnerServiceIntegrationTest : CsmRedisTestBase() { assertEquals(expectedRunId, lastRunId) } + @Test + fun `As a viewer, I can only see my information in security property for getRunner`() { + every { getCurrentAccountIdentifier(any()) } returns defaultName + runner = makeRunner(userName = defaultName, role = ROLE_VIEWER) + runnerSaved = runnerApiService.createRunner(organizationSaved.id!!, workspaceSaved.id!!, runner) + + runnerSaved = + runnerApiService.getRunner(organizationSaved.id!!, workspaceSaved.id!!, runnerSaved.id!!) + assertEquals( + RunnerSecurity( + default = ROLE_NONE, mutableListOf(RunnerAccessControl(defaultName, ROLE_VIEWER))), + runnerSaved.security) + assertEquals(1, runnerSaved.security!!.accessControlList.size) + } + + @Test + fun `As a viewer, I can only see my information in security property for listRunners`() { + every { getCurrentAccountIdentifier(any()) } returns defaultName + organizationSaved = organizationApiService.registerOrganization(organization) + datasetSaved = datasetApiService.createDataset(organizationSaved.id!!, dataset) + materializeTwingraph() + solutionSaved = solutionApiService.createSolution(organizationSaved.id!!, solution) + workspace = makeWorkspace() + workspaceSaved = workspaceApiService.createWorkspace(organizationSaved.id!!, workspace) + runner = makeRunner(userName = defaultName, role = ROLE_VIEWER) + runnerSaved = runnerApiService.createRunner(organizationSaved.id!!, workspaceSaved.id!!, runner) + + val runners = + runnerApiService.listRunners(organizationSaved.id!!, workspaceSaved.id!!, null, null) + runners.forEach { + assertEquals( + RunnerSecurity( + default = ROLE_NONE, mutableListOf(RunnerAccessControl(defaultName, ROLE_VIEWER))), + it.security) + assertEquals(1, it.security!!.accessControlList.size) + } + } + private fun makeConnector(name: String = "name"): Connector { return Connector( key = UUID.randomUUID().toString(), @@ -1056,7 +1094,8 @@ class RunnerServiceIntegrationTest : CsmRedisTestBase() { default = ROLE_NONE, accessControlList = mutableListOf( - DatasetAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN)))) + DatasetAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN), + DatasetAccessControl(defaultName, ROLE_USER)))) } fun makeSolution(organizationId: String = organizationSaved.id!!): Solution { @@ -1089,7 +1128,8 @@ class RunnerServiceIntegrationTest : CsmRedisTestBase() { default = ROLE_NONE, accessControlList = mutableListOf( - SolutionAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN)))) + SolutionAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN), + SolutionAccessControl(id = defaultName, role = ROLE_USER)))) } fun makeOrganization( diff --git a/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceRBACTest.kt b/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceRBACTest.kt index 9790ef292..9a2783f30 100644 --- a/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceRBACTest.kt +++ b/runner/src/integrationTest/kotlin/com/cosmotech/runner/service/RunnerServiceRBACTest.kt @@ -1955,7 +1955,7 @@ class RunnerServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Runner RBAC getRunnerPermissions`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to false, ROLE_NONE to true, @@ -2278,7 +2278,7 @@ class RunnerServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Runner RBAC getRunnerSecurity`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to false, ROLE_NONE to true, @@ -3390,7 +3390,7 @@ class RunnerServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Runner RBAC getRunnerAccessControl`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to false, ROLE_NONE to true, @@ -4459,7 +4459,7 @@ class RunnerServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Runner RBAC getRunnerSecurityUsers`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_VALIDATOR to false, ROLE_NONE to true, diff --git a/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerApiServiceImpl.kt b/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerApiServiceImpl.kt index b5f102718..e3f0e2612 100644 --- a/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerApiServiceImpl.kt +++ b/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerApiServiceImpl.kt @@ -84,7 +84,6 @@ internal class RunnerApiServiceImpl( val defaultPageSize = csmPlatformProperties.twincache.runner.defaultPageSize val pageRequest = constructPageRequest(page, size, defaultPageSize) ?: PageRequest.of(0, defaultPageSize) - return runnerService.listInstances(pageRequest) } diff --git a/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerService.kt b/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerService.kt index 25da3194e..de8480b36 100644 --- a/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerService.kt +++ b/runner/src/main/kotlin/com/cosmotech/runner/service/RunnerService.kt @@ -11,6 +11,7 @@ import com.cosmotech.api.exceptions.CsmClientException import com.cosmotech.api.exceptions.CsmResourceNotFoundException import com.cosmotech.api.rbac.CsmRbac import com.cosmotech.api.rbac.PERMISSION_READ +import com.cosmotech.api.rbac.PERMISSION_READ_SECURITY import com.cosmotech.api.rbac.ROLE_NONE import com.cosmotech.api.rbac.ROLE_USER import com.cosmotech.api.rbac.ROLE_VALIDATOR @@ -61,6 +62,22 @@ class RunnerService( private var workspace: Workspace? = null, ) : CsmPhoenixService() { + fun updateSecurityVisibility(runner: Runner): Runner { + if (csmRbac.check(runner.getRbac(), PERMISSION_READ_SECURITY).not()) { + val username = getCurrentAccountIdentifier(csmPlatformProperties) + val retrievedAC = runner.security!!.accessControlList.firstOrNull { it.id == username } + if (retrievedAC != null) { + runner.security = + RunnerSecurity( + default = runner.security!!.default, accessControlList = mutableListOf(retrievedAC)) + } else { + runner.security = + RunnerSecurity(default = runner.security!!.default, accessControlList = mutableListOf()) + } + } + return runner + } + fun inOrganization(organizationId: String): RunnerService = apply { this.organization = organizationApiService.findOrganizationById(organizationId) } @@ -161,24 +178,29 @@ class RunnerService( CsmResourceNotFoundException( "Runner $runnerId not found in workspace ${workspace!!.id} and organization ${organization!!.id}") } - + updateSecurityVisibility(runner) return RunnerInstance(runner).userHasPermission(PERMISSION_READ) } fun listInstances(pageRequest: PageRequest): List { val isPlatformAdmin = getCurrentAuthenticatedRoles(this.csmPlatformProperties).contains(ROLE_PLATFORM_ADMIN) - return if (!this.csmPlatformProperties.rbac.enabled || isPlatformAdmin) { - runnerRepository - .findByWorkspaceId(organization!!.id!!, workspace!!.id!!, pageRequest) - .toList() + var runners = listOf() + if (!this.csmPlatformProperties.rbac.enabled || isPlatformAdmin) { + runners = + runnerRepository + .findByWorkspaceId(organization!!.id!!, workspace!!.id!!, pageRequest) + .toList() } else { val currentUser = getCurrentAccountIdentifier(this.csmPlatformProperties) - runnerRepository - .findByWorkspaceIdAndSecurity( - organization!!.id!!, workspace!!.id!!, currentUser, pageRequest) - .toList() + runners = + runnerRepository + .findByWorkspaceIdAndSecurity( + organization!!.id!!, workspace!!.id!!, currentUser, pageRequest) + .toList() } + runners.forEach { it.security = updateSecurityVisibility(it).security } + return runners } fun startRunWith(runnerInstance: RunnerInstance): CreatedRun { diff --git a/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceIntegrationTest.kt b/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceIntegrationTest.kt index eef6c9173..48c85ab1f 100644 --- a/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceIntegrationTest.kt +++ b/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceIntegrationTest.kt @@ -520,6 +520,36 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() { } } + @Test + fun `As viewer, I can only see my information in security property for findSolutionById`() { + solutionSaved = solutionApiService.createSolution(organizationSaved.id!!, solution) + every { getCurrentAccountIdentifier(any()) } returns CONNECTED_READER_USER + + solutionSaved = solutionApiService.findSolutionById(organizationSaved.id!!, solutionSaved.id!!) + assertEquals( + SolutionSecurity( + default = ROLE_NONE, + mutableListOf(SolutionAccessControl(CONNECTED_READER_USER, ROLE_VIEWER))), + solutionSaved.security) + assertEquals(1, solutionSaved.security!!.accessControlList.size) + } + + @Test + fun `As viewer, I can only see my information in security property for findAllSolutions`() { + solutionSaved = solutionApiService.createSolution(organizationSaved.id!!, solution) + every { getCurrentAccountIdentifier(any()) } returns CONNECTED_READER_USER + + val solutions = solutionApiService.findAllSolutions(organizationSaved.id!!, null, null) + solutions.forEach { + assertEquals( + SolutionSecurity( + default = ROLE_NONE, + mutableListOf(SolutionAccessControl(CONNECTED_READER_USER, ROLE_VIEWER))), + it.security) + assertEquals(1, it.security!!.accessControlList.size) + } + } + fun makeOrganization(id: String = "organization_id"): Organization { return Organization( id = id, @@ -530,13 +560,15 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() { default = ROLE_NONE, accessControlList = mutableListOf( - OrganizationAccessControl(id = CONNECTED_READER_USER, role = "reader"), - OrganizationAccessControl(id = CONNECTED_ADMIN_USER, role = "admin")))) + OrganizationAccessControl(id = CONNECTED_READER_USER, role = ROLE_VIEWER), + OrganizationAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN)))) } fun makeSolution( organizationId: String = organizationSaved.id!!, - runTemplates: MutableList = mutableListOf() + runTemplates: MutableList = mutableListOf(), + userName: String = CONNECTED_READER_USER, + role: String = ROLE_VIEWER ): Solution { return Solution( id = "solutionId", @@ -550,7 +582,7 @@ class SolutionServiceIntegrationTest : CsmRedisTestBase() { default = ROLE_NONE, accessControlList = mutableListOf( - SolutionAccessControl(id = CONNECTED_READER_USER, role = "reader"), - SolutionAccessControl(id = CONNECTED_ADMIN_USER, role = "admin")))) + SolutionAccessControl(id = userName, role = role), + SolutionAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN)))) } } diff --git a/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceRBACTest.kt b/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceRBACTest.kt index 2b4b14632..f2521fc42 100644 --- a/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceRBACTest.kt +++ b/solution/src/integrationTest/kotlin/com/cosmotech/solution/service/SolutionServiceRBACTest.kt @@ -519,7 +519,7 @@ class SolutionServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Solution RBAC getSolutionAccessControl`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, @@ -596,7 +596,7 @@ class SolutionServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Solution RBAC getSolutionSecurityUsers`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, @@ -1473,7 +1473,7 @@ class SolutionServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Solution RBAC getSolutionSecurity`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, diff --git a/solution/src/main/kotlin/com/cosmotech/solution/service/SolutionServiceImpl.kt b/solution/src/main/kotlin/com/cosmotech/solution/service/SolutionServiceImpl.kt index c52111094..267d8bdef 100644 --- a/solution/src/main/kotlin/com/cosmotech/solution/service/SolutionServiceImpl.kt +++ b/solution/src/main/kotlin/com/cosmotech/solution/service/SolutionServiceImpl.kt @@ -82,11 +82,12 @@ class SolutionServiceImpl( solutionRepository.findByOrganizationId(organizationId, pageable).toList() } } + result.forEach { it.security = updateSecurityVisibility(it).security } return result } override fun findSolutionById(organizationId: String, solutionId: String): Solution { - return getVerifiedSolution(organizationId, solutionId) + return updateSecurityVisibility(getVerifiedSolution(organizationId, solutionId)) } override fun removeAllRunTemplates(organizationId: String, solutionId: String) { @@ -389,6 +390,26 @@ class SolutionServiceImpl( csmRbac.verify(solution.getRbac(), requiredPermission) return solution } + + fun updateSecurityVisibility(solution: Solution): Solution { + if (csmRbac.check(solution.getRbac(), PERMISSION_READ_SECURITY).not()) { + val username = getCurrentAccountIdentifier(csmPlatformProperties) + val retrievedAC = solution.security!!.accessControlList.firstOrNull { it.id == username } + if (retrievedAC != null) { + return solution.copy( + security = + SolutionSecurity( + default = solution.security!!.default, + accessControlList = mutableListOf(retrievedAC))) + } else { + return solution.copy( + security = + SolutionSecurity( + default = solution.security!!.default, accessControlList = mutableListOf())) + } + } + return solution + } } fun Solution.getRbac(): RbacSecurity { diff --git a/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceIntegrationTest.kt b/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceIntegrationTest.kt index e7eb2c6b9..cbf26c8d0 100644 --- a/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceIntegrationTest.kt +++ b/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceIntegrationTest.kt @@ -8,6 +8,7 @@ import com.cosmotech.api.exceptions.CsmResourceNotFoundException import com.cosmotech.api.rbac.ROLE_ADMIN import com.cosmotech.api.rbac.ROLE_EDITOR import com.cosmotech.api.rbac.ROLE_NONE +import com.cosmotech.api.rbac.ROLE_USER import com.cosmotech.api.rbac.ROLE_VIEWER import com.cosmotech.api.tests.CsmRedisTestBase import com.cosmotech.api.utils.getCurrentAccountIdentifier @@ -437,6 +438,55 @@ class WorkspaceServiceIntegrationTest : CsmRedisTestBase() { .linkedDatasetIdList) } + @Test + fun `As a viewer, I can only see my information in security property for findWorkspaceById`() { + every { getCurrentAccountIdentifier(any()) } returns CONNECTED_DEFAULT_USER + organization = + makeOrganization( + id = "Organization test", userName = CONNECTED_DEFAULT_USER, role = ROLE_VIEWER) + organizationSaved = organizationApiService.registerOrganization(organization) + solution = makeSolution(userName = CONNECTED_DEFAULT_USER, role = ROLE_VIEWER) + solutionSaved = solutionApiService.createSolution(organizationSaved.id!!, solution) + dataset = makeDataset() + datasetSaved = datasetApiService.createDataset(organizationSaved.id!!, dataset) + workspace = makeWorkspace() + workspaceSaved = workspaceApiService.createWorkspace(organizationSaved.id!!, workspace) + + workspaceSaved = + workspaceApiService.findWorkspaceById(organizationSaved.id!!, workspaceSaved.id!!) + assertEquals( + WorkspaceSecurity( + default = ROLE_NONE, + mutableListOf(WorkspaceAccessControl(CONNECTED_DEFAULT_USER, ROLE_VIEWER))), + workspaceSaved.security) + assertEquals(1, workspaceSaved.security!!.accessControlList.size) + } + + @Test + fun `As a viewer, I can only see my information in security property for findAllWorkspaces`() { + every { getCurrentAccountIdentifier(any()) } returns CONNECTED_DEFAULT_USER + organization = + makeOrganization( + id = "Organization test", userName = CONNECTED_DEFAULT_USER, role = ROLE_VIEWER) + organizationSaved = organizationApiService.registerOrganization(organization) + solution = makeSolution(userName = CONNECTED_DEFAULT_USER, role = ROLE_VIEWER) + solutionSaved = solutionApiService.createSolution(organizationSaved.id!!, solution) + dataset = makeDataset() + datasetSaved = datasetApiService.createDataset(organizationSaved.id!!, dataset) + workspace = makeWorkspace() + workspaceSaved = workspaceApiService.createWorkspace(organizationSaved.id!!, workspace) + + var workspaces = workspaceApiService.findAllWorkspaces(organizationSaved.id!!, null, null) + workspaces.forEach { + assertEquals( + WorkspaceSecurity( + default = ROLE_NONE, + mutableListOf(WorkspaceAccessControl(CONNECTED_DEFAULT_USER, ROLE_VIEWER))), + it.security) + assertEquals(1, it.security!!.accessControlList.size) + } + } + fun makeOrganization( id: String, userName: String = CONNECTED_ADMIN_USER, @@ -455,7 +505,11 @@ class WorkspaceServiceIntegrationTest : CsmRedisTestBase() { OrganizationAccessControl("userLambda", "viewer")))) } - fun makeSolution(organizationId: String = organizationSaved.id!!): Solution { + fun makeSolution( + organizationId: String = organizationSaved.id!!, + userName: String = CONNECTED_DEFAULT_USER, + role: String = ROLE_USER + ): Solution { return Solution( id = "solutionId", key = UUID.randomUUID().toString(), @@ -467,7 +521,8 @@ class WorkspaceServiceIntegrationTest : CsmRedisTestBase() { default = ROLE_NONE, accessControlList = mutableListOf( - SolutionAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN)))) + SolutionAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN), + SolutionAccessControl(id = userName, role = role)))) } fun makeWorkspace( @@ -507,7 +562,9 @@ class WorkspaceServiceIntegrationTest : CsmRedisTestBase() { fun makeDataset( organizationId: String = organizationSaved.id!!, name: String = "name", - connector: Connector = connectorSaved + connector: Connector = connectorSaved, + userName: String = CONNECTED_DEFAULT_USER, + role: String = ROLE_ADMIN ): Dataset { return Dataset( name = name, @@ -524,6 +581,7 @@ class WorkspaceServiceIntegrationTest : CsmRedisTestBase() { default = ROLE_NONE, accessControlList = mutableListOf( + DatasetAccessControl(id = userName, role = role), DatasetAccessControl(id = CONNECTED_ADMIN_USER, role = ROLE_ADMIN)))) } } diff --git a/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceRBACTest.kt b/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceRBACTest.kt index 71dc97c7d..5344a8272 100644 --- a/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceRBACTest.kt +++ b/workspace/src/integrationTest/kotlin/com/cosmotech/workspace/service/WorkspaceServiceRBACTest.kt @@ -1035,7 +1035,7 @@ class WorkspaceServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Workspace RBAC getWorkspacePermissions`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, @@ -1127,7 +1127,7 @@ class WorkspaceServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Workspace RBAC getWorkspaceSecurity`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, @@ -1411,7 +1411,7 @@ class WorkspaceServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Workspace RBAC getWorkspaceAccessControl`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, @@ -1699,7 +1699,7 @@ class WorkspaceServiceRBACTest : CsmRedisTestBase() { @TestFactory fun `test Workspace RBAC getWorkspaceSecurityUsers`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_USER to false, ROLE_NONE to true, diff --git a/workspace/src/main/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImpl.kt b/workspace/src/main/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImpl.kt index 8d629d190..51cfceecf 100644 --- a/workspace/src/main/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImpl.kt +++ b/workspace/src/main/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImpl.kt @@ -100,11 +100,12 @@ internal class WorkspaceServiceImpl( .toList() } } + result.forEach { it.security = updateSecurityVisibility(it).security } return result } override fun findWorkspaceById(organizationId: String, workspaceId: String): Workspace { - return getVerifiedWorkspace(organizationId, workspaceId) + return updateSecurityVisibility(getVerifiedWorkspace(organizationId, workspaceId)) } override fun createWorkspace(organizationId: String, workspace: Workspace): Workspace { @@ -133,7 +134,7 @@ internal class WorkspaceServiceImpl( workspaceId: String, workspace: Workspace ): Workspace { - val existingWorkspace = getVerifiedWorkspace(organizationId, workspaceId, PERMISSION_WRITE) + val existingWorkspace = this.getVerifiedWorkspace(organizationId, workspaceId, PERMISSION_WRITE) // Security cannot be changed by updateWorkspace var hasChanged = existingWorkspace @@ -305,12 +306,15 @@ internal class WorkspaceServiceImpl( datasetId: String ): Workspace { organizationService.getVerifiedOrganization(organizationId) + this.getVerifiedWorkspace(organizationId, workspaceId, PERMISSION_WRITE) sendAddWorkspaceToDatasetEvent(organizationId, datasetId, workspaceId) return addDatasetToLinkedDatasetIdList(organizationId, workspaceId, datasetId) } @EventListener(AddDatasetToWorkspace::class) fun processEventAddDatasetToWorkspace(addDatasetToWorkspace: AddDatasetToWorkspace) { + this.getVerifiedWorkspace( + addDatasetToWorkspace.organizationId, addDatasetToWorkspace.workspaceId, PERMISSION_WRITE) addDatasetToLinkedDatasetIdList( addDatasetToWorkspace.organizationId, addDatasetToWorkspace.workspaceId, @@ -322,6 +326,7 @@ internal class WorkspaceServiceImpl( workspaceId: String, datasetId: String ): Workspace { + this.getVerifiedWorkspace(organizationId, workspaceId, PERMISSION_WRITE) sendRemoveWorkspaceFromDatasetEvent(organizationId, datasetId, workspaceId) return removeDatasetFromLinkedDatasetIdList(organizationId, workspaceId, datasetId) } @@ -330,6 +335,10 @@ internal class WorkspaceServiceImpl( fun processEventRemoveDatasetFromWorkspace( removeDatasetFromWorkspace: RemoveDatasetFromWorkspace ) { + this.getVerifiedWorkspace( + removeDatasetFromWorkspace.organizationId, + removeDatasetFromWorkspace.workspaceId, + PERMISSION_WRITE) removeDatasetFromLinkedDatasetIdList( removeDatasetFromWorkspace.organizationId, removeDatasetFromWorkspace.workspaceId, @@ -543,6 +552,26 @@ internal class WorkspaceServiceImpl( this.eventPublisher.publishEvent( DeleteHistoricalDataWorkspace(this, organizationId, it.id!!, data.deleteUnknown)) } + + fun updateSecurityVisibility(workspace: Workspace): Workspace { + if (csmRbac.check(workspace.getRbac(), PERMISSION_READ_SECURITY).not()) { + val username = getCurrentAccountIdentifier(csmPlatformProperties) + val retrievedAC = workspace.security!!.accessControlList.firstOrNull { it.id == username } + if (retrievedAC != null) { + return workspace.copy( + security = + WorkspaceSecurity( + default = workspace.security!!.default, + accessControlList = mutableListOf(retrievedAC))) + } else { + return workspace.copy( + security = + WorkspaceSecurity( + default = workspace.security!!.default, accessControlList = mutableListOf())) + } + } + return workspace + } } fun Workspace.getRbac(): RbacSecurity { diff --git a/workspace/src/test/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImplTests.kt b/workspace/src/test/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImplTests.kt index 393054bbb..baf78e76c 100644 --- a/workspace/src/test/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImplTests.kt +++ b/workspace/src/test/kotlin/com/cosmotech/workspace/service/WorkspaceServiceImplTests.kt @@ -11,7 +11,6 @@ import com.cosmotech.api.rbac.CsmAdmin import com.cosmotech.api.rbac.CsmRbac import com.cosmotech.api.rbac.PERMISSION_CREATE_CHILDREN import com.cosmotech.api.rbac.PERMISSION_READ -import com.cosmotech.api.rbac.PERMISSION_WRITE import com.cosmotech.api.rbac.ROLE_ADMIN import com.cosmotech.api.rbac.ROLE_EDITOR import com.cosmotech.api.rbac.ROLE_NONE @@ -289,11 +288,9 @@ class WorkspaceServiceImplTests { id = WORKSPACE_ID, key = "my-workspace-key", name = "my workspace name", - solution = WorkspaceSolution(solutionId = "SOL-my-solution-id")) - workspace.security = WorkspaceSecurity(ROLE_ADMIN, mutableListOf()) - every { - workspaceServiceImpl.getVerifiedWorkspace(ORGANIZATION_ID, WORKSPACE_ID, PERMISSION_WRITE) - } returns workspace + solution = WorkspaceSolution(solutionId = "SOL-my-solution-id"), + security = WorkspaceSecurity(ROLE_ADMIN, mutableListOf())) + every { workspaceRepository.findByIdOrNull(WORKSPACE_ID) } returns workspace every { solutionService.findSolutionById(ORGANIZATION_ID, any()) } throws CsmResourceNotFoundException("Solution not found") assertThrows { @@ -470,7 +467,7 @@ class WorkspaceServiceImplTests { @TestFactory fun `test RBAC get workspace security`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_ADMIN to false, ROLE_VALIDATOR to true, @@ -504,7 +501,7 @@ class WorkspaceServiceImplTests { @TestFactory fun `test RBAC get workspace access control`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_ADMIN to false, ROLE_VALIDATOR to true, @@ -579,7 +576,7 @@ class WorkspaceServiceImplTests { @TestFactory fun `test RBAC get workspace security users`() = mapOf( - ROLE_VIEWER to false, + ROLE_VIEWER to true, ROLE_EDITOR to false, ROLE_ADMIN to false, ROLE_VALIDATOR to true,