From 32e7b5d961ae32581bbf2ef3f9e552f2fc302026 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Tue, 26 Mar 2024 16:53:45 +0100 Subject: [PATCH] - Fixed behaviour of access rule management in case of multiple specificAssetIds with same keys and different values. --- CHANGELOG.md | 3 +- .../api/model/ShellVisibilityCriteria.java | 3 +- .../model/ShellVisibilityCriteriaTest.java | 8 +- .../SqlBackedAccessControlRuleService.java | 45 +++++- ...SqlBackedAccessControlRuleServiceTest.java | 29 +++- .../service/GranularShellAccessHandler.java | 11 +- ...setAdministrationShellApiSecurityTest.java | 34 ++-- ...setAdministrationShellApiSecurityTest.java | 147 +++++++++++++++++- docs/README.md | 5 +- pom.xml | 4 +- 10 files changed, 251 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a933c9fc..1abc29a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added ## fixed - KICS findings fixed - +- Fixed behaviour of access rule management in case of multiple specificAssetIds with same keys and different values. +- Update Springboot to version 3.2.4 ## 0.4.1 ### Added diff --git a/access-control-service-interface/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteria.java b/access-control-service-interface/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteria.java index 9b640040..3ca0308e 100644 --- a/access-control-service-interface/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteria.java +++ b/access-control-service-interface/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteria.java @@ -20,10 +20,11 @@ package org.eclipse.tractusx.semantics.accesscontrol.api.model; +import java.util.Map; import java.util.Set; import lombok.NonNull; public record ShellVisibilityCriteria( - @NonNull String aasId, @NonNull Set visibleSpecificAssetIdNames, @NonNull Set visibleSemanticIds, boolean publicOnly) { + @NonNull String aasId, @NonNull Map> visibleSpecificAssetIdNames, @NonNull Set visibleSemanticIds, boolean publicOnly) { } diff --git a/access-control-service-interface/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteriaTest.java b/access-control-service-interface/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteriaTest.java index c36c7139..1ae76906 100644 --- a/access-control-service-interface/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteriaTest.java +++ b/access-control-service-interface/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/api/model/ShellVisibilityCriteriaTest.java @@ -22,6 +22,7 @@ import static org.assertj.core.api.Assertions.*; +import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.stream.Stream; @@ -37,7 +38,7 @@ public static Stream nullProvider() { return Stream. builder() .add( Arguments.of( null, null, null, true ) ) .add( Arguments.of( UUID.randomUUID().toString(), null, null, true ) ) - .add( Arguments.of( null, Set.of(), null, false ) ) + .add( Arguments.of( null, Map.of(), null, false ) ) .add( Arguments.of( null, null, Set.of(), false ) ) .build(); } @@ -45,7 +46,7 @@ public static Stream nullProvider() { @ParameterizedTest @MethodSource( "nullProvider" ) void testConstructorCalledWithNullExpectException( - String aasId, Set visibleSpecificAssetIdNames, Set visibleSemanticIds, boolean publicOnly ) { + String aasId, Map> visibleSpecificAssetIdNames, Set visibleSemanticIds, boolean publicOnly ) { assertThatThrownBy( () -> new ShellVisibilityCriteria( aasId, visibleSpecificAssetIdNames, visibleSemanticIds, publicOnly ) ) .isExactlyInstanceOf( NullPointerException.class ); } @@ -53,7 +54,8 @@ void testConstructorCalledWithNullExpectException( @Test void testConstructorCalledWithValidDataExpectSuccess() { final String aasId = UUID.randomUUID().toString(); - final Set visibleSpecificAssetIdNames = Set.of( "name1" ); + + final Map> visibleSpecificAssetIdNames = Map.of( "name1", Set.of( "" ) ); final Set visibleSemanticIds = Set.of( "name2" ); final boolean publicOnly = true; diff --git a/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleService.java b/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleService.java index 51c857f2..296e9cba 100644 --- a/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleService.java +++ b/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleService.java @@ -22,6 +22,8 @@ import java.time.Instant; import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -65,9 +67,11 @@ public List filterValidSpecificAssetIdsForLookup( .filter( accessControlRule -> aShellContext.specificAssetIds().containsAll( accessControlRule.getMandatorySpecificAssetIds() ) ) .flatMap( accessControlRule -> accessControlRule.getVisibleSpecificAssetIdNames().stream() ) .collect( Collectors.toSet() ); + Map> duplicatedSpecificAssetIds = getDuplicateSpecificAssetIds( aShellContext ); return aShellContext.specificAssetIds().stream() - .filter( id -> visibleSpecificAssetIdNames.contains( id.name() ) ) - .collect( Collectors.toSet() ).containsAll( userQuery ); + .filter( id -> isSpecificAssetIdVisible( id, visibleSpecificAssetIdNames, duplicatedSpecificAssetIds ) ) + .collect( Collectors.toSet() ) + .containsAll( userQuery ); } ) .map( ShellVisibilityContext::aasId ) .toList(); @@ -76,15 +80,28 @@ public List filterValidSpecificAssetIdsForLookup( @Override public ShellVisibilityCriteria fetchVisibilityCriteriaForShell( ShellVisibilityContext shellContext, String bpn ) throws DenyAccessException { Set matchingAccessControlRules = findMatchingAccessControlRules( shellContext, bpn ); - Set visibleSpecificAssetIdNames = matchingAccessControlRules.stream() - .flatMap( accessControlRule -> accessControlRule.getVisibleSpecificAssetIdNames().stream() ) - .collect( Collectors.toSet() ); + + Map> duplicatedSpecificAssetIds = getDuplicateSpecificAssetIds( shellContext ); + + Map> visibleSpecificAssetIds = new HashMap<>(); + matchingAccessControlRules.forEach( rule -> { + rule.getMandatorySpecificAssetIds().forEach( specificAssetId -> { + if ( duplicatedSpecificAssetIds.containsKey( specificAssetId.name() ) ) { + visibleSpecificAssetIds.computeIfAbsent( specificAssetId.name(), k -> new HashSet<>() ).add( specificAssetId.value() ); + } + } ); + + rule.getVisibleSpecificAssetIdNames().forEach( specificAssetIdName -> { + visibleSpecificAssetIds.computeIfAbsent( specificAssetIdName, k -> new HashSet<>() ); + } ); + } ); + Set visibleSemanticIds = matchingAccessControlRules.stream() .map( AccessRulePolicy::getVisibleSemanticIds ) .flatMap( Collection::stream ) .collect( Collectors.toSet() ); boolean publicOnly = matchingAccessControlRules.stream().noneMatch( rule -> rule.getBpn().equals( bpn ) ); - return new ShellVisibilityCriteria( shellContext.aasId(), visibleSpecificAssetIdNames, visibleSemanticIds, publicOnly ); + return new ShellVisibilityCriteria( shellContext.aasId(), visibleSpecificAssetIds, visibleSemanticIds, publicOnly ); } @Override @@ -123,4 +140,20 @@ private Set findMatchingAccessControlRules( ShellVisibilityCon } return matching; } + + private Map> getDuplicateSpecificAssetIds( ShellVisibilityContext shellContext ) { + return shellContext.specificAssetIds().stream() + .collect( Collectors.groupingBy( SpecificAssetId::name, + Collectors.mapping( SpecificAssetId::value, Collectors.toSet() ) ) ) + .entrySet().stream() + .filter( specificAssetIds -> specificAssetIds.getValue().size() > 1 ) + .collect( Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue ) ); + } + + private boolean isSpecificAssetIdVisible( SpecificAssetId specificAssetId, Set visibleSpecificAssetIds, + Map> duplicatedSpecificAssetIds ) { + Set identifierValues = duplicatedSpecificAssetIds.getOrDefault( specificAssetId.name(), Set.of() ); + return visibleSpecificAssetIds.contains( specificAssetId.name() ) + && (identifierValues.isEmpty() || identifierValues.contains( specificAssetId.value() )); + } } diff --git a/access-control-service-sql-impl/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleServiceTest.java b/access-control-service-sql-impl/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleServiceTest.java index 1c44c853..016f2d3e 100644 --- a/access-control-service-sql-impl/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleServiceTest.java +++ b/access-control-service-sql-impl/src/test/java/org/eclipse/tractusx/semantics/accesscontrol/sql/service/SqlBackedAccessControlRuleServiceTest.java @@ -29,6 +29,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.UUID; @@ -89,19 +90,37 @@ public static Stream matchingSpecificAssetIdFilterProvider() { public static Stream matchingSpecificAssetIdVisibilityProvider() { return Stream. builder() .add( Arguments.of( - Set.of( MANUFACTURER_PART_ID_99991, CUSTOMER_PART_ID_ACME001, PART_INSTANCE_ID_00001, VERSION_NUMBER_01 ), + Set.of( MANUFACTURER_PART_ID_99991, CUSTOMER_PART_ID_ACME001, PART_INSTANCE_ID_00001, PART_INSTANCE_ID_00002, VERSION_NUMBER_01 ), BPNA, - Set.of( MANUFACTURER_PART_ID, CUSTOMER_PART_ID, PART_INSTANCE_ID, VERSION_NUMBER ), + Map.of( + MANUFACTURER_PART_ID_99991.name(), + Set.of(), + CUSTOMER_PART_ID_ACME001.name(), Set.of(), + PART_INSTANCE_ID_00001.name(), Set.of( PART_INSTANCE_ID_00001.value() ), + VERSION_NUMBER_01.name(), Set.of() + ), Set.of( TRACEABILITYV_1_1_0 ) ) ) .add( Arguments.of( Set.of( MANUFACTURER_PART_ID_99991, CUSTOMER_PART_ID_ACME001, PART_INSTANCE_ID_00002, REVISION_NUMBER_01 ), BPNA, - Set.of( MANUFACTURER_PART_ID, CUSTOMER_PART_ID, PART_INSTANCE_ID, REVISION_NUMBER ), + Map.of( + MANUFACTURER_PART_ID_99991.name(), Set.of(), + CUSTOMER_PART_ID_ACME001.name(), Set.of(), + PART_INSTANCE_ID_00001.name(), Set.of(), + REVISION_NUMBER_01.name(), Set.of() + ), Set.of( PRODUCT_CARBON_FOOTPRINTV_1_1_0 ) ) ) .add( Arguments.of( Set.of( MANUFACTURER_PART_ID_99991, CUSTOMER_PART_ID_ACME001, PART_INSTANCE_ID_00001, VERSION_NUMBER_01, REVISION_NUMBER_01 ), BPNA, - Set.of( MANUFACTURER_PART_ID, CUSTOMER_PART_ID, PART_INSTANCE_ID, REVISION_NUMBER, VERSION_NUMBER ), + Map.of( + MANUFACTURER_PART_ID_99991.name(), + Set.of(), + CUSTOMER_PART_ID_ACME001.name(), Set.of(), + PART_INSTANCE_ID_00001.name(), Set.of(), + VERSION_NUMBER_01.name(), Set.of(), + REVISION_NUMBER_01.name(), Set.of() + ), Set.of( PRODUCT_CARBON_FOOTPRINTV_1_1_0, TRACEABILITYV_1_1_0 ) ) ) .build(); } @@ -165,7 +184,7 @@ void testFetchVisibilityCriteriaForShellWhenNoMatchingBpnExpectException() { @MethodSource( "matchingSpecificAssetIdVisibilityProvider" ) void testFetchVisibilityCriteriaForShellWhenMatchingSpecificAssetIdsProvidedExpectFilteringList( Set specificAssetIds, String bpn, - Set expectedSpecificAssetIdNames, Set expectedSemanticIds ) throws DenyAccessException { + Map> expectedSpecificAssetIdNames, Set expectedSemanticIds ) throws DenyAccessException { ShellVisibilityContext shellContext = new ShellVisibilityContext( UUID.randomUUID().toString(), specificAssetIds ); final var actual = underTest.fetchVisibilityCriteriaForShell( shellContext, bpn ); diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/GranularShellAccessHandler.java b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/GranularShellAccessHandler.java index 3e64a88b..fe34b060 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/GranularShellAccessHandler.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/GranularShellAccessHandler.java @@ -23,6 +23,7 @@ import java.time.Instant; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; @@ -151,7 +152,7 @@ private Shell filterShellContents( Shell shell, ShellVisibilityCriteria visibili Set filteredSubmodels = shell.getSubmodels().stream() .filter( submodel -> submodel.getSemanticId().getKeys().stream() .anyMatch( key -> key.getType() == ReferenceKeyType.SUBMODEL - && visibilityCriteria.visibleSemanticIds().contains( key.getValue() ) ) ) + && visibilityCriteria.visibleSemanticIds().contains( key.getValue() ) ) ) .collect( Collectors.toSet() ); final Shell filtered; if ( visibilityCriteria.publicOnly() ) { @@ -182,8 +183,7 @@ private ShellVisibilityContext toShellVisibilityContext( Shell shell ) { private Set filterSpecificAssetIdsByTenantId( Set shellIdentifiers, ShellVisibilityCriteria visibilityCriteria ) { //noinspection SimplifyStreamApiCallChains return shellIdentifiers.stream() - .filter( identifier -> identifier.getKey().equals( ShellIdentifier.GLOBAL_ASSET_ID_KEY ) - || visibilityCriteria.visibleSpecificAssetIdNames().contains( identifier.getKey() ) ) + .filter( identifier -> isSpecificAssetIdVisible( visibilityCriteria.visibleSpecificAssetIdNames(), identifier ) ) //TODO: Do we need to clear the list of external subject Ids? .map( identifier -> { Optional.ofNullable( identifier.getExternalSubjectId() ) @@ -191,6 +191,11 @@ private Set filterSpecificAssetIdsByTenantId( Set> visibleSpecificAssetIdNames, ShellIdentifier identifier ) { + Set identifierValues = visibleSpecificAssetIdNames.get( identifier.getKey() ); + return identifier.getKey().equals( ShellIdentifier.GLOBAL_ASSET_ID_KEY ) || + identifierValues != null && (identifierValues.isEmpty() || identifierValues.contains( identifier.getValue() )); } } 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 1a2ea719..2a6100ae 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 @@ -24,6 +24,7 @@ import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import java.util.ArrayList; import java.util.Base64; import java.util.List; import java.util.UUID; @@ -664,10 +665,6 @@ public void testGetAllShellsWithDefaultClosedFilteredSpecificAssetIdsByTenantId( @Test public void testGetShellWithFilteredSpecificAssetIdsByTenantId() throws Exception { - AssetAdministrationShellDescriptor shellPayload = TestUtil - .createCompleteAasDescriptor( keyPrefix + "semanticId", "http://example.com/" ); - shellPayload.setId( keyPrefix ); - shellPayload.setSpecificAssetIds( null ); SpecificAssetId asset1 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantTwoAssetIdValue", List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); SpecificAssetId asset2 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId2", "tenantThreeAssetIdValue", @@ -680,11 +677,22 @@ public void testGetShellWithFilteredSpecificAssetIdsByTenantId() throws Exceptio SpecificAssetId asset5 = TestUtil.createSpecificAssetId( "manufacturerPartId", keyPrefix + "wildcardAllowed", List.of( getExternalSubjectIdWildcardPrefix() ) ); - shellPayload.setSpecificAssetIds( List.of( asset1, asset2, asset3, asset4, asset5 ) ); + List specificAssetIds = List.of( asset1, asset2, asset3, asset4, asset5 ); + List expectedSpecificAssetIdsTenantTwo = List.of( asset1, asset3, asset5 ); + testGetShellWithFilteredSpecificAssetIdsByTenantId( specificAssetIds, expectedSpecificAssetIdsTenantTwo ); + } + + public void testGetShellWithFilteredSpecificAssetIdsByTenantId( List specificAssetIds, List expectedSpecificAssetIds ) + throws Exception { + AssetAdministrationShellDescriptor shellPayload = TestUtil + .createCompleteAasDescriptor( keyPrefix + "semanticId", "http://example.com/" ); + shellPayload.setId( keyPrefix ); + shellPayload.setSpecificAssetIds( specificAssetIds ); performShellCreateRequest( mapper.writeValueAsString( shellPayload ) ); String shellId = shellPayload.getId(); String encodedShellId = getEncodedValue( shellId ); + // Owner of tenant has access to all specificAssetIds mvc.perform( MockMvcRequestBuilders @@ -696,11 +704,13 @@ public void testGetShellWithFilteredSpecificAssetIdsByTenantId() throws Exceptio .andDo( MockMvcResultHandlers.print() ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$.id", equalTo( shellId ) ) ) - .andExpect( jsonPath( "$.specificAssetIds[*].value", - containsInAnyOrder( "tenantTwoAssetIdValue", "tenantThreeAssetIdValue", "withoutTenantAssetIdValue", "ignoreWildcard", - keyPrefix + "wildcardAllowed" ) ) ); + .andExpect( jsonPath( "$.specificAssetIds[*].name", hasItems( specificAssetIds.stream().map( SpecificAssetId::getName ).toArray() ) ) ) + .andExpect( jsonPath( "$.specificAssetIds[*].value", hasItems( specificAssetIds.stream().map( SpecificAssetId::getValue ).toArray() ) ) ); // test with tenant two + ArrayList hiddenSpecificAssetIds = new ArrayList<>( specificAssetIds ); + hiddenSpecificAssetIds.removeAll( expectedSpecificAssetIds ); + mvc.perform( MockMvcRequestBuilders .get( SINGLE_SHELL_BASE_PATH, encodedShellId ) @@ -711,10 +721,10 @@ public void testGetShellWithFilteredSpecificAssetIdsByTenantId() throws Exceptio .andDo( MockMvcResultHandlers.print() ) .andExpect( status().isOk() ) .andExpect( jsonPath( "$.id", equalTo( shellId ) ) ) - .andExpect( jsonPath( "$.specificAssetIds[*].value", - hasItems( "tenantTwoAssetIdValue", "withoutTenantAssetIdValue", keyPrefix + "wildcardAllowed" ) ) ) - .andExpect( jsonPath( "$.specificAssetIds[*].value", - not( hasItems( "tenantThreeAssetIdValue", "ignoreWildcard" ) ) ) ); + .andExpect( jsonPath( "$.specificAssetIds[*].name", hasItems( expectedSpecificAssetIds.stream().map( SpecificAssetId::getName ).toArray() ) ) ) + .andExpect( jsonPath( "$.specificAssetIds[*].value", hasItems( expectedSpecificAssetIds.stream().map( SpecificAssetId::getValue ).toArray() ) ) ) + .andExpect( + jsonPath( "$.specificAssetIds[*].value", not( hasItems( hiddenSpecificAssetIds.stream().map( SpecificAssetId::getValue ).toArray() ) ) ) ); } @Test diff --git a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/GranularAssetAdministrationShellApiSecurityTest.java b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/GranularAssetAdministrationShellApiSecurityTest.java index 809a76f9..f3405f1b 100644 --- a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/GranularAssetAdministrationShellApiSecurityTest.java +++ b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/GranularAssetAdministrationShellApiSecurityTest.java @@ -208,19 +208,158 @@ public void testGetAllShellsWithDefaultClosedFilteredSpecificAssetIdsByTenantId( } @Test + @DisplayName( "Test GetShell with filtered specificAssetId by tenantId" ) public void testGetShellWithFilteredSpecificAssetIdsByTenantId() throws Exception { + // Create and save rule accessControlRuleRepository.saveAllAndFlush( List.of( - TestUtil.createAccessRule( TestUtil.PUBLIC_READABLE, + // Rule for BPN + TestUtil.createAccessRule( + // Rule for BPN + TestUtil.PUBLIC_READABLE, + // Rule for mandatory specificAssetIds Map.of( keyPrefix + "BPID", "ignoreWildcard", "manufacturerPartId", keyPrefix + "wildcardAllowed" ), + // Rule for visible specificAssetIds Set.of( "manufacturerPartId" ), Set.of( keyPrefix + "semanticId" ) ), - TestUtil.createAccessRule( jwtTokenFactory.tenantTwo().getTenantId(), + TestUtil.createAccessRule( + // Rule for BPN + jwtTokenFactory.tenantTwo().getTenantId(), + // Rule for mandatory specificAssetIds Map.of( keyPrefix + "CustomerPartId", "tenantTwoAssetIdValue", keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue" ), + // Rule for visible specificAssetIds Set.of( keyPrefix + "CustomerPartId", keyPrefix + "MaterialNumber" ), Set.of( keyPrefix + "semanticId" ) ), - TestUtil.createAccessRule( jwtTokenFactory.tenantThree().getTenantId(), + TestUtil.createAccessRule( + // Rule for BPN + jwtTokenFactory.tenantThree().getTenantId(), + // Rule for mandatory specificAssetIds Map.of( keyPrefix + "CustomerPartId2", "tenantThreeAssetIdValue" ), + // Rule for visible specificAssetIds Set.of( keyPrefix + "CustomerPartId2" ), Set.of( keyPrefix + "semanticId" ) ) ) ); - super.testGetShellWithFilteredSpecificAssetIdsByTenantId(); + + SpecificAssetId asset1 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantTwoAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + SpecificAssetId asset2 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId2", "tenantThreeAssetIdValue", + List.of( jwtTokenFactory.tenantThree().getTenantId() ) ); + SpecificAssetId asset3 = TestUtil.createSpecificAssetId( keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + // Define specificAsset with wildcard which not allowed. (Only manufacturerPartId is defined in application.yml) + SpecificAssetId asset4 = TestUtil.createSpecificAssetId( keyPrefix + "BPID", "ignoreWildcard", List.of( getExternalSubjectIdWildcardPrefix() ) ); + // Define specificAsset with wildcard which is allowed. (Only manufacturerPartId is defined in application.yml) + SpecificAssetId asset5 = TestUtil.createSpecificAssetId( "manufacturerPartId", keyPrefix + "wildcardAllowed", + List.of( getExternalSubjectIdWildcardPrefix() ) ); + + // Define all available specificAssetIds + List specificAssetIds = List.of( asset1, asset2, asset3, asset4, asset5 ); + // Define available specificAssetIds for tenantTwo + List expectedSpecificAssetIdsTenantTwo = List.of( asset1, asset3, asset5 ); + super.testGetShellWithFilteredSpecificAssetIdsByTenantId( specificAssetIds, expectedSpecificAssetIdsTenantTwo ); + } + + @Test + @DisplayName( "Test GetShell with filtered specificAssetIds with same keys where the specific mandatory Rule match over ANY rule by tenantId" ) + public void testGetShellWithFilteredSpecificAssetIdsWithSameKeysAndMandatoryRuleMatchedOverAnyByTenantId() throws Exception { + // Create and save rule + accessControlRuleRepository.saveAllAndFlush( List.of( + // Rule for BPN + TestUtil.createAccessRule( + // Rule for BPN + TestUtil.PUBLIC_READABLE, + // Rule for mandatory specificAssetIds + Map.of( keyPrefix + "BPID", "ignoreWildcard", "manufacturerPartId", keyPrefix + "wildcardAllowed" ), + // Rule for visible specificAssetIds + Set.of( "manufacturerPartId" ), Set.of( keyPrefix + "semanticId" ) ), + TestUtil.createAccessRule( + // Rule for BPN + jwtTokenFactory.tenantTwo().getTenantId(), + // Rule for mandatory specificAssetIds + Map.of( keyPrefix + "CustomerPartId", "tenantTwoAssetIdValue", keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue" ), + // Rule for visible specificAssetIds + Set.of( keyPrefix + "CustomerPartId", keyPrefix + "MaterialNumber" ), Set.of( keyPrefix + "semanticId" ) ) ) ); + + SpecificAssetId asset1 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantTwoAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + SpecificAssetId asset2 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantThreeAssetIdValue", + List.of( jwtTokenFactory.tenantThree().getTenantId() ) ); + SpecificAssetId asset3 = TestUtil.createSpecificAssetId( keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + + // Define all available specificAssetIds + List specificAssetIds = List.of( asset1, asset2, asset3 ); + // Define available specificAssetIds for tenantTwo + List expectedSpecificAssetIdsTenantTwo = List.of( asset1, asset3 ); + super.testGetShellWithFilteredSpecificAssetIdsByTenantId( specificAssetIds, expectedSpecificAssetIdsTenantTwo ); + } + + @Test + @DisplayName( "Test GetShell with filtered specificAssetIds with same keys where the ANY Rule matched by tenantId" ) + public void testGetShellWithFilteredSpecificAssetIdsWithSameKeysAndAnyRuleMatchedByTenantId() throws Exception { + // Create and save rule + accessControlRuleRepository.saveAllAndFlush( List.of( + TestUtil.createAccessRule( + // Rule for BPN + TestUtil.PUBLIC_READABLE, + // Rule for mandatory specificAssetIds + Map.of( keyPrefix + "BPID", "ignoreWildcard", "manufacturerPartId", keyPrefix + "wildcardAllowed" ), + // Rule for visible specificAssetIds + Set.of( "manufacturerPartId" ), Set.of( keyPrefix + "semanticId" ) ), + TestUtil.createAccessRule( + // Rule for BPN + jwtTokenFactory.tenantTwo().getTenantId(), + // Rule for mandatory specificAssetIds + Map.of( keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue" ), + // Rule for visible specificAssetIds + Set.of( keyPrefix + "CustomerPartId", keyPrefix + "MaterialNumber" ), Set.of( keyPrefix + "semanticId" ) ) ) ); + + SpecificAssetId asset1 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantTwoAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + SpecificAssetId asset2 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantThreeAssetIdValue", + List.of( jwtTokenFactory.tenantThree().getTenantId() ) ); + SpecificAssetId asset3 = TestUtil.createSpecificAssetId( keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + SpecificAssetId asset4 = TestUtil.createSpecificAssetId( keyPrefix + "PartInstanceId", "OwnerAssetidValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + + // Define all available specificAssetIds + List specificAssetIds = List.of( asset1, asset2, asset3, asset4 ); + // Define available specificAssetIds for tenantTwo + List expectedSpecificAssetIdsTenantTwo = List.of( asset1, asset2, asset3 ); + super.testGetShellWithFilteredSpecificAssetIdsByTenantId( specificAssetIds, expectedSpecificAssetIdsTenantTwo ); + } + + @Test + @DisplayName( "Test GetShell with filtered specificAssetIds with same keys where mandatory rule not match and disable specificAssetId by tenantId" ) + public void testGetShellWithFilteredSpecificAssetIdsWithSameKeysAndMandatoryRuleNotMatchedTenantId() throws Exception { + // Create and save rule + accessControlRuleRepository.saveAllAndFlush( List.of( + TestUtil.createAccessRule( + // Rule for BPN + TestUtil.PUBLIC_READABLE, + // Rule for mandatory specificAssetIds + Map.of( keyPrefix + "BPID", "ignoreWildcard", "manufacturerPartId", keyPrefix + "wildcardAllowed" ), + // Rule for visible specificAssetIds + Set.of( "manufacturerPartId" ), Set.of( keyPrefix + "semanticId" ) ), + TestUtil.createAccessRule( + // Rule for BPN + jwtTokenFactory.tenantTwo().getTenantId(), + // Rule for mandatory specificAssetIds + Map.of( keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue" ), + // Rule for visible specificAssetIds + Set.of( keyPrefix + "CustomerPartId", keyPrefix + "MaterialNumber" ), Set.of( keyPrefix + "semanticId" ) ) ) ); + + SpecificAssetId asset1 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantTwoAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + SpecificAssetId asset2 = TestUtil.createSpecificAssetId( keyPrefix + "CustomerPartId", "tenantThreeAssetIdValue", + List.of( jwtTokenFactory.tenantThree().getTenantId() ) ); + SpecificAssetId asset3 = TestUtil.createSpecificAssetId( keyPrefix + "MaterialNumber", "withoutTenantAssetIdValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + SpecificAssetId asset4 = TestUtil.createSpecificAssetId( keyPrefix + "PartInstanceId", "OwnerAssetidValue", + List.of( jwtTokenFactory.tenantTwo().getTenantId() ) ); + + // Define all available specificAssetIds + List specificAssetIds = List.of( asset1, asset2, asset3, asset4 ); + // Define available specificAssetIds for tenantTwo + List expectedSpecificAssetIdsTenantTwo = List.of( asset3 ); + super.testGetShellWithFilteredSpecificAssetIdsByTenantId( specificAssetIds, expectedSpecificAssetIdsTenantTwo ); } @Test diff --git a/docs/README.md b/docs/README.md index 3bd1335c..a3c62ddd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -840,7 +840,10 @@ The process is similar to the lookup shells, the filtering and access control of 2. The list of shells fetched in the previous step is filtered by applying the access control rules to them one-by-one. 3. The process is repeated until we have the desired number of *Digital Twins* or there are no more *Digital Twins* to fetch. 4. The visible properties of the visible *Digital Twins* are returned. - + 1. If the list of *specificAssetIds* in the shell has *multiple* entries with the same name, then the following rules apply: + 1. Usecase 1: A rule includes a specificAssetId (for example customerPartId=123) as *mandatorySpecificAssetId and visibleSpecificAssetId*. The Shell has multiple entries with the same name and *one of the entry* matched the value from mandatorySpecificAssetId, then only the matched entry is visible. + 2. Usecase 2: A rule includes a specificAssetId (for example customerPartId=123) as *mandatorySpecificAssetId and visibleSpecificAssetId*. The Shell has multiple entries with the same name and *none of the entries* matched the value from mandatorySpecificAssetId, then no entries with the same name is visible. + ###### Get Shell by AAS Id - `GET {{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier` To determine the visibility of a single *Digital Twin*, we can simply: diff --git a/pom.xml b/pom.xml index 742dfc8c..5e944e36 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.3 + 3.2.4 @@ -66,7 +66,7 @@ - 3.2.3 + 3.2.4 1.6.14 1.18.24 1.5.20