diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellAccessHandler.java b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellAccessHandler.java index 90b5e082..5dd8ec2e 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellAccessHandler.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellAccessHandler.java @@ -96,7 +96,7 @@ public Set filterSpecificAssetIdsByTenantId( Set root,CriteriaQuery cq, CriteriaBuilder Instant searchValue = shellCursor.getShellSearchCursor(); cq.orderBy( criteriaBuilder.asc( criteriaBuilder.coalesce( root.get( sortFieldName ), Instant.now() ) ) ); - if(owningTenantId == tenantId){ + if(owningTenantId.equals( tenantId )){ return criteriaBuilder.greaterThan( root.get( sortFieldName ), searchValue ); } diff --git a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AbstractAssetAdministrationShellApi.java b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AbstractAssetAdministrationShellApi.java index 4a5331cd..c74c9b1a 100644 --- a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AbstractAssetAdministrationShellApi.java +++ b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AbstractAssetAdministrationShellApi.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.tractusx.semantics.RegistryProperties; +import org.eclipse.tractusx.semantics.registry.repository.ShellRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -66,6 +67,9 @@ public abstract class AbstractAssetAdministrationShellApi { @Autowired private RegistryProperties registryProperties; + @Autowired + protected ShellRepository shellRepository; + protected String getId(ObjectNode payload) { return payload.get("identification").textValue(); } diff --git a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiSecurityTest.java b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiSecurityTest.java index 8eb73bb3..c4440840 100644 --- a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiSecurityTest.java +++ b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiSecurityTest.java @@ -20,7 +20,6 @@ package org.eclipse.tractusx.semantics.registry; import static org.hamcrest.Matchers.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.util.List; import java.util.UUID; @@ -40,11 +39,7 @@ import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultHandlers; -import java.util.List; -import java.util.UUID; - import static org.eclipse.tractusx.semantics.registry.TestUtil.*; -import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import com.fasterxml.jackson.databind.JsonNode; @@ -1042,4 +1037,208 @@ public void testFindExternalShellIdsBySpecificAssetIdsWithDefaultClosedTenantBas .andExpect(jsonPath("$.result", hasSize(0))); } } + + /** + * The specificAssetId#externalSubjectId indicates which tenant is allowed to see the shell with all properties or not. + */ + @Nested + @DisplayName("Tenant based Shell visibility test") + class TenantBasedShellVisibilityTest { + + @BeforeEach + public void before() { + shellRepository.deleteAll(); + } + + @Test + public void testGetAllShellsByOwningTenantId() throws Exception { + AssetAdministrationShellDescriptor shellPayload = TestUtil.createCompleteAasDescriptor(); + shellPayload.setId(UUID.randomUUID().toString()); + List shellpayloadSpecificAssetIDs = shellPayload.getSpecificAssetIds(); + // Make all specificAssetIds to closed with externalSubjectId==null. + shellpayloadSpecificAssetIDs.forEach( specificAssetId -> specificAssetId.setExternalSubjectId( null ) ); + shellPayload.setSpecificAssetIds( shellpayloadSpecificAssetIDs ); + + performShellCreateRequest(mapper.writeValueAsString(shellPayload)); + + // Request with owner TenantId (TENANT_ONE) returns one shell + mvc.perform( + MockMvcRequestBuilders + .get(SHELL_BASE_PATH) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.result").exists()) + .andExpect(jsonPath("$.result", hasSize(1))) + .andExpect(jsonPath("$.result[0].description[*]").isNotEmpty()) + .andExpect(jsonPath("$.result[0].idShort",is(shellPayload.getIdShort()))); + + // Request with TenantId (TENANT_TWO) returns no shells, because the shell not includes the externalSubjectId of Tenant_two as specificId + mvc.perform( + MockMvcRequestBuilders + .get(SHELL_BASE_PATH) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantTwo().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.result").exists()) + .andExpect(jsonPath("$.result", hasSize(0))); + } + + @Test + public void testGetAllShellsWithPublicAccessByTenantId() throws Exception { + // the keyPrefix ensures that this test can run against a persistent database multiple times + String keyPrefix = UUID.randomUUID().toString(); + AssetAdministrationShellDescriptor shellPayload = TestUtil.createCompleteAasDescriptor(); + shellPayload.setSpecificAssetIds(null); + shellPayload.setId(UUID.randomUUID().toString()); + + // asset1 is only visible for the owner because the externalSubjectId = null + SpecificAssetId asset1 = TestUtil.createSpecificAssetId(keyPrefix + "defaultClosed","value_1",null); + // asset2 is visible for everyone, because externalSubjectId = PUBLIC_READABLE and specificAssetKey is manufacturerPartId (which is in the list of allowedTypes via application.yml) + SpecificAssetId asset2 = TestUtil.createSpecificAssetId("manufacturerPartId","value_2",List.of(getExternalSubjectIdWildcardPrefix())); + // asset3 is visible for tenantTwo, because externalSubjectId = tenantTwo + SpecificAssetId asset3 = TestUtil.createSpecificAssetId(keyPrefix + "tenantTwo","value_2",List.of(jwtTokenFactory.tenantTwo().getTenantId())); + + shellPayload.setSpecificAssetIds(List.of(asset1,asset2,asset3)); + performShellCreateRequest(mapper.writeValueAsString(shellPayload)); + + // Request with TenantId (TENANT_TWO) returns one shell with extend visibility of shell-properties, because tenantId is included in the specificAssetIds + mvc.perform( + MockMvcRequestBuilders + .get(SHELL_BASE_PATH) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantTwo().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.result").exists()) + .andExpect(jsonPath("$.result", hasSize(1))) + .andExpect(jsonPath("$.result[0].description[*]").isNotEmpty()) + .andExpect(jsonPath("$.result[0].idShort",is(shellPayload.getIdShort()))) + .andExpect(jsonPath("$.result[0].id",is( shellPayload.getId() ))) + .andExpect(jsonPath("$.result[0].submodelDescriptors[*]").exists()) + .andExpect(jsonPath("$.result[0].specificAssetIds[*]").exists()); + + // Request with TenantId (TENANT_THREE) returns one shell with only public visible shell-properties + mvc.perform( + MockMvcRequestBuilders + .get(SHELL_BASE_PATH) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantThree().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.result").exists()) + .andExpect(jsonPath("$.result", hasSize(1))) + .andExpect(jsonPath("$.result[0].description[*]").doesNotExist()) + .andExpect(jsonPath("$.result[0].idShort").doesNotExist()) + .andExpect(jsonPath("$.result[0].id",is( shellPayload.getId() ))) + .andExpect(jsonPath("$.result[0].submodelDescriptors[*]").exists()) + .andExpect(jsonPath("$.result[0].specificAssetIds[*]").exists()); + } + + @Test + public void testGetShellByExternalIdByOwningTenantId() throws Exception { + AssetAdministrationShellDescriptor shellPayload = TestUtil.createCompleteAasDescriptor(); + shellPayload.setId(UUID.randomUUID().toString()); + List shellpayloadSpecificAssetIDs = shellPayload.getSpecificAssetIds(); + // Make all specificAssetIds to closed with externalSubjectId==null. + shellpayloadSpecificAssetIDs.forEach( specificAssetId -> specificAssetId.setExternalSubjectId( null ) ); + shellPayload.setSpecificAssetIds( shellpayloadSpecificAssetIDs ); + + performShellCreateRequest(mapper.writeValueAsString(shellPayload)); + + + // Request with owner TenantId (TENANT_ONE) returns one shell + mvc.perform( + MockMvcRequestBuilders + .get(SINGLE_SHELL_BASE_PATH, getEncodedValue( shellPayload.getId() )) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description[*]").isNotEmpty()) + .andExpect(jsonPath("$.idShort",is(shellPayload.getIdShort()))); + + // Request with TenantId (TENANT_TWO) returns no shell, because the shell not includes the externalSubjectId of Tenant_two as specificId + mvc.perform( + MockMvcRequestBuilders + .get(SINGLE_SHELL_BASE_PATH, getEncodedValue( shellPayload.getId() )) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantTwo().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isNotFound()); + } + + @Test + public void testGetAllShellByExternalIdWithPublicAccessByTenantId() throws Exception { + // the keyPrefix ensures that this test can run against a persistent database multiple times + String keyPrefix = UUID.randomUUID().toString(); + AssetAdministrationShellDescriptor shellPayload = TestUtil.createCompleteAasDescriptor(); + shellPayload.setSpecificAssetIds(null); + shellPayload.setId(UUID.randomUUID().toString()); + + // asset1 is only visible for the owner because the externalSubjectId = null + SpecificAssetId asset1 = TestUtil.createSpecificAssetId(keyPrefix + "defaultClosed","value_1",null); + // asset2 is visible for everyone, because externalSubjectId = PUBLIC_READABLE and specificAssetKey is manufacturerPartId (which is in the list of allowedTypes via application.yml) + SpecificAssetId asset2 = TestUtil.createSpecificAssetId("manufacturerPartId","value_2",List.of(getExternalSubjectIdWildcardPrefix())); + // asset3 is visible for tenantTwo, because externalSubjectId = tenantTwo + SpecificAssetId asset3 = TestUtil.createSpecificAssetId(keyPrefix + "tenantTwo","value_2",List.of(jwtTokenFactory.tenantTwo().getTenantId())); + + shellPayload.setSpecificAssetIds(List.of(asset1,asset2,asset3)); + performShellCreateRequest(mapper.writeValueAsString(shellPayload)); + + // Request with TenantId (TENANT_TWO) returns one shell with extend visibility of shell-properties, because tenantId is included in the specificAssetIds + mvc.perform( + MockMvcRequestBuilders + .get(SINGLE_SHELL_BASE_PATH, getEncodedValue( shellPayload.getId() )) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantTwo().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description[*]").isNotEmpty()) + .andExpect(jsonPath("$.idShort",is(shellPayload.getIdShort()))) + .andExpect(jsonPath("$.id",is( shellPayload.getId() ))) + .andExpect(jsonPath("$.submodelDescriptors[*]").exists()) + .andExpect(jsonPath("$.specificAssetIds[*]").exists()); + + // Request with TenantId (TENANT_THREE) returns one shell with only public visible shell-properties + mvc.perform( + MockMvcRequestBuilders + .get(SINGLE_SHELL_BASE_PATH, getEncodedValue( shellPayload.getId() )) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantThree().getTenantId() ) + .queryParam("pageSize", "100") + .accept(MediaType.APPLICATION_JSON) + .with(jwtTokenFactory.allRoles()) + ) + .andDo(MockMvcResultHandlers.print()) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.description[*]").doesNotExist()) + .andExpect(jsonPath("$.idShort").doesNotExist()) + .andExpect(jsonPath("$.id",is( shellPayload.getId() ))) + .andExpect(jsonPath("$.submodelDescriptors[*]").exists()) + .andExpect(jsonPath("$.specificAssetIds[*]").exists()); + } + } }