diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d1e8f2..ed44aea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## 0.3.15-M1 +### Added + +## fixed +- Add deprecated api `/query` for release 3.2. The Api `query` is not a part of AAS 3.0. But for release 3.2, this api will be added and marked as deprecated. This api will be removed in future releases. + ## 0.3.14-M1 ### Added diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/controller/AssetAdministrationShellApiDelegate.java b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/controller/AssetAdministrationShellApiDelegate.java index ab5ed1e1..19293732 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/controller/AssetAdministrationShellApiDelegate.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/controller/AssetAdministrationShellApiDelegate.java @@ -176,13 +176,14 @@ public ResponseEntity> postAllAssetLinksById(byte[] aasIde * Since /query is not part of AAS 3.0, so this method is not used. * Keeping it for the reason that it might come up in next version. */ + @Deprecated public ResponseEntity> postQueryAllAssetAdministrationShellIds(ShellLookup shellLookup,@RequestHeader String externalSubjectId) { List assetIds = shellLookup.getQuery().getAssetIds(); List externalIds = shellService.findExternalShellIdsByIdentifiersByAnyMatch(shellMapper.fromApiDto(assetIds),getExternalSubjectIdOrEmpty(externalSubjectId)); return new ResponseEntity<>(externalIds, HttpStatus.OK); } - private String getExternalSubjectIdOrEmpty(String externalSubjectId) { + private String getExternalSubjectIdOrEmpty(String externalSubjectId) { return (null ==externalSubjectId) ? "" : externalSubjectId; } @@ -194,6 +195,5 @@ private String getDecodedId( byte[] aasIdentifier ) { throw new IllegalArgumentException("Incorrect Base64 encoded value provided as parameter"); } } - } diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellRepository.java b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellRepository.java index 481e3579..369079da 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellRepository.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellRepository.java @@ -100,15 +100,18 @@ List findExternalShellIdsByIdentifiersByExactMatch(@Param("keyValueCombi * @param keyValueCombinations the keys values to search for as tuples * @return external shell ids for the given key value combinations */ - @Query(value = - "select distinct s.id_external from shell s where s.id in (" + - "select si.fk_shell_id from shell_identifier si " + - "where concat(si.namespace,si.identifier) in (:keyValueCombinations) " + - "and (si.external_subject_id is null or :tenantId = :owningTenantId or si.external_subject_id = :tenantId) " + - "group by si.fk_shell_id " + - ")",nativeQuery = true - ) + @Query( value = "select s.id_external from shell s where s.id in (" + + "select si.fk_shell_id from shell_identifier si " + + "where concat(si.namespace,si.identifier) in (:keyValueCombinations) " + + "and (:tenantId = :owningTenantId or si.namespace= :globalAssetId or exists (" + + "Select sider.ref_key_value from SHELL_IDENTIFIER_EXTERNAL_SUBJECT_REFERENCE_KEY sider where (sider.ref_key_value = :tenantId or (sider.ref_key_value = :publicWildcardPrefix and si.namespace in (:publicWildcardAllowedTypes) )) and sider.FK_SI_EXTERNAL_SUBJECT_REFERENCE_ID="+ + "(select sies.id from SHELL_IDENTIFIER_EXTERNAL_SUBJECT_REFERENCE sies where sies.FK_SHELL_IDENTIFIER_EXTERNAL_SUBJECT_ID=si.id)"+ + ")) group by si.fk_shell_id " + + ")",nativeQuery = true ) List findExternalShellIdsByIdentifiersByAnyMatch(@Param("keyValueCombinations") List keyValueCombinations, - @Param("tenantId") String tenantId, - @Param("owningTenantId") String owningTenantId); + @Param("tenantId") String tenantId, + @Param ("publicWildcardPrefix") String publicWildcardPrefix, + @Param ("publicWildcardAllowedTypes") List publicWildcardAllowedTypes, + @Param("owningTenantId") String owningTenantId, + @Param("globalAssetId") String globalAssetId); } diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellService.java b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellService.java index 8f1fd2b0..e46c81e1 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellService.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/service/ShellService.java @@ -234,7 +234,13 @@ private int getCursorDecoded( String cursor, List queryResult ) { public List findExternalShellIdsByIdentifiersByAnyMatch(Set shellIdentifiers,String externalSubjectId) { List keyValueCombinations=shellIdentifiers.stream().map( shellIdentifier -> shellIdentifier.getKey()+shellIdentifier.getValue()).toList(); - return shellRepository.findExternalShellIdsByIdentifiersByAnyMatch(keyValueCombinations,externalSubjectId , owningTenantId); + return shellRepository.findExternalShellIdsByIdentifiersByAnyMatch( + keyValueCombinations, + externalSubjectId, + externalSubjectIdWildcardPrefix, + externalSubjectIdWildcardAllowedTypes, + owningTenantId, + ShellIdentifier.GLOBAL_ASSET_ID_KEY); } // Not used in AAS3 diff --git a/backend/src/main/resources/static/aas-registry-openapi.yaml b/backend/src/main/resources/static/aas-registry-openapi.yaml index cf24bb15..d85642f9 100644 --- a/backend/src/main/resources/static/aas-registry-openapi.yaml +++ b/backend/src/main/resources/static/aas-registry-openapi.yaml @@ -840,6 +840,34 @@ paths: description: Asset identifier key-value-pairs deleted successfully x-semanticIds: - https://admin-shell.io/aas/API/DeleteAllAssetLinksById/1/0/RC02 + /lookup/shells/query: + post: + tags: + - Registry and Discovery Interface + summary: Returns a list of Asset Administration Shell ids based on Asset identifier key-value-pairs. + operationId: PostQueryAllAssetAdministrationShellIds + parameters: + - $ref: '#/components/parameters/ExternalSubjectIdHeader' + requestBody: + description: Asset Administration Shell Descriptor object + content: + application/json: + schema: + $ref: '#/components/schemas/ShellLookup' + required: true + responses: + "200": + description: Requested Asset Administration Shell ids + content: + application/json: + schema: + type: array + maxItems: 10000 + items: + $ref: '#/components/schemas/Identifier' + examples: + complete: + $ref: '#/components/examples/lookup-shells-by-aas-identifier-response' components: securitySchemes: bearerAuth: 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 8405a215..b0c06d85 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 @@ -573,8 +573,7 @@ public void testRbacCreateShellInBatch() throws Exception { .andExpect(status().isCreated()); } - //TODO: Test will be ignored, because the new api does not provided batch, fetch and query. This will be come later in version 0.3.1 - //@Test + @Test public void testRbacForFindShellsWithAnyMatch() throws Exception { JsonNode anyMatchLookupPayload = mapper.createObjectNode().set("query", mapper.createObjectNode() .set("assetIds", emptyArrayNode().add(specificAssetId("abc", "123"))) diff --git a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiTest.java b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiTest.java index 6547351f..361bb0f3 100644 --- a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiTest.java +++ b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/AssetAdministrationShellApiTest.java @@ -838,42 +838,102 @@ public void testCreateShellInBatchExpectSuccess() throws Exception { .andExpect( jsonPath( "$", hasSize( 5 ) ) ); } - //@Test + @Test public void testFindExternalShellIdsBySpecificAssetIdsWithAnyMatchExpectSuccess() throws Exception { // the keyPrefix ensures that this test can run against a persistent database multiple times String keyPrefix = UUID.randomUUID().toString(); - ObjectNode commonAssetId = specificAssetId( keyPrefix + "commonAssetIdKey", "commonAssetIdValue" ); - // first shell - ObjectNode firstShellPayload = createBaseIdPayload( "sampleForQuery", "idShortSampleForQuery" ); - firstShellPayload.set( "specificAssetIds", emptyArrayNode() - .add( specificAssetId( keyPrefix + "findExternalShellIdQueryKey_1", "value_1" ) ) ); - performShellCreateRequest( toJson( firstShellPayload ) ); - - // second shell - ObjectNode secondShellPayload = createBaseIdPayload( "sampleForQuery", "idShortSampleForQuery" ); - secondShellPayload.set( "specificAssetIds", emptyArrayNode() - .add( specificAssetId( keyPrefix + "findExternalShellIdQueryKey_2", "value_2" ) ) ); - performShellCreateRequest( toJson( secondShellPayload ) ); - - // query to retrieve any match - JsonNode anyMatchAueryByAssetIds = mapper.createObjectNode().set( "query", mapper.createObjectNode() + AssetAdministrationShellDescriptor shellPayload = TestUtil.createCompleteAasDescriptor(); + shellPayload.setSpecificAssetIds(null); + shellPayload.setId(UUID.randomUUID().toString()); + SpecificAssetId asset1 = TestUtil.createSpecificAssetId(keyPrefix + "findExternal_2","value_2",null); + SpecificAssetId asset2 = TestUtil.createSpecificAssetId(keyPrefix + "findExternal_2_1","value_2_1",List.of(jwtTokenFactory.tenantTwo().getTenantId())); + SpecificAssetId asset3 = TestUtil.createSpecificAssetId(keyPrefix + "findExternal_2_2","value_2_2",List.of(jwtTokenFactory.tenantThree().getTenantId())); + + shellPayload.setSpecificAssetIds(List.of(asset1,asset2,asset3)); + + performShellCreateRequest(mapper.writeValueAsString(shellPayload)); + + // query to retrieve any match (asset1 is only visible for OWNER TENANT_ONE) + JsonNode anyMatchQueryByOwnerAssetIds = mapper.createObjectNode().set( "query", mapper.createObjectNode() .set( "assetIds", emptyArrayNode() - .add( specificAssetId( keyPrefix + "findExternalShellIdQueryKey_1", "value_1" ) ) - .add( specificAssetId( keyPrefix + "findExternalShellIdQueryKey_2", "value_2" ) ) - .add( commonAssetId ) ) + .add( specificAssetId( asset1.getName(), asset1.getValue() ) ) + .add( specificAssetId( "not_available_key_in_shell","not_available_value_in_shell" ) )) ); + // Test with non Owner(TENANT_TWO) mvc.perform( MockMvcRequestBuilders .post( LOOKUP_SHELL_BASE_PATH + "/query" ) - .content( toJson( anyMatchAueryByAssetIds ) ) + .content( toJson( anyMatchQueryByOwnerAssetIds ) ) .contentType( MediaType.APPLICATION_JSON ) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantTwo().getTenantId() ) .with( jwtTokenFactory.allRoles() ) ) .andDo( MockMvcResultHandlers.print() ) .andExpect( status().isOk() ) - .andExpect( jsonPath( "$", hasSize( 2 ) ) ) - .andExpect( jsonPath( "$", containsInAnyOrder( getId( firstShellPayload ), getId( secondShellPayload ) ) ) ); + .andExpect( jsonPath( "$", hasSize( 0 ) ) ); + + // Test with owner DTR (TENANT_ONE) + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH + "/query" ) + .content( toJson( anyMatchQueryByOwnerAssetIds ) ) + .contentType( MediaType.APPLICATION_JSON ) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "$", hasSize( 1 ) ) ) + .andExpect( jsonPath( "$[0]" ,is(shellPayload.getId()) )); + + // query to retrieve any match (asset2 is only visible for OWNER TENANT_ONE and TENANT_TWO) + JsonNode anyMatchQueryByTenantTwoAssetIds = mapper.createObjectNode().set( "query", mapper.createObjectNode() + .set( "assetIds", emptyArrayNode() + .add( specificAssetId( asset2.getName(), asset2.getValue() ) ) + .add( specificAssetId( "not_available_key_in_shell","not_available_value_in_shell" ) )) + ); + + // Test with non Owner(TENANT_TWO) + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH + "/query" ) + .content( toJson( anyMatchQueryByTenantTwoAssetIds ) ) + .contentType( MediaType.APPLICATION_JSON ) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantTwo().getTenantId() ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( jsonPath( "$", hasSize( 1 ) ) ) + .andExpect( jsonPath( "$[0]" ,is(shellPayload.getId()) )); + + // Test with non Owner(TENANT_THREE) + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH + "/query" ) + .content( toJson( anyMatchQueryByOwnerAssetIds ) ) + .contentType( MediaType.APPLICATION_JSON ) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantThree().getTenantId() ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "$", hasSize( 0 ) ) ); + + // Test with owner DTR (TENANT_ONE) + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH + "/query" ) + .content( toJson( anyMatchQueryByTenantTwoAssetIds ) ) + .contentType( MediaType.APPLICATION_JSON ) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "$", hasSize( 1 ) ) ) + .andExpect( jsonPath( "$[0]" ,is(shellPayload.getId()) )); + } //@Test diff --git a/docs/README.md b/docs/README.md index 96063bf6..ca64d88a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -352,63 +352,6 @@ locally, using minikube. For further information checkout the [readme.md](https: The overall concept can be found under **2 Architecture and constraints**. -### Visibility of specificAssetIds based on tenantId/BPN -The visibility of `specificAssetIds` in the Digital Twin Registry based on the Business Partner Number (BPN) can be controlled with the attribute `externalSubjectId`. Hence, the `externalSubjectId` is identified with the BPN. - -* The BPN as attribute to a *specificAssetId* can be provided in `specificAssetIds`. This can be done with `externalSubjectId`. -* Only users, who have the same BPN in the Eclipse Dataspace Components-BPN Header are able to see content of `specificAssetIds`. -* The behavior is closed by default, *i.e.*, if no `externalSubjectId` is defined to a `specificAssetId`, the content of this particular `specificAssetId` (key, value) is only visible for the owner of the twin (also known as data provider). -* To mark a `specificAssetId` as public for every reader on a *Digital Twin*, the defined character (`"PUBLIC_READABLE"`) needs to be added in the `externalSubjectId`. -* The communication between consumer and provider is via EDC. Before the provider EDC sends the request to the DTR, the property Edc-Bpn with the BPN of the consumer will be set by the provider EDC. -* The specificAssetIds of Digital Twins you created will always be shown to you. - -Detailed example: -``` -// Given specificAssetIds: - "specificAssetIds":[ - { - "name":"partInstanceId", - "value":"24975539203421" - }, - { - "name":"customerPartId", - "value":"231982", - "externalSubjectId":{ - "type":"ExternalReference", - "keys":[ - { - "type":"GlobalReference", - "value":"BPN_COMPANY_001" - } - ] - } - }, - { - "name":"manufacturerPartId", - "value":"123829238", - "externalSubjectId":{ - "type":"ExternalReference", - "keys":[ - { - "type":"GlobalReference", - "value":"PUBLIC_READABLE" - } - ] - } - } - ] -``` -This example is a *Digital Twin* with three different `specificAssetIds` as descriptors. -* `partInstanceID` is only visible for the owner of the twin, since no `externalSubjectId` is defined. -* `customerPartId` is only visible for the owner of the twin and an (external) reader via EDC, who has the bpn-value "BPN_COMPANY_001" in the header of the EDC -* `manufacturerPartId` is visible for everyone, because the `externalSubjectId` has the wildcard value `"PUBLIC_READABLE"` included. - -For example, if an (external) reader via EDC requests the here shown *Digital Twin* and the edc-bpn header includes the bpn-value "BPN_COMPANY_001", the list of `specificAssetIds` contains two entries, namely: -* `customerPartId`, because its `externalSubjectId` matches to the incoming bpn-value "BPN_COMPANY_001" -* `manufacturerPartId`, because this `specificAssetId` has the `externalSubjectId = "PUBLIC_READABLE"` and therefore is public for everyone - -In this example, the `specificAssetId` `"name": "partInstanceId"` is filtered out, because it is only visible for the owner of the *Digital Twin*. - ### Asset Administration Shell specification The Digital Twin Registry has implemented Asset Administration Shell specification in version 3.0. The corresponding openapi file can be found here: "backend/src/main/resources/static/aas-registry-openapi.yaml" @@ -484,10 +427,12 @@ The AAS Registry can be accessed on behalf of a user. The token has to be obtain *Support contact* tractusx-dev@eclipse.org ### Access control to Digital Twins Based on the BPN (Business Partner Number)/ TenantId -The visibility of `specificAssetIds` in the Digital Twin Registry based on the Business Partner Number (BPN) can be controlled with the attribute `externalSubjectId`. Hence, the `externalSubjectId` is identified with the BPN. +The visibility of `specificAssetIds` in the Digital Twin Registry based on the Business Partner Number (BPN) (Which is send via header Edc-Bpn) can be controlled with the attribute `externalSubjectId`. Hence, the `externalSubjectId` is identified with the BPN. +The communication between consumer and provider is via EDC. Before the provider EDC sends the request to the DTR, the property Edc-Bpn with the BPN of the consumer will be set by the provider EDC. + * The BPN as attribute to a *specificAssetId* can be optionally provided in `specificAssetIds`. This can be done with `externalSubjectId`. -* Only those users, where `externalSubjectId` matches the Eclipse Dataspace Components-Header (*i.e.* BPN) are able to discover and read exactly this content of `specificAssetIds`. +* Only those users, where `externalSubjectId` matches the Eclipse Dataspace Components-Header (Edc-Bpn -> *i.e.* BPN) are able to discover and read exactly this content of `specificAssetIds`. * The behavior is **closed by default**, *i.e.*, if no `externalSubjectId` is defined to a `specificAssetId`, the content of this particular `specificAssetId` (key, value) is only visible for the owner of the *Digital Twin* (also known as data provider). * To mark a `specificAssetId` as public for every reader on a *Digital Twin*, the defined characters (`"PUBLIC_READABLE"`) need to be added in the `externalSubjectId`. * *Cave: The publisher of `specificAssetIds` needs to consider antitrust law. This use of `"PUBLIC_READABLE"` is only allowed for the *specificAssetId* `"assetLifecyclePhase"` (only `"value":"AsBuilt"`, and `"value":"AsPlanned"` allowed; see [Eclipse Tractus-X - Traceability Kit](https://eclipse-tractusx.github.io/docs-kits/next/kits/Traceability%20Kit/Software%20Development%20View/Specification%20Traceability%20Kit)), and `"manufacturerPartId"` (which is technically enforced by the Digital Twin Registry) if its content describes material numbers of products and those products are or were in serial production for the open market. If its content describes material numbers of products in state of, *e.g.*, pre-production, being planned for production, being unsold, the use of `"PUBLIC_READABLE"` is not allowed and use of dedicated read access via `externalSubjectId` is to be used instead. `"manufacturerPartId"` is not allowed to be used for different content than the one described here.*