From 0410b8ac0561d37971ce5f1e85d103a7f0f7d0b9 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Wed, 21 Feb 2024 14:24:02 +0100 Subject: [PATCH 01/26] Remove ordered from sequence (liquibase) because it is not support in postgres. --- backend/postgres/docker-compose.yml | 2 +- backend/src/main/resources/db/changelog/db.changelog-v4.yaml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/postgres/docker-compose.yml b/backend/postgres/docker-compose.yml index ba553874..d05ebad0 100644 --- a/backend/postgres/docker-compose.yml +++ b/backend/postgres/docker-compose.yml @@ -22,7 +22,7 @@ version: "3.9" services: postgres: - image: postgres:13.6-alpine + image: bitnami/postgresql:15.4.0-debian-11-r45 container_name: postgres ports: - "5432:5432" diff --git a/backend/src/main/resources/db/changelog/db.changelog-v4.yaml b/backend/src/main/resources/db/changelog/db.changelog-v4.yaml index 979e3404..29a7c1cf 100644 --- a/backend/src/main/resources/db/changelog/db.changelog-v4.yaml +++ b/backend/src/main/resources/db/changelog/db.changelog-v4.yaml @@ -29,7 +29,6 @@ databaseChangeLog: dataType: int incrementBy: 50 minValue: 1 - ordered: true sequenceName: ACCESS_RULE_SEQ startValue: 1 - createTable: From 21360bde9fd99b650d1b2c3c118c715fc83c3605 Mon Sep 17 00:00:00 2001 From: Istvan Zoltan Nagy Date: Wed, 21 Feb 2024 17:04:58 +0100 Subject: [PATCH 02/26] Implementation of CRUD API for Access management APIs - Fixes issues which were causing failures when using with PostgreSQL --- .../tractusx/semantics/accesscontrol/sql/model/AccessRule.java | 2 +- .../tractusx/semantics/registry/service/ShellService.java | 1 + backend/src/main/resources/db/changelog/db.changelog-v4.yaml | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/model/AccessRule.java b/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/model/AccessRule.java index 6a1fba54..d08bb374 100644 --- a/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/model/AccessRule.java +++ b/access-control-service-sql-impl/src/main/java/org/eclipse/tractusx/semantics/accesscontrol/sql/model/AccessRule.java @@ -56,7 +56,7 @@ public enum PolicyType { @Null( groups = OnCreate.class ) @NotNull( groups = OnUpdate.class ) @Id - @GeneratedValue( strategy = GenerationType.AUTO ) + @GeneratedValue( strategy = GenerationType.SEQUENCE, generator = "ACCESS_RULE_SEQ") @Column( name = "ID", nullable = false, updatable = false ) private Long id; 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 32f8bccc..b1b0ff8d 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 @@ -494,6 +494,7 @@ public List saveBatch( List shells ) { } ).collect( Collectors.toList() ); } + @Transactional(readOnly = true) public boolean hasAccessToShellWithVisibleSubmodelEndpoint( String endpointAddress, String externalSubjectId ) { List shells = shellRepository.findAllBySubmodelEndpointAddress( endpointAddress ); List filtered = shellAccessHandler.filterListOfShellProperties( shells, externalSubjectId ); diff --git a/backend/src/main/resources/db/changelog/db.changelog-v4.yaml b/backend/src/main/resources/db/changelog/db.changelog-v4.yaml index 29a7c1cf..a7fc9b0e 100644 --- a/backend/src/main/resources/db/changelog/db.changelog-v4.yaml +++ b/backend/src/main/resources/db/changelog/db.changelog-v4.yaml @@ -43,7 +43,6 @@ databaseChangeLog: nullable: false validateNullable: true autoIncrement: true - generationType: ALWAYS startWith: 1 incrementBy: 50 - column: From 8a4aeb6141cb6b16af72bf19b43a62a9de504683 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Thu, 22 Feb 2024 10:00:49 +0100 Subject: [PATCH 03/26] Add postman collection for access rule management. Adjust default wildcard types --- charts/registry/values.yaml | 2 +- docs/development/postman/README.md | 4 + ...s-rule-management-api-v1.0-collection.json | 168 ++++++++++++++++++ 3 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json diff --git a/charts/registry/values.yaml b/charts/registry/values.yaml index 19e68a87..8e325841 100644 --- a/charts/registry/values.yaml +++ b/charts/registry/values.yaml @@ -48,7 +48,7 @@ registry: idpClientId: default-client tenantId: default-tenant externalSubjectIdWildcardPrefix: PUBLIC_READABLE - externalSubjectIdWildcardAllowedTypes: manufacturerPartId,assetLifecyclePhase + externalSubjectIdWildcardAllowedTypes: manufacturerPartId,digitalTwinType useGranularAccessControl: "false" service: port: 8080 diff --git a/docs/development/postman/README.md b/docs/development/postman/README.md index 42b6da4a..4ba50584 100644 --- a/docs/development/postman/README.md +++ b/docs/development/postman/README.md @@ -1,8 +1,12 @@ # DTR Postman Collection ## Postman +### DTR Postman Collection: The postman collection can be used to call all provided APIs of the DTR. +### Access rule management Postman Collection: +The postman collection can be used to call all provided APIs of the access rule management. + ### Setup Postman To install postman, download the newest version: diff --git a/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json b/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json new file mode 100644 index 00000000..6edbf46f --- /dev/null +++ b/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json @@ -0,0 +1,168 @@ +{ + "info": { + "_postman_id": "47f1eefc-bd84-4c6b-9c30-2ff696b1dde0", + "name": "tractusx-dtr-access-rule-management-api-v1.0 Copy", + "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" + }, + "item": [ + { + "name": "get Rules", + "request": { + "method": "GET", + "header": [], + "url": "{{baseUrl}}/api/v3.0/access-controls/rules" + }, + "response": [] + }, + { + "name": "create Rule", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"validFrom\": \"2020-01-02T03:04:05Z\",\n \"validTo\": \"4999-01-02T03:04:05Z\",\n \"description\": \"ACME policy within set validity period\",\n \"policyType\": \"AAS\",\n \"policy\": {\n \"accessRules\": [\n {\n \"attribute\": \"bpn\",\n \"operator\": \"eq\",\n \"value\": \"BPNL00000000000A\"\n },\n {\n \"attribute\": \"mandatorySpecificAssetIds\",\n \"operator\": \"includes\",\n \"values\": [\n {\n \"attribute\": \"manufacturerPartId\",\n \"operator\": \"eq\",\n \"value\": \"99991\"\n },\n {\n \"attribute\": \"customerPartId\",\n \"operator\": \"eq\",\n \"value\": \"ACME001\"\n }\n ]\n },\n {\n \"attribute\": \"visibleSpecificAssetIdNames\",\n \"operator\": \"includes\",\n \"values\": [\n {\n \"attribute\": \"name\",\n \"operator\": \"eq\",\n \"value\": \"manufacturerPartId\"\n },\n {\n \"attribute\": \"name\",\n \"operator\": \"eq\",\n \"value\": \"customerPartId\"\n },\n {\n \"attribute\": \"name\",\n \"operator\": \"eq\",\n \"value\": \"partInstanceId\"\n }\n ]\n },\n {\n \"attribute\": \"visibleSemanticIds\",\n \"operator\": \"includes\",\n \"values\": [\n {\n \"attribute\": \"modelUrn\",\n \"operator\": \"eq\",\n \"value\": \"urn:samm:net.catenax.vehicle.submodel_external1:1.0.0#Pcf\"\n }\n ]\n }\n ]\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": "{{baseUrl}}/api/v3.0/access-controls/rules" + }, + "response": [] + }, + { + "name": "get Rule by ruleId", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/api/v3.0/access-controls/rules/:ruleId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "access-controls", + "rules", + ":ruleId" + ], + "variable": [ + { + "key": "ruleId", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "update Rule by ruleId", + "request": { + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"id\": 1,\n \"tid\": \"00000000-1111-2222-3333-444444444444\",\n \"policyType\": \"AAS\",\n \"policy\": {\n \"accessRules\": [\n {\n \"attribute\": \"bpn\",\n \"operator\": \"eq\",\n \"value\": \"BPNL00000000000A\"\n },\n {\n \"attribute\": \"mandatorySpecificAssetIds\",\n \"operator\": \"includes\",\n \"values\": [\n {\n \"attribute\": \"manufacturerPartId\",\n \"operator\": \"eq\",\n \"value\": \"99991\"\n },\n {\n \"attribute\": \"customerPartId\",\n \"operator\": \"eq\",\n \"value\": \"ACME001\"\n }\n ]\n },\n {\n \"attribute\": \"visibleSpecificAssetIdNames\",\n \"operator\": \"includes\",\n \"values\": [\n {\n \"attribute\": \"name\",\n \"operator\": \"eq\",\n \"value\": \"manufacturerPartId\"\n },\n {\n \"attribute\": \"name\",\n \"operator\": \"eq\",\n \"value\": \"customerPartId\"\n },\n {\n \"attribute\": \"name\",\n \"operator\": \"eq\",\n \"value\": \"partInstanceId\"\n }\n ]\n },\n {\n \"attribute\": \"visibleSemanticIds\",\n \"operator\": \"includes\",\n \"values\": [\n {\n \"attribute\": \"modelUrn\",\n \"operator\": \"eq\",\n \"value\": \"Traceabilityv1.1.0\"\n },\n {\n \"attribute\": \"modelUrn\",\n \"operator\": \"eq\",\n \"value\": \"ProductCarbonFootprintv1.1.0\"\n }\n ]\n }\n ]\n },\n \"description\": \"Access rule description.\",\n \"validFrom\": \"2024-01-02T03:04:05Z\",\n \"validTo\": \"2024-06-07T08:09:10Z\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/v3.0/access-controls/rules/:ruleId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "access-controls", + "rules", + ":ruleId" + ], + "variable": [ + { + "key": "ruleId", + "value": "" + } + ] + } + }, + "response": [] + }, + { + "name": "delete rule by ruleId", + "request": { + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/api/v3.0/access-controls/rules/:ruleId", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "access-controls", + "rules", + ":ruleId" + ], + "variable": [ + { + "key": "ruleId", + "value": "" + } + ] + } + }, + "response": [] + } + ], + "auth": { + "type": "oauth2", + "oauth2": { + "clientSecret": "", + "clientId": "", + "accessTokenUrl": "", + "challengeAlgorithm": "S256", + "scope": "openid", + "redirect_uri": "", + "grant_type": "client_credentials", + "authUrl": "", + "addTokenTo": "header", + "client_authentication": "header" + } + }, + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "default" + } + ] +} \ No newline at end of file From ada965c8755f51307ce5f51970cfd96276070baa Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Thu, 22 Feb 2024 10:42:07 +0100 Subject: [PATCH 04/26] increase helm version --- charts/registry/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml index 2b0980a0..5a9949a4 100644 --- a/charts/registry/Chart.yaml +++ b/charts/registry/Chart.yaml @@ -26,7 +26,7 @@ sources: - https://github.com/eclipse-tractusx/sldt-digital-twin-registry type: application -version: 0.4.6 +version: 0.4.7 appVersion: 0.3.23 dependencies: From 1d8ef13fe9d1349125d4d8b34e9276c9372da087 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Thu, 22 Feb 2024 13:40:02 +0100 Subject: [PATCH 05/26] adjust name --- ...tractusx-dtr-access-rule-management-api-v1.0-collection.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json b/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json index 6edbf46f..f2f77b2b 100644 --- a/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json +++ b/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json @@ -1,7 +1,7 @@ { "info": { "_postman_id": "47f1eefc-bd84-4c6b-9c30-2ff696b1dde0", - "name": "tractusx-dtr-access-rule-management-api-v1.0 Copy", + "name": "tractusx-dtr-access-rule-management-api-v1.0", "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" }, "item": [ From 9664d2c34e1ad0d8123232176d0826aae9788b11 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Fri, 23 Feb 2024 14:17:22 +0100 Subject: [PATCH 06/26] Move docker notice file to backend folder --- .github/workflows/publish-image-registry.yml | 2 +- README.md | 15 ++++++++++++++- DOCKER_NOTICE.md => backend/DOCKER_NOTICE.md | 0 3 files changed, 15 insertions(+), 2 deletions(-) rename DOCKER_NOTICE.md => backend/DOCKER_NOTICE.md (100%) diff --git a/.github/workflows/publish-image-registry.yml b/.github/workflows/publish-image-registry.yml index 067df86a..11ece8d0 100644 --- a/.github/workflows/publish-image-registry.yml +++ b/.github/workflows/publish-image-registry.yml @@ -106,4 +106,4 @@ jobs: username: ${{ secrets.DOCKER_HUB_USER }} password: ${{ secrets.DOCKER_HUB_TOKEN }} repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} - readme-filepath: ./DOCKER_NOTICE.md + readme-filepath: ./backend/DOCKER_NOTICE.md diff --git a/README.md b/README.md index 796aaf1d..513bab98 100644 --- a/README.md +++ b/README.md @@ -35,4 +35,17 @@ Run `docker build -t registry .` In case you want to publish your image into a remote container registry, apply the tag accordingly and `docker push` the image. ## Install Instructions -For detailed install instructions please refer to our [INSTALL.md](https://github.com/eclipse-tractusx/sldt-digital-twin-registry/blob/main/INSTALL.md) \ No newline at end of file +For detailed install instructions please refer to our [INSTALL.md](https://github.com/eclipse-tractusx/sldt-digital-twin-registry/blob/main/INSTALL.md) + +## License +[Apache License 2.0](https://github.com/eclipse-tractusx/sldt-digital-twin-registry/blob/main/LICENSE) + +## Notice for Docker image +Below you can find the information regarding Docker Notice for this application. + +- [digital-twin-registry Docker Notice](https://github.com/eclipse-tractusx/sldt-digital-twin-registry/blob/main/backend/DOCKER_NOTICE.md) + +## Contact +Contact the Eclipse Tractus-X developers via the developer mailing list. + +* https://accounts.eclipse.org/mailing-list/tractusx-dev \ No newline at end of file diff --git a/DOCKER_NOTICE.md b/backend/DOCKER_NOTICE.md similarity index 100% rename from DOCKER_NOTICE.md rename to backend/DOCKER_NOTICE.md From 8b2c919530b0e6f2633fa45b99807fce22c875b5 Mon Sep 17 00:00:00 2001 From: Istvan Zoltan Nagy Date: Wed, 21 Feb 2024 13:04:07 +0100 Subject: [PATCH 07/26] Shell lookups must return shells ordered in a deterministic way - Modifies repository and service implementation to fetch AAS Ids ordered by the createdDate (and then the AAS Id as secondary criteria) - Fixes a bug in list all API - Makes fetch size configurable - Adds new configuration property to charts - Adds new tests to cover the changes better --- CHANGELOG.md | 6 + .../semantics/RegistryProperties.java | 4 + .../repository/ShellIdentifierRepository.java | 54 +++-- .../registry/repository/ShellRepository.java | 4 + .../registry/service/ShellService.java | 142 +++++++----- backend/src/main/resources/application.yml | 1 + .../service/GranularShellServiceTest.java | 112 +++++++++ .../service/LegacyShellServiceTest.java | 214 ++++++++++++++++++ charts/registry/Chart.yaml | 2 +- charts/registry/README.md | 116 +++++----- .../templates/registry/registry-secret.yaml | 1 + charts/registry/values.yaml | 1 + 12 files changed, 533 insertions(+), 124 deletions(-) create mode 100644 backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/GranularShellServiceTest.java create mode 100644 backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/LegacyShellServiceTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 45c3c1cd..35b2361f 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.4.0 +### Added +- Granular access control + +## fixed + ## 0.3.23 ### Added diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/RegistryProperties.java b/backend/src/main/java/org/eclipse/tractusx/semantics/RegistryProperties.java index fb092813..4218ef66 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/RegistryProperties.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/RegistryProperties.java @@ -53,6 +53,10 @@ public class RegistryProperties { * This flag turns on the granular access control logic if set to true. */ private Boolean useGranularAccessControl; + /** + * Configures the number of records fetched in one batch when a page of shells is requested. + */ + private Integer granularAccessControlFetchSize; /** * Properties for Identity Management system diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellIdentifierRepository.java b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellIdentifierRepository.java index 47d0908b..fee8b96a 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellIdentifierRepository.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/repository/ShellIdentifierRepository.java @@ -20,6 +20,7 @@ package org.eclipse.tractusx.semantics.registry.repository; +import java.time.Instant; import java.util.List; import java.util.Set; import java.util.UUID; @@ -27,6 +28,7 @@ import org.eclipse.tractusx.semantics.registry.model.Shell; import org.eclipse.tractusx.semantics.registry.model.ShellIdentifier; import org.eclipse.tractusx.semantics.registry.model.projection.ShellIdentifierMinimal; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; @@ -41,19 +43,36 @@ public interface ShellIdentifierRepository extends JpaRepository findByShellId( Shell shellId ); @Query( value = """ - SELECT NEW org.eclipse.tractusx.semantics.registry.model.projection.ShellIdentifierMinimal(sid.shellId.idExternal, sid.key, sid.value) + SELECT s.id + FROM ShellIdentifier sid + JOIN sid.shellId s + WHERE + CONCAT( sid.key, sid.value ) IN ( :keyValueCombinations ) + AND ( + s.createdDate > :cutoffDate + OR ( s.createdDate = :cutoffDate AND s.idExternal > :cursorValue ) + ) + GROUP BY s.id, s.createdDate, s.idExternal + HAVING COUNT(*) = :keyValueCombinationsSize + ORDER BY s.createdDate ASC, s.idExternal ASC + """ ) + List findAPageOfShellIdsBySpecificAssetIds( + List keyValueCombinations, int keyValueCombinationsSize, Instant cutoffDate, String cursorValue, Pageable pageable ); + + @Query( value = """ + SELECT NEW org.eclipse.tractusx.semantics.registry.model.projection.ShellIdentifierMinimal(s.idExternal, sid.key, sid.value) FROM ShellIdentifier sid + JOIN sid.shellId s WHERE - sid.shellId.id IN ( - SELECT filtersid.shellId.id - FROM ShellIdentifier filtersid - WHERE - CONCAT(filtersid.key, filtersid.value) IN (:keyValueCombinations) - GROUP BY filtersid.shellId.id - HAVING COUNT(*) = :keyValueCombinationsSize - ) + s.id IN ( :shellIds ) + AND ( + s.createdDate > :cutoffDate + OR ( s.createdDate = :cutoffDate AND s.idExternal > :cursorValue ) + ) + ORDER BY s.createdDate ASC, s.idExternal ASC """ ) - List findMinimalShellIdsBySpecificAssetIds( List keyValueCombinations, int keyValueCombinationsSize ); + List findMinimalShellIdsByShellIds( + List shellIds, Instant cutoffDate, String cursorValue ); /** * Returns external shell ids for the given keyValueCombinations. @@ -74,7 +93,11 @@ HAVING COUNT(*) = :keyValueCombinationsSize FROM shell s JOIN shell_identifier si ON s.id = si.fk_shell_id WHERE - CONCAT(si.namespace, si.identifier) IN (:keyValueCombinations) + CONCAT( si.namespace, si.identifier ) IN ( :keyValueCombinations ) + AND ( + s.created_date > :cutoffDate + OR ( s.created_date = :cutoffDate AND s.id_external > :cursorValue ) + ) AND ( :tenantId = :owningTenantId OR si.namespace = :globalAssetId @@ -90,8 +113,10 @@ OR EXISTS ( AND sies.FK_SHELL_IDENTIFIER_EXTERNAL_SUBJECT_ID = si.id ) ) - GROUP BY s.id_external + GROUP BY s.id_external, s.created_date HAVING COUNT(*) = :keyValueCombinationsSize + ORDER BY s.created_date, s.id_external + LIMIT :pageSize """, nativeQuery = true ) List findExternalShellIdsByIdentifiersByExactMatch( @Param( "keyValueCombinations" ) List keyValueCombinations, @Param( "keyValueCombinationsSize" ) int keyValueCombinationsSize, @@ -99,5 +124,8 @@ List findExternalShellIdsByIdentifiersByExactMatch( @Param( "keyValueCom @Param( "publicWildcardPrefix" ) String publicWildcardPrefix, @Param( "publicWildcardAllowedTypes" ) List publicWildcardAllowedTypes, @Param( "owningTenantId" ) String owningTenantId, - @Param( "globalAssetId" ) String globalAssetId ); + @Param( "globalAssetId" ) String globalAssetId, + @Param( "cutoffDate" ) Instant cutoffDate, + @Param( "cursorValue" ) String cursorValue, + @Param( "pageSize" ) int pageSize); } 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 33d4d7e2..9bf5202f 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 @@ -20,6 +20,7 @@ package org.eclipse.tractusx.semantics.registry.repository; +import java.time.Instant; import java.util.List; import java.util.Optional; import java.util.Set; @@ -142,4 +143,7 @@ s.id IN ( ) """) List findAllBySubmodelEndpointAddress( String endpointAddress ); + + @Query("SELECT s.createdDate FROM Shell s WHERE s.idExternal = :idExternal") + Optional getCreatedDateByIdExternal( String idExternal ); } 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 b1b0ff8d..4a7a86df 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 @@ -22,6 +22,9 @@ import static org.springframework.data.domain.PageRequest.ofSize; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; import java.util.ArrayList; import java.util.Base64; import java.util.Collection; @@ -56,6 +59,7 @@ import org.eclipse.tractusx.semantics.registry.utils.ShellSpecification; import org.springframework.dao.DuplicateKeyException; import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.jpa.domain.Specification; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -73,7 +77,11 @@ public class ShellService { public static final String DUPLICATE_SUBMODEL_ID_SHORT_EXCEPTION = "An AssetAdministration Submodel for the given IdShort does already exists."; private static final String SORT_FIELD_NAME_SHELL = "createdDate"; private static final String SORT_FIELD_NAME_SUBMODEL = "id"; + private static final String DEFAULT_EXTERNAL_ID = "00000000-0000-0000-0000-000000000000"; + private static final Instant MINIMUM_SQL_DATETIME = OffsetDateTime + .of( 1800, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC ).toInstant(); private static final int MAXIMUM_RECORDS = 1000; + private static final int DEFAULT_FETCH_SIZE = 500; private final ShellRepository shellRepository; private final ShellIdentifierRepository shellIdentifierRepository; @@ -82,6 +90,7 @@ public class ShellService { private final String owningTenantId; private final String externalSubjectIdWildcardPrefix; private final List externalSubjectIdWildcardAllowedTypes; + private final int granularAccessControlFetchSize; public ShellService( ShellRepository shellRepository, ShellIdentifierRepository shellIdentifierRepository, @@ -95,6 +104,7 @@ public ShellService( ShellRepository shellRepository, this.owningTenantId = registryProperties.getIdm().getOwningTenantId(); this.externalSubjectIdWildcardPrefix = registryProperties.getExternalSubjectIdWildcardPrefix(); this.externalSubjectIdWildcardAllowedTypes = registryProperties.getExternalSubjectIdWildcardAllowedTypes(); + this.granularAccessControlFetchSize = Optional.ofNullable( registryProperties.getGranularAccessControlFetchSize() ).orElse( DEFAULT_FETCH_SIZE ); } @Transactional @@ -199,16 +209,19 @@ public ShellCollectionDto findAllShells( Integer pageSize, String cursorVal, Str ShellCursor cursor = new ShellCursor( pageSize, cursorVal ); var specification = shellAccessHandler.shellFilterSpecification( SORT_FIELD_NAME_SHELL, cursor, externalSubjectId ); final var foundList = new ArrayList(); - boolean hasNext = true; //fetch 1 more item to make sure there is a visible item for the next page - int fetchSize = pageSize + 1; - while ( foundList.size() < fetchSize && hasNext ) { - Page currentPage = shellRepository.findAll( specification, ofSize( fetchSize ) ); + while ( foundList.size() < pageSize + 1 ) { + Page currentPage = shellRepository.findAll( specification, ofSize( granularAccessControlFetchSize ) ); List shells = shellAccessHandler.filterListOfShellProperties( currentPage.stream().toList(), externalSubjectId ); shells.stream() - .limit( (long) fetchSize - foundList.size() ) + .limit( (long) pageSize + 1 - foundList.size() ) .forEach( foundList::add ); - hasNext = currentPage.hasNext(); + if ( !currentPage.hasNext() ) { + break; + } + ShellCursor shellCursor = new ShellCursor( pageSize, + cursor.getEncodedCursorShell( lastItemOf( currentPage.getContent() ).getCreatedDate(), currentPage.hasNext() ) ); + specification = shellAccessHandler.shellFilterSpecification( SORT_FIELD_NAME_SHELL, shellCursor, externalSubjectId ); } String nextCursor = null; @@ -261,35 +274,17 @@ public GetAllAssetAdministrationShellIdsByAssetLink200Response findExternalShell Integer pageSize, String cursor, String externalSubjectId ) { pageSize = getPageSize( pageSize ); + final String cursorValue = getCursorDecoded( cursor ).orElse( DEFAULT_EXTERNAL_ID ); try { - List keyValueCombinations = shellIdentifiers.stream().map( shellIdentifier -> shellIdentifier.getKey() + shellIdentifier.getValue() ).toList(); - - //TODO: if we can define a sorting order, we should consider doing it on the database side - final List assetIdList = new ArrayList<>(); - String nextCursor; + final List visibleAssetIds; if ( shellAccessHandler.supportsGranularAccessControl() ) { - List queryResults = shellIdentifierRepository - .findMinimalShellIdsBySpecificAssetIds( keyValueCombinations, keyValueCombinations.size() ); - Set userQuery = shellIdentifiers.stream() - .map( id -> new SpecificAssetId( id.getKey(), id.getValue() ) ) - .collect( Collectors.toSet() ); - List allVisible = shellAccessHandler.filterToVisibleShellIdsForLookup( userQuery, queryResults, externalSubjectId ); - allVisible.stream() - .skip( getCursorDecoded( cursor, allVisible ) ) - .limit( pageSize ) - .forEach( assetIdList::add ); - nextCursor = getCursorEncoded( allVisible, assetIdList ); + visibleAssetIds = fetchAPageOfAasIdsUsingGranularAccessControl( shellIdentifiers, externalSubjectId, cursorValue, pageSize ); } else { - List queryResult = shellIdentifierRepository.findExternalShellIdsByIdentifiersByExactMatch( keyValueCombinations, - keyValueCombinations.size(), externalSubjectId, externalSubjectIdWildcardPrefix, externalSubjectIdWildcardAllowedTypes, owningTenantId, - ShellIdentifier.GLOBAL_ASSET_ID_KEY ); - pageSize = getPageSize( pageSize ); - - int startIndex = getCursorDecoded( cursor, queryResult ); - queryResult.stream().skip( startIndex ).limit( pageSize ).forEach( assetIdList::add ); - - nextCursor = getCursorEncoded( queryResult, assetIdList ); + visibleAssetIds = fetchAPageOfAasIdsUsingLegacyAccessControl( shellIdentifiers, externalSubjectId, cursorValue, pageSize ); } + + final var assetIdList = visibleAssetIds.stream().limit( pageSize ).toList(); + final String nextCursor = getCursorEncoded( visibleAssetIds, assetIdList ); final var response = new GetAllAssetAdministrationShellIdsByAssetLink200Response(); response.setResult( assetIdList ); response.setPagingMetadata( new PagedResultPagingMetadata().cursor( nextCursor ) ); @@ -301,32 +296,50 @@ public GetAllAssetAdministrationShellIdsByAssetLink200Response findExternalShell } } - private String getCursorEncoded( List queryResult, List assetIdList ) { - if ( !queryResult.isEmpty() ) { - if ( !assetIdList.get( assetIdList.size() - 1 ).equals( queryResult.get( queryResult.size() - 1 ) ) ) { - String lastEle = assetIdList.get( assetIdList.size() - 1 ); - return Base64.getEncoder().encodeToString( lastEle.getBytes() ); + private List fetchAPageOfAasIdsUsingLegacyAccessControl( + Set shellIdentifiers, String externalSubjectId, String cursorValue, int pageSize ) { + final var fetchSize = pageSize + 1; + final Instant cutoffDate = shellRepository.getCreatedDateByIdExternal( cursorValue ) + .orElse( MINIMUM_SQL_DATETIME ); + List keyValueCombinations = toKeyValueCombinations( shellIdentifiers ); + return shellIdentifierRepository.findExternalShellIdsByIdentifiersByExactMatch( keyValueCombinations, + keyValueCombinations.size(), externalSubjectId, externalSubjectIdWildcardPrefix, externalSubjectIdWildcardAllowedTypes, owningTenantId, + ShellIdentifier.GLOBAL_ASSET_ID_KEY, cutoffDate, cursorValue, fetchSize ); + } + + private List fetchAPageOfAasIdsUsingGranularAccessControl( + Set shellIdentifiers, String externalSubjectId, String cursorValue, int pageSize ) + throws DenyAccessException { + Set userQuery = shellIdentifiers.stream() + .map( id -> new SpecificAssetId( id.getKey(), id.getValue() ) ) + .collect( Collectors.toSet() ); + List keyValueCombinations = toKeyValueCombinations( shellIdentifiers ); + final var fetchSize = granularAccessControlFetchSize; + + String currentCursorValue = cursorValue; + final List visibleAssetIds = new ArrayList<>(); + while ( visibleAssetIds.size() < pageSize + 1 ) { + final Instant currentCutoffDate = shellRepository.getCreatedDateByIdExternal( currentCursorValue ) + .orElse( MINIMUM_SQL_DATETIME ); + List shellIds = shellIdentifierRepository.findAPageOfShellIdsBySpecificAssetIds( + keyValueCombinations, keyValueCombinations.size(), currentCutoffDate, currentCursorValue, PageRequest.ofSize( fetchSize ) ); + if ( shellIds.isEmpty() ) { + break; } - } - return null; - } + List queryResults = shellIdentifierRepository + .findMinimalShellIdsByShellIds( shellIds, currentCutoffDate, currentCursorValue ); - private String getCursorDecoded( String cursor ) { - return Optional.ofNullable( cursor ) - .map( Base64.getDecoder()::decode ) - .map( String::new ) - .orElse( null ); - } - - private int getCursorDecoded( String cursor, List queryResult ) { - return Optional.ofNullable( getCursorDecoded( cursor ) ) - .map( decodedValue -> queryResult.indexOf( decodedValue ) + 1 ) - .orElse( 0 ); + shellAccessHandler.filterToVisibleShellIdsForLookup( userQuery, queryResults, externalSubjectId ).stream() + .limit( (long) fetchSize - visibleAssetIds.size() ) + .forEach( visibleAssetIds::add ); + currentCursorValue = lastItemOf( queryResults ).shellId(); + } + return visibleAssetIds; } @Transactional( readOnly = true ) public List findExternalShellIdsByIdentifiersByAnyMatch( Set shellIdentifiers, String externalSubjectId ) { - List keyValueCombinations = shellIdentifiers.stream().map( shellIdentifier -> shellIdentifier.getKey() + shellIdentifier.getValue() ).toList(); + List keyValueCombinations = toKeyValueCombinations( shellIdentifiers ); return shellRepository.findExternalShellIdsByIdentifiersByAnyMatch( keyValueCombinations, @@ -386,7 +399,7 @@ public Set save( String externalShellId, Set s return ImmutableSet.copyOf( shellIdentifierRepository.saveAll( identifiersToUpdate ) ); } - private static void mapShellIdentifier( Stream identifiersToUpdate ) { + private void mapShellIdentifier( Stream identifiersToUpdate ) { identifiersToUpdate.filter( identifiers -> !identifiers.getKey().equalsIgnoreCase( "globalAssetId" ) ).forEach( identifier -> { if ( identifier.getSemanticId() != null ) { @@ -494,7 +507,7 @@ public List saveBatch( List shells ) { } ).collect( Collectors.toList() ); } - @Transactional(readOnly = true) + @Transactional( readOnly = true ) public boolean hasAccessToShellWithVisibleSubmodelEndpoint( String endpointAddress, String externalSubjectId ) { List shells = shellRepository.findAllBySubmodelEndpointAddress( endpointAddress ); List filtered = shellAccessHandler.filterListOfShellProperties( shells, externalSubjectId ); @@ -509,4 +522,27 @@ private Shell doFindShellByExternalIdWithoutFiltering( String externalShellId ) return shellRepository.findByIdExternal( externalShellId ) .orElseThrow( () -> new EntityNotFoundException( String.format( "Shell for identifier %s not found", externalShellId ) ) ); } + + private T lastItemOf( List list ) { + return list.get( list.size() - 1 ); + } + + private List toKeyValueCombinations( Set shellIdentifiers ) { + return shellIdentifiers.stream() + .map( shellIdentifier -> shellIdentifier.getKey() + shellIdentifier.getValue() ) + .toList(); + } + + private String getCursorEncoded( List queryResult, List assetIdList ) { + if ( !queryResult.isEmpty() && !lastItemOf( assetIdList ).equals( lastItemOf( queryResult ) ) ) { + return Base64.getEncoder().encodeToString( lastItemOf( assetIdList ).getBytes() ); + } + return null; + } + + private Optional getCursorDecoded( String cursor ) { + return Optional.ofNullable( cursor ) + .map( Base64.getDecoder()::decode ) + .map( String::new ); + } } diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 3e449267..d6605325 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -66,6 +66,7 @@ registry: idm: public-client-id: catenax-portal use-granular-access-control: false + granular-access-control-fetch-size: 500 springdoc: cache: diff --git a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/GranularShellServiceTest.java b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/GranularShellServiceTest.java new file mode 100644 index 00000000..faad7a40 --- /dev/null +++ b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/GranularShellServiceTest.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.registry.service; + +import java.util.Set; + +import org.eclipse.tractusx.semantics.RegistryProperties; +import org.eclipse.tractusx.semantics.accesscontrol.sql.model.AccessRule; +import org.eclipse.tractusx.semantics.accesscontrol.sql.model.AccessRulePolicy; +import org.eclipse.tractusx.semantics.accesscontrol.sql.model.policy.AccessRulePolicyValue; +import org.eclipse.tractusx.semantics.accesscontrol.sql.model.policy.PolicyOperator; +import org.eclipse.tractusx.semantics.accesscontrol.sql.repository.AccessControlRuleRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.ActiveProfiles; + +@SpringBootTest +@AutoConfigureMockMvc +@ActiveProfiles( profiles = { "granular", "test" } ) +@EnableConfigurationProperties( RegistryProperties.class ) +class GranularShellServiceTest extends LegacyShellServiceTest { + + private static final String TENANT_ONE = "TENANT_ONE"; + + @Autowired + private AccessControlRuleRepository accessControlRuleRepository; + + @Test + void testsLookupWithNoMatchingRecordsExpectEmptyListAndNoCursor() { + createRule(); + super.testsLookupWithNoMatchingRecordsExpectEmptyListAndNoCursor(); + } + + @Test + void testsLookupWithLessThanAPageOfMatchingRecordsExpectPartialListAndNoCursor() { + createRule(); + super.testsLookupWithLessThanAPageOfMatchingRecordsExpectPartialListAndNoCursor(); + } + + @Test + void testsLookupWithExactlyOnePageOfMatchingRecordsExpectFullListAndNoCursor() { + createRule(); + super.testsLookupWithExactlyOnePageOfMatchingRecordsExpectFullListAndNoCursor(); + } + + @Test + void testsLookupWithOneMoreThanOnePageOfMatchingRecordsExpectFullListAndCursor() { + createRule(); + super.testsLookupWithOneMoreThanOnePageOfMatchingRecordsExpectFullListAndCursor(); + } + + @Test + void testsLookupWithTwoPagesOfMatchingRecordsExpectFullListAndCursor() { + createRule(); + super.testsLookupWithTwoPagesOfMatchingRecordsExpectFullListAndCursor(); + } + + @Test + void testsLookupWithThreePagesOfMatchingRecordsRequestingSecondPageExpectFullListAndCursor() { + createRule(); + super.testsLookupWithThreePagesOfMatchingRecordsRequestingSecondPageExpectFullListAndCursor(); + } + + @Test + void testsLookupWithThreePagesOfMatchingRecordsRequestingPageOfOnlyLastItemExpectSingleItemAndNoCursor() { + createRule(); + super.testsLookupWithThreePagesOfMatchingRecordsRequestingPageOfOnlyLastItemExpectSingleItemAndNoCursor(); + } + + private void createRule() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + AccessRulePolicy policy = new AccessRulePolicy(); + policy.setAccessRules( Set.of( + new AccessRulePolicyValue( AccessRulePolicy.BPN_RULE_NAME, PolicyOperator.EQUALS, TENANT_TWO, null ), + new AccessRulePolicyValue( AccessRulePolicy.MANDATORY_SPECIFIC_ASSET_IDS_RULE_NAME, PolicyOperator.INCLUDES, null, Set.of( + new AccessRulePolicyValue( specificAssetIdName, PolicyOperator.EQUALS, specificAssetIdValue, null ) + ) ), + new AccessRulePolicyValue( AccessRulePolicy.VISIBLE_SPECIFIC_ASSET_ID_NAMES_RULE_NAME, PolicyOperator.INCLUDES, null, Set.of( + new AccessRulePolicyValue( "name", PolicyOperator.EQUALS, specificAssetIdName, null ) + ) ), + new AccessRulePolicyValue( AccessRulePolicy.VISIBLE_SEMANTIC_IDS_RULE_NAME, PolicyOperator.INCLUDES, null, Set.of() ) + ) ); + AccessRule accessRule = new AccessRule(); + accessRule.setPolicyType( AccessRule.PolicyType.AAS ); + accessRule.setTid( TENANT_ONE ); + accessRule.setTargetTenant( TENANT_TWO ); + accessRule.setPolicy( policy ); + accessControlRuleRepository.save( accessRule ); + } +} \ No newline at end of file diff --git a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/LegacyShellServiceTest.java b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/LegacyShellServiceTest.java new file mode 100644 index 00000000..88c43f4f --- /dev/null +++ b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/service/LegacyShellServiceTest.java @@ -0,0 +1,214 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.registry.service; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Base64; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.stream.IntStream; + +import org.eclipse.tractusx.semantics.RegistryProperties; +import org.eclipse.tractusx.semantics.aas.registry.model.AssetAdministrationShellDescriptor; +import org.eclipse.tractusx.semantics.registry.TestUtil; +import org.eclipse.tractusx.semantics.registry.mapper.ShellMapper; +import org.eclipse.tractusx.semantics.registry.model.Shell; +import org.eclipse.tractusx.semantics.registry.model.ShellIdentifier; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +@AutoConfigureMockMvc +@EnableConfigurationProperties( RegistryProperties.class ) +class LegacyShellServiceTest { + + protected static final String TENANT_TWO = "TENANT_TWO"; + + @Autowired + private ShellService shellService; + @Autowired + private ShellMapper shellMapper; + protected String keyPrefix; + + @BeforeEach + void setUp() { + keyPrefix = UUID.randomUUID().toString(); + } + + @Test + void testsLookupWithNoMatchingRecordsExpectEmptyListAndNoCursor() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + Set criteria = Set.of( new ShellIdentifier().withKey( specificAssetIdName ).withValue( specificAssetIdValue ) ); + + final var actual = shellService.findExternalShellIdsByIdentifiersByExactMatch( + criteria, null, null, TENANT_TWO ); + + assertThat( actual ).isNotNull(); + assertThat( actual.getResult() ).isNotNull().isEmpty(); + assertThat( actual.getPagingMetadata() ).isNotNull(); + assertThat( actual.getPagingMetadata().getCursor() ).isNull(); + } + + @Test + void testsLookupWithLessThanAPageOfMatchingRecordsExpectPartialListAndNoCursor() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + Set criteria = Set.of( new ShellIdentifier().withKey( specificAssetIdName ).withValue( specificAssetIdValue ) ); + String id = UUID.randomUUID().toString(); + createShellWithIdAndSpecificAssetIds( id, specificAssetIdName, specificAssetIdValue ); + + final var actual = shellService.findExternalShellIdsByIdentifiersByExactMatch( + criteria, 5, null, TENANT_TWO ); + + assertThat( actual ).isNotNull(); + assertThat( actual.getResult() ).isNotNull().hasSize( 1 ).contains( id ); + assertThat( actual.getPagingMetadata() ).isNotNull(); + assertThat( actual.getPagingMetadata().getCursor() ).isNull(); + } + + @Test + void testsLookupWithExactlyOnePageOfMatchingRecordsExpectFullListAndNoCursor() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + Set criteria = Set.of( new ShellIdentifier().withKey( specificAssetIdName ).withValue( specificAssetIdValue ) ); + List expectedIds = IntStream.rangeClosed( 0, 4 ) + .mapToObj( i -> UUID.randomUUID().toString() ) + .toList(); + expectedIds.forEach( id -> createShellWithIdAndSpecificAssetIds( id, specificAssetIdName, specificAssetIdValue ) ); + + final var actual = shellService.findExternalShellIdsByIdentifiersByExactMatch( + criteria, 5, null, TENANT_TWO ); + + assertThat( actual ).isNotNull(); + assertThat( actual.getResult() ).isNotNull().hasSize( expectedIds.size() ).containsAll( expectedIds ); + assertThat( actual.getPagingMetadata() ).isNotNull(); + assertThat( actual.getPagingMetadata().getCursor() ).isNull(); + } + + @Test + void testsLookupWithOneMoreThanOnePageOfMatchingRecordsExpectFullListAndCursor() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + Set criteria = Set.of( new ShellIdentifier().withKey( specificAssetIdName ).withValue( specificAssetIdValue ) ); + int pageSize = 5; + int totalItems = pageSize + 1; + List expectedIds = IntStream.range( 0, totalItems ) + .mapToObj( i -> UUID.randomUUID().toString() ) + .toList(); + expectedIds.forEach( id -> createShellWithIdAndSpecificAssetIds( id, specificAssetIdName, specificAssetIdValue ) ); + + final var actual = shellService.findExternalShellIdsByIdentifiersByExactMatch( + criteria, pageSize, null, TENANT_TWO ); + + assertThat( actual ).isNotNull(); + assertThat( actual.getResult() ).isNotNull().hasSize( pageSize ).containsAll( expectedIds.subList( 0, pageSize ) ); + assertThat( actual.getPagingMetadata() ).isNotNull(); + assertThat( actual.getPagingMetadata().getCursor() ).isNotNull() + .isEqualTo( toCursor( expectedIds, pageSize - 1 ) ); + } + + @Test + void testsLookupWithTwoPagesOfMatchingRecordsExpectFullListAndCursor() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + Set criteria = Set.of( new ShellIdentifier().withKey( specificAssetIdName ).withValue( specificAssetIdValue ) ); + int pageSize = 5; + int totalItems = pageSize + pageSize; + List expectedIds = IntStream.range( 0, totalItems ) + .mapToObj( i -> UUID.randomUUID().toString() ) + .toList(); + expectedIds.forEach( id -> createShellWithIdAndSpecificAssetIds( id, specificAssetIdName, specificAssetIdValue ) ); + + final var actual = shellService.findExternalShellIdsByIdentifiersByExactMatch( + criteria, pageSize, null, TENANT_TWO ); + + assertThat( actual ).isNotNull(); + assertThat( actual.getResult() ).isNotNull().hasSize( pageSize ).containsAll( expectedIds.subList( 0, pageSize ) ); + assertThat( actual.getPagingMetadata() ).isNotNull(); + assertThat( actual.getPagingMetadata().getCursor() ).isNotNull() + .isEqualTo( toCursor( expectedIds, pageSize - 1 ) ); + } + + @Test + void testsLookupWithThreePagesOfMatchingRecordsRequestingSecondPageExpectFullListAndCursor() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + Set criteria = Set.of( new ShellIdentifier().withKey( specificAssetIdName ).withValue( specificAssetIdValue ) ); + int pageSize = 5; + int totalItems = pageSize * 3; + List expectedIds = IntStream.range( 0, totalItems ) + .mapToObj( i -> UUID.randomUUID().toString() ) + .toList(); + expectedIds.forEach( id -> createShellWithIdAndSpecificAssetIds( id, specificAssetIdName, specificAssetIdValue ) ); + + final var actual = shellService.findExternalShellIdsByIdentifiersByExactMatch( + criteria, pageSize, toCursor( expectedIds, pageSize - 1 ), TENANT_TWO ); + + assertThat( actual ).isNotNull(); + assertThat( actual.getResult() ).isNotNull().hasSize( pageSize ).containsAll( expectedIds.subList( pageSize, pageSize * 2 ) ); + assertThat( actual.getPagingMetadata() ).isNotNull(); + assertThat( actual.getPagingMetadata().getCursor() ).isNotNull() + .isEqualTo( toCursor( expectedIds, pageSize * 2 - 1 ) ); + } + + @Test + void testsLookupWithThreePagesOfMatchingRecordsRequestingPageOfOnlyLastItemExpectSingleItemAndNoCursor() { + String specificAssetIdName = keyPrefix + "key"; + String specificAssetIdValue = "value"; + Set criteria = Set.of( new ShellIdentifier().withKey( specificAssetIdName ).withValue( specificAssetIdValue ) ); + int pageSize = 5; + int totalItems = pageSize * 3; + List expectedIds = IntStream.range( 0, totalItems ) + .mapToObj( i -> UUID.randomUUID().toString() ) + .toList(); + expectedIds.forEach( id -> createShellWithIdAndSpecificAssetIds( id, specificAssetIdName, specificAssetIdValue ) ); + + final var actual = shellService.findExternalShellIdsByIdentifiersByExactMatch( + criteria, pageSize, toCursor( expectedIds, pageSize * 3 - 2 ), TENANT_TWO ); + + assertThat( actual ).isNotNull(); + assertThat( actual.getResult() ).isNotNull().hasSize( 1 ).containsAll( expectedIds.subList( pageSize * 3 - 1, pageSize * 3 ) ); + assertThat( actual.getPagingMetadata() ).isNotNull(); + assertThat( actual.getPagingMetadata().getCursor() ).isNull(); + } + + private String toCursor( List expectedIds, int indexOfLastVisibleId ) { + return new String( Base64.getUrlEncoder().encode( expectedIds.get( indexOfLastVisibleId ).getBytes() ) ); + } + + private void createShellWithIdAndSpecificAssetIds( String id, String specificAssetIdName, String specificAssetIdValue ) { + AssetAdministrationShellDescriptor shellDescriptor = TestUtil.createCompleteAasDescriptor(); + shellDescriptor.setId( id ); + shellDescriptor.setSpecificAssetIds( List.of( TestUtil.createSpecificAssetId( specificAssetIdName, specificAssetIdValue, List.of( TENANT_TWO ) ) ) ); + Shell shell = shellMapper.fromApiDto( shellDescriptor ); + shellService.mapShellCollection( shell ); + if ( !shell.getSubmodels().isEmpty() ) + shellService.mapSubmodel( shell.getSubmodels() ); + shellService.save( shell ); + } +} \ No newline at end of file diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml index 5a9949a4..488ac4e5 100644 --- a/charts/registry/Chart.yaml +++ b/charts/registry/Chart.yaml @@ -26,7 +26,7 @@ sources: - https://github.com/eclipse-tractusx/sldt-digital-twin-registry type: application -version: 0.4.7 +version: 0.4.8 appVersion: 0.3.23 dependencies: diff --git a/charts/registry/README.md b/charts/registry/README.md index c5b9de0b..00bfa19c 100644 --- a/charts/registry/README.md +++ b/charts/registry/README.md @@ -30,63 +30,65 @@ helm install registry -n semantics charts/registry ## Values -| Key | Type | Default | Description | -|------------------------------------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------| -| enableKeycloak | bool | `true` | | -| enablePostgres | bool | `true` | | -| keycloak.args[0] | string | `"kc.sh import --file /opt/keycloak/data/import/default-realm-import.json; kc.sh start-dev --hostname=registry-keycloak --hostname-strict=false --proxy=edge"` | | -| keycloak.auth.adminPassword | string | `"admin"` | | -| keycloak.auth.adminUser | string | `"admin"` | | -| keycloak.command[0] | string | `"/bin/sh"` | | -| keycloak.command[1] | string | `"-c"` | | -| keycloak.externalDatabase.existingSecret | string | `"keycloak-database-credentials"` | | -| keycloak.extraVolumeMounts[0].mountPath | string | `"/opt/keycloak/data/import/default-realm-import.json"` | | -| keycloak.extraVolumeMounts[0].name | string | `"init-script-vol"` | | -| keycloak.extraVolumeMounts[0].subPath | string | `"default-realm-import.json"` | | -| keycloak.extraVolumes[0].configMap.name | string | `"init-script-vol"` | | -| keycloak.extraVolumes[0].name | string | `"init-script-vol"` | | -| keycloak.fullnameOverride | string | `"registry-keycloak"` | | -| keycloak.postgresql.enabled | bool | `false` | | -| keycloak.service.type | string | `"ClusterIP"` | | -| postgresql.auth.database | string | `"default-database"` | | -| postgresql.auth.password | string | `"password"` | | -| postgresql.auth.username | string | `"default-user"` | | -| postgresql.primary.persistence.enabled | bool | `true` | | -| postgresql.primary.persistence.size | string | `"50Gi"` | | -| postgresql.service.ports.postgresql | int | `5432` | | -| registry.authentication | bool | `true` | | -| registry.containerPort | int | `4243` | | -| registry.dataSource.driverClassName | string | `"org.postgresql.Driver"` | | -| registry.dataSource.password | string | `""` | | -| registry.dataSource.sqlInitPlatform | string | `"pg"` | | -| registry.dataSource.url | string | `"jdbc:postgresql://database:5432"` | | -| registry.dataSource.user | string | `"default-user"` | | -| registry.host | string | `"minikube"` | | -| registry.idpClientId | string | `"default-client"` | | -| registry.idpIssuerUri | string | `""` | | -| registry.image.registry | string | `"docker.io"` | | -| registry.image.repository | string | `"tractusx/sldt-digital-twin-registry"` | | -| registry.image.version | string | `""` | | -| registry.imagePullPolicy | string | `"IfNotPresent"` | | -| registry.ingress.annotations | object | `{}` | | -| registry.ingress.className | string | `"nginx"` | | -| registry.ingress.enabled | bool | `false` | | -| registry.ingress.tls | bool | `false` | | -| registry.ingress.urlPrefix | string | `"/semantics/registry"` | | -| registry.replicaCount | int | `1` | | -| registry.resources.limits.memory | string | `"1024Mi"` | | -| registry.resources.requests.memory | string | `"512Mi"` | | -| registry.service.port | int | `8080` | | -| registry.service.type | string | `"ClusterIP"` | | -| registry.tenantId | string | `"default-tenant"` | | -| registry.externalSubjectIdWildcardPrefix | string | `PUBLIC_READABLE` | | -| registry.externalSubjectIdWildcardAllowedTypes | string | `manufacturerPartId,assetLifecyclePhase` | | -| registry.livenessProbe.initialDelaySeconds | int | `100` | | -| registry.livenessProbe.failureThreshold | int | `3` | | -| registry.livenessProbe.periodSeconds | int | `3` | | -| registry.readinessProbe.initialDelaySeconds | int | `100` | | -| registry.readinessProbe.failureThreshold | int | `3` | | -| registry.readinessProbe.periodSeconds | int | `3` | | +| Key | Type | Default | Description | +|------------------------------------------------|--------|----------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| +| enableKeycloak | bool | `true` | | +| enablePostgres | bool | `true` | | +| keycloak.args[0] | string | `"kc.sh import --file /opt/keycloak/data/import/default-realm-import.json; kc.sh start-dev --hostname=registry-keycloak --hostname-strict=false --proxy=edge"` | | +| keycloak.auth.adminPassword | string | `"admin"` | | +| keycloak.auth.adminUser | string | `"admin"` | | +| keycloak.command[0] | string | `"/bin/sh"` | | +| keycloak.command[1] | string | `"-c"` | | +| keycloak.externalDatabase.existingSecret | string | `"keycloak-database-credentials"` | | +| keycloak.extraVolumeMounts[0].mountPath | string | `"/opt/keycloak/data/import/default-realm-import.json"` | | +| keycloak.extraVolumeMounts[0].name | string | `"init-script-vol"` | | +| keycloak.extraVolumeMounts[0].subPath | string | `"default-realm-import.json"` | | +| keycloak.extraVolumes[0].configMap.name | string | `"init-script-vol"` | | +| keycloak.extraVolumes[0].name | string | `"init-script-vol"` | | +| keycloak.fullnameOverride | string | `"registry-keycloak"` | | +| keycloak.postgresql.enabled | bool | `false` | | +| keycloak.service.type | string | `"ClusterIP"` | | +| postgresql.auth.database | string | `"default-database"` | | +| postgresql.auth.password | string | `"password"` | | +| postgresql.auth.username | string | `"default-user"` | | +| postgresql.primary.persistence.enabled | bool | `true` | | +| postgresql.primary.persistence.size | string | `"50Gi"` | | +| postgresql.service.ports.postgresql | int | `5432` | | +| registry.authentication | bool | `true` | | +| registry.containerPort | int | `4243` | | +| registry.dataSource.driverClassName | string | `"org.postgresql.Driver"` | | +| registry.dataSource.password | string | `""` | | +| registry.dataSource.sqlInitPlatform | string | `"pg"` | | +| registry.dataSource.url | string | `"jdbc:postgresql://database:5432"` | | +| registry.dataSource.user | string | `"default-user"` | | +| registry.host | string | `"minikube"` | | +| registry.idpClientId | string | `"default-client"` | | +| registry.idpIssuerUri | string | `""` | | +| registry.image.registry | string | `"docker.io"` | | +| registry.image.repository | string | `"tractusx/sldt-digital-twin-registry"` | | +| registry.image.version | string | `""` | | +| registry.imagePullPolicy | string | `"IfNotPresent"` | | +| registry.ingress.annotations | object | `{}` | | +| registry.ingress.className | string | `"nginx"` | | +| registry.ingress.enabled | bool | `false` | | +| registry.ingress.tls | bool | `false` | | +| registry.ingress.urlPrefix | string | `"/semantics/registry"` | | +| registry.replicaCount | int | `1` | | +| registry.resources.limits.memory | string | `"1024Mi"` | | +| registry.resources.requests.memory | string | `"512Mi"` | | +| registry.service.port | int | `8080` | | +| registry.service.type | string | `"ClusterIP"` | | +| registry.tenantId | string | `"default-tenant"` | | +| registry.externalSubjectIdWildcardPrefix | string | `PUBLIC_READABLE` | | +| registry.externalSubjectIdWildcardAllowedTypes | string | `manufacturerPartId,assetLifecyclePhase` | | +| registry.livenessProbe.initialDelaySeconds | int | `100` | | +| registry.livenessProbe.failureThreshold | int | `3` | | +| registry.livenessProbe.periodSeconds | int | `3` | | +| registry.readinessProbe.initialDelaySeconds | int | `100` | | +| registry.readinessProbe.failureThreshold | int | `3` | | +| registry.readinessProbe.periodSeconds | int | `3` | | +| registry.useGranularAccessControl | string | `"true"` | Turns the granular access control on/off. | +| registry.granularAccessControlFetchSize | string | `"500"` | Defines how many records should be fetched in one query when using granular access control. | ---------------------------------------------- Autogenerated from chart metadata using [helm-docs v1.11.0](https://github.com/norwoodj/helm-docs/releases/v1.11.0) diff --git a/charts/registry/templates/registry/registry-secret.yaml b/charts/registry/templates/registry/registry-secret.yaml index 0996c02b..05f4fa27 100644 --- a/charts/registry/templates/registry/registry-secret.yaml +++ b/charts/registry/templates/registry/registry-secret.yaml @@ -40,4 +40,5 @@ data: REGISTRY_EXTERNAL_SUBJECT_ID_WILDCARD_PREFIX: {{ .Values.registry.externalSubjectIdWildcardPrefix | b64enc }} REGISTRY_EXTERNAL_SUBJECT_ID_WILDCARD_ALLOWED_TYPES: {{ .Values.registry.externalSubjectIdWildcardAllowedTypes | b64enc }} REGISTRY_USE_GRANULAR_ACCESS_CONTROL: {{ .Values.registry.useGranularAccessControl | b64enc }} + REGISTRY_GRANULAR_ACCESS_CONTROL_FETCH_SIZE: {{ .Values.registry.granularAccessControlFetchSize | b64enc }} diff --git a/charts/registry/values.yaml b/charts/registry/values.yaml index 8e325841..61dcfb22 100644 --- a/charts/registry/values.yaml +++ b/charts/registry/values.yaml @@ -50,6 +50,7 @@ registry: externalSubjectIdWildcardPrefix: PUBLIC_READABLE externalSubjectIdWildcardAllowedTypes: manufacturerPartId,digitalTwinType useGranularAccessControl: "false" + granularAccessControlFetchSize: "500" service: port: 8080 type: ClusterIP From a7640fec5d6307f337e6941d8705ce76f6690393 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Mon, 26 Feb 2024 15:22:46 +0100 Subject: [PATCH 08/26] Fix release of two new component with 0.3.23 (one time merge to release branch). --- access-control-service-interface/pom.xml | 2 +- access-control-service-sql-impl/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/access-control-service-interface/pom.xml b/access-control-service-interface/pom.xml index aaac4a65..711a816a 100644 --- a/access-control-service-interface/pom.xml +++ b/access-control-service-interface/pom.xml @@ -26,7 +26,7 @@ org.eclipse.tractusx digital-twin-registry - DEV-SNAPSHOT + 0.3.23 ../pom.xml diff --git a/access-control-service-sql-impl/pom.xml b/access-control-service-sql-impl/pom.xml index 612d4b08..8babc27c 100644 --- a/access-control-service-sql-impl/pom.xml +++ b/access-control-service-sql-impl/pom.xml @@ -26,7 +26,7 @@ org.eclipse.tractusx digital-twin-registry - DEV-SNAPSHOT + 0.3.23 ../pom.xml From 574374641b7adf312bef4736c4049e4cfd02d51a Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Mon, 26 Feb 2024 15:30:15 +0100 Subject: [PATCH 09/26] revert reference version to parent pom (DEV-SNAPSHOT) --- access-control-service-interface/pom.xml | 2 +- access-control-service-sql-impl/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/access-control-service-interface/pom.xml b/access-control-service-interface/pom.xml index 711a816a..aaac4a65 100644 --- a/access-control-service-interface/pom.xml +++ b/access-control-service-interface/pom.xml @@ -26,7 +26,7 @@ org.eclipse.tractusx digital-twin-registry - 0.3.23 + DEV-SNAPSHOT ../pom.xml diff --git a/access-control-service-sql-impl/pom.xml b/access-control-service-sql-impl/pom.xml index 8babc27c..612d4b08 100644 --- a/access-control-service-sql-impl/pom.xml +++ b/access-control-service-sql-impl/pom.xml @@ -26,7 +26,7 @@ org.eclipse.tractusx digital-twin-registry - 0.3.23 + DEV-SNAPSHOT ../pom.xml From 0774ed9212a113bfb0b7259de5d855a5f51c2c46 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Mon, 26 Feb 2024 16:09:52 +0100 Subject: [PATCH 10/26] Prepeare Release 0.4.9-RC1 --- CHANGELOG.md | 2 +- charts/registry/Chart.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35b2361f..79b33193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ 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.4.0 +## 0.4.1-RC1 ### Added - Granular access control diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml index 488ac4e5..4915615d 100644 --- a/charts/registry/Chart.yaml +++ b/charts/registry/Chart.yaml @@ -26,8 +26,8 @@ sources: - https://github.com/eclipse-tractusx/sldt-digital-twin-registry type: application -version: 0.4.8 -appVersion: 0.3.23 +version: 0.4.9-RC1 +appVersion: 0.4.1-RC1 dependencies: - repository: https://charts.bitnami.com/bitnami From bc64c31f833db120434523acc8c722c447450645 Mon Sep 17 00:00:00 2001 From: Istvan Zoltan Nagy Date: Tue, 27 Feb 2024 13:58:28 +0100 Subject: [PATCH 11/26] Document granular access control - Adds clarification about how granular access control works --- docs/README.md | 199 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 198 insertions(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 4f00bb92..44f3c213 100644 --- a/docs/README.md +++ b/docs/README.md @@ -430,6 +430,9 @@ 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 + +#### Classic implementation + 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. @@ -464,7 +467,6 @@ The defined string for `"PUBLIC_READABLE"` and the list of allowed types to mark * `registry.externalSubjectIdWildcardAllowedTypes` (Default is `"manufacturerPartId,assetLifecyclePhase"` ) Detailed information can be found [here](../INSTALL.md) - _________________ @@ -605,6 +607,201 @@ Any (external) readers with respect to the `"PUBLIC_READABLE"` flag at `specific of this *Digital Twin*. +#### Granular access control implementation + +The granular access control implementation is provided as an alternative option to enforce visibility rules of the *Digital Twin* details. These can be: + +1. The visibility of the *Digital Twin* as a whole +2. The visibility of certain `specificAssetId` names and values of the *Digital Twin* +3. The visibility of certain `submodelDescriptors` of the *Digital Twin* +4. Restricting access to *Digital Twin* details which are `"PUBLIC_READABLE"` + (only showing the `id`, the public readable `specificAssetId` names and values, the `createdDate` and the filtered `submodelDescriptors` ) + +##### Configuring granular access control + +To enable granular access control (instead of the classic implementation), the `registry.useGranularAccessControl` configuration HELM chart property must be set to `"true"`. +This will in turn set `registry.use-granular-access-control` Spring property to `true`, which will activate the granular access control. + +In addition to the aforementioned property, we can set the number of records fetched when listing records. This can be done by setting the +`registry.granularAccessControlFetchSize` HELM chart property. The default value is `"500"`. Providing this property will set the `registry.granular-access-control-fetch-size` +Spring property of the Digital Twin Registry to the equivalent int value. In general, the higher we can set this value, the fewer fetches will be required when shells are +listed and filtered. It is recommended to use at least 1000 if the registry has more than 100 000 Digital Twins. + +##### Creating an access rule + +The access rules can be managed using the provided access rule API ([See API specs here](../access-control-service-sql-impl/src/main/resources/static/access-control-openapi.yaml)). + +> [!NOTE] +> In order to use the API, the client must have the `read_access_rules` and `write_access_rules` roles. + +Please refer to the following table to get familiar with the schema of the access rules. + +| Property | Type | Required on create | Description | +|-------------|----------|--------------------|----------------------------------------------------------------------------------------| +| id | long | No (read-only) | The auto-incremented Id of the rule. | +| tid | string | No (read-only) | The Id of the owner tenant (the owner of the Digital Twin Registry). | +| policyType | enum | Yes | Defines the policy language used for the rule's policy. Possible values: `AAS`. | +| policy | json | Yes | The definition of the access rule. | +| description | string | No (optional) | An short, optional description or note to help with the maintenance of the rule. | +| validFrom | datetime | No (optional) | An optional timestamp representing the earlier time when the rule should be in effect. | +| validTo | datetime | No (optional) | An optional timestamp representing the latest time when the rule should be in effect. | + +An example policy: + +```json +{ + "id": 1, + "tid": "00000000-1111-2222-3333-444444444444", + "policyType": "AAS", + "policy": { + "accessRules": [ + { + "attribute": "bpn", + "operator": "eq", + "value": "BPNL00000000000A" + }, + { + "attribute": "mandatorySpecificAssetIds", + "operator": "includes", + "values": [ + { + "attribute": "manufacturerPartId", + "operator": "eq", + "value": "99991" + }, + { + "attribute": "customerPartId", + "operator": "eq", + "value": "ACME001" + } + ] + }, + { + "attribute": "visibleSpecificAssetIdNames", + "operator": "includes", + "values": [ + { + "attribute": "name", + "operator": "eq", + "value": "manufacturerPartId" + }, + { + "attribute": "name", + "operator": "eq", + "value": "customerPartId" + }, + { + "attribute": "name", + "operator": "eq", + "value": "partInstanceId" + } + ] + }, + { + "attribute": "visibleSemanticIds", + "operator": "includes", + "values": [ + { + "attribute": "modelUrn", + "operator": "eq", + "value": "Traceabilityv1.1.0" + }, + { + "attribute": "modelUrn", + "operator": "eq", + "value": "ProductCarbonFootprintv1.1.0" + } + ] + } + ] + }, + "description": "Access rule description.", + "validFrom": "2024-01-02T03:04:05Z", + "validTo": "2024-06-07T08:09:10Z" +} +``` + +The example policy above can be split into multiple parts when read. + +1. Validity - It is valid between `2024-01-02T03:04:05Z` and `2024-06-07T08:09:10Z`. Otherwise, it is ignored. +2. Scope - Outlining when a rule is applicable. + 1. The first access rule (`$.policy.accessRules[0]`) defines the *bpn* (*externalSubjectId*) of the tenant to whom the policy applies. + 2. The second access rule (`$.policy.accessRules[1]`) defines the *mandatorySpecificAssetIds* which must be present in the *Digital Twin* in order for the rule to be applicable. The rule will become applicable only if __all__ *specificAssetId* name-value pairs of the rule are present in the *Digital Twin*. +3. Effect - Defines which parts of the matching *Digital Twins* should be visible when the client's *externalSubjectId* matches the rule's. + 1. The third access rule (`$.policy.accessRules[2]`) defines the *visibleSpecificAssetIdNames*. These are the names of the *specificAssetIds* from the *Digital Twin* which should be visible when the rule matches. + 2. The fourth access rule (`$.policy.accessRules[3]`) defines the *visibleSemanticIds*. These *semanticIds* are identifying the *submodelDescriptors* from the *Digital Twin* which should be visible when the rule matches. + +##### How the rule evaluation works? + +In general, when a shell's visibility is evaluated, we must: + +1. Take the *externalSubjectId* of the client, the *ownerTenantId* of the *Digital Twin Registry* and the contents of the *Digital Twin* in question. +2. If the *externalSubjectId* is equal to the *ownerTenantId*, the client can see the full content. +3. Otherwise, we must fetch all access rules which belong to the client's *externalSubjectId* or `PUBLIC_READABLE`; and is in the specified validity period +4. Then, for each *specificAssetId* of the *Digital Twin*, we must verify whether there is at least one applicable rule that gives access to the *specificAssetId* of the *Digital Twin*. +5. Similarly, for each *submodelDescriptor* of the *Digital Twin*, we must verify whether there is at least one applicable rule that gives access to the *semanticId* of the *submodelDescriptor* from the *Digital Twin*. + +The process acn be summed up in a more visual way as shown in the diagram below: + +```mermaid +flowchart LR + START + START-->isAdmin{Is\nexternalSubjectId\n=\nownerTenantId?} + isAdmin-- no -->fetchRules[Fetch valid rules\nfor externalSubjectId\nor PUBLIC_READABLE] + isAdmin-- yes -->showAll[Show full content] + fetchRules-->anyRules{Any rules found?} + anyRules-- yes -->startForEach((For each rule)) + startForEach-->isRuleMatching{Is the\nrule matching\nthe Digital Twin?} + isRuleMatching-- no -->endForEach((For each rule)) + isRuleMatching-- yes -->applyRule[Apply rule effect] + applyRule-->endForEach + endForEach-->isShellVisible{Is the\nDigital Twin\nvisible?} + isShellVisible-- yes -->showFiltered[Show filtered Digital Twin] + isShellVisible-- no -->denied[Hide Digital Twin] + anyRules-- no -->denied + showAll-->END + denied-->END + showFiltered-->END +``` + +###### Lookup shells - `GET {{baseUrl}}/api/v3.0/lookup/shells?assetIds=...` + +In case of the lookup shells, the filtering and access control of the *Digital Twins* is done using the following steps: + +1. A page (fetchSize) of *Digital Twins* is loaded which are matching the client's query expression. +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 AAS Ids of the visible *Digital Twins* are returned. + +###### Get all shells - `GET {{baseUrl}}/api/v3.0/shell-descriptors` + +The process is similar to the lookup shells, the filtering and access control of the *Digital Twins* is done as follows: + +1. A page (fetchSize) of *Digital Twins* is loaded. +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. + +###### Get Shell by AAS Id - `GET {{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier` + +To determine the visibility of a single *Digital Twin*, we can simply: + +1. Fetch the *Digital Twin* +2. Apply the access control rules using the process defined at the beginning of this section +3. Return the visible parts of the *Digital Twin* (or empty result in case the *Digital Twin* is not visible at all) + +#### Public readable + +When a *Digital Twin* is only visible because there are applicable `PUBLIC_READABLE` rules which make certain properties visible, +the shell details are further limited. This means, that we are returning only: + +- the `id`, +- the `idEsternal` (*AAS Id*), +- the public readable `specificAssetId` names and values, +- the `createdDate` +- the filtered `submodelDescriptors` + + ## 7 Quality scenarios ### Quality Requirements From b3ee48a3e37de1b6ca12bccf78f99606e7715714 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Tue, 27 Feb 2024 16:52:05 +0100 Subject: [PATCH 12/26] Add mermaid diagram overview --- docs/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/README.md b/docs/README.md index 4f00bb92..65a04df7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,6 +28,27 @@ Because now the DTR is deployed decentralized on each Data Provider side. There The whole search and the embedding of the now decentralized Digital Twin is shown below: ### decentralized Digital Twin Registry environment +```mermaid +graph TD + subgraph Consumer_Environment [Consumer Environment] + Consumer_Application[Consumer Application] --> Consumer_EDC[Consumer EDC] + end + + subgraph Central_Environment [Portal] + Portal_SSI[SSI] + end + + subgraph Provider_Environment[Provider Environment] + Provider_EDC --> Decentralized_DTR[Decentralized DTR] + Provider[Provider] --> |create twins| Decentralized_DTR + Keycloak -.->|get token| Provider + Keycloak -.->|get token| Provider_EDC + end + + Consumer_EDC -->|request twins| Provider_EDC + Consumer_EDC --> Central_Environment + Provider_EDC --> Central_Environment +``` ![](img/decentralEnviroment.PNG) From d33d0aace30ad493244a80791b147bd5de00174c Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Wed, 28 Feb 2024 12:26:02 +0100 Subject: [PATCH 13/26] Update Springboot to version 3.2.3 Update postgres dependency to 42.7.2 --- CHANGELOG.md | 7 +++ DEPENDENCIES | 153 ++++++++++++++++++++++++++------------------------- pom.xml | 6 +- 3 files changed, 87 insertions(+), 79 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b33193..becd6c93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ 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.4.1-RC2 +### Added +- Update Springboot to version 3.2.3 +- Update postgres dependency version to 42.7.2 + +## fixed + ## 0.4.1-RC1 ### Added - Granular access control diff --git a/DEPENDENCIES b/DEPENDENCIES index 81f8e278..ec11fe8d 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,12 +1,14 @@ +Invalid: org.eclipse.tractusx.digital_twin_registry:digital-twin-registry-access-control-service-interface:jar:DEV-SNAPSHOT:compile, unknown, restricted, none +Invalid: org.eclipse.tractusx.digital_twin_registry:digital-twin-registry-access-control-service-sql-impl:jar:DEV-SNAPSHOT:compile -- module digital.twin.registry.access.control.service.sql.impl.DEV.SNAPSHOT (auto), unknown, restricted, none maven/mavencentral/ch.qos.logback/logback-classic/1.4.14, EPL-1.0 OR LGPL-2.1-only, approved, #3435 maven/mavencentral/ch.qos.logback/logback-core/1.4.14, EPL-1.0 OR LGPL-2.1-only, approved, #3373 -maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.3, Apache-2.0, approved, #7947 -maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.3, MIT AND Apache-2.0, approved, #7932 +maven/mavencentral/com.fasterxml.jackson.core/jackson-annotations/2.15.4, Apache-2.0, approved, #7947 +maven/mavencentral/com.fasterxml.jackson.core/jackson-core/2.15.4, MIT AND Apache-2.0, approved, #7932 maven/mavencentral/com.fasterxml.jackson.core/jackson-databind/2.14.1, Apache-2.0, approved, #4105 -maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.3, Apache-2.0, approved, #8802 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.15.3, Apache-2.0, approved, #8808 -maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.3, Apache-2.0, approved, #7930 -maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.15.3, Apache-2.0, approved, #8803 +maven/mavencentral/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml/2.15.4, Apache-2.0, approved, #8802 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jdk8/2.15.4, Apache-2.0, approved, #8808 +maven/mavencentral/com.fasterxml.jackson.datatype/jackson-datatype-jsr310/2.15.4, Apache-2.0, approved, #7930 +maven/mavencentral/com.fasterxml.jackson.module/jackson-module-parameter-names/2.15.4, Apache-2.0, approved, #8803 maven/mavencentral/com.fasterxml/classmate/1.6.0, Apache-2.0, approved, clearlydefined maven/mavencentral/com.github.stephenc.jcip/jcip-annotations/1.0-1, Apache-2.0, approved, CQ21949 maven/mavencentral/com.google.code.findbugs/jsr305/3.0.2, Apache-2.0, approved, #20 @@ -20,10 +22,10 @@ maven/mavencentral/com.nimbusds/nimbus-jose-jwt/9.24.4, Apache-2.0, approved, cl maven/mavencentral/com.opencsv/opencsv/5.7.1, Apache-2.0, approved, clearlydefined maven/mavencentral/com.zaxxer/HikariCP/5.0.1, Apache-2.0, approved, clearlydefined maven/mavencentral/io.github.classgraph/classgraph/4.8.149, MIT, approved, CQ22530 -maven/mavencentral/io.micrometer/micrometer-commons/1.12.2, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #11679 -maven/mavencentral/io.micrometer/micrometer-core/1.12.2, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #11678 -maven/mavencentral/io.micrometer/micrometer-jakarta9/1.12.2, Apache-2.0, approved, #12923 -maven/mavencentral/io.micrometer/micrometer-observation/1.12.2, Apache-2.0, approved, #11680 +maven/mavencentral/io.micrometer/micrometer-commons/1.12.3, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #11679 +maven/mavencentral/io.micrometer/micrometer-core/1.12.3, Apache-2.0 AND (Apache-2.0 AND MIT), approved, #11678 +maven/mavencentral/io.micrometer/micrometer-jakarta9/1.12.3, Apache-2.0, approved, #12923 +maven/mavencentral/io.micrometer/micrometer-observation/1.12.3, Apache-2.0, approved, #11680 maven/mavencentral/io.swagger.core.v3/swagger-annotations-jakarta/2.2.7, Apache-2.0, approved, #5947 maven/mavencentral/io.swagger.core.v3/swagger-annotations/2.0.0, Apache-2.0, approved, clearlydefined maven/mavencentral/io.swagger.core.v3/swagger-core-jakarta/2.2.7, Apache-2.0, approved, #5929 @@ -45,37 +47,36 @@ maven/mavencentral/org.apache.commons/commons-lang3/3.13.0, Apache-2.0, approved maven/mavencentral/org.apache.commons/commons-text/1.10.0, Apache-2.0, approved, clearlydefined maven/mavencentral/org.apache.logging.log4j/log4j-api/2.21.1, Apache-2.0 AND (Apache-2.0 AND LGPL-2.0-or-later), approved, #11079 maven/mavencentral/org.apache.logging.log4j/log4j-to-slf4j/2.21.1, Apache-2.0, approved, #11919 -maven/mavencentral/org.apache.tomcat.embed/tomcat-embed-el/10.1.18, Apache-2.0, approved, #6997 maven/mavencentral/org.aspectj/aspectjweaver/1.9.21, Apache-2.0 AND BSD-3-Clause AND EPL-1.0 AND BSD-3-Clause AND Apache-1.1, approved, #7695 -maven/mavencentral/org.checkerframework/checker-qual/3.5.0, MIT, approved, clearlydefined -maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-client/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-common/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-server/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jetty-server/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-servlet/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-annotations/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-plus/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-servlet/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-servlets/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-webapp/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-core-client/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-core-common/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-core-server/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-jetty-api/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-jetty-common/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-alpn-client/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-client/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-http/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-io/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-jndi/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-plus/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-security/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-server/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-session/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-util/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty -maven/mavencentral/org.eclipse.jetty/jetty-xml/12.0.5, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.checkerframework/checker-qual/3.42.0, MIT, approved, clearlydefined +maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-client/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-common/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jakarta-server/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-jetty-server/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10.websocket/jetty-ee10-websocket-servlet/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-annotations/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-plus/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-servlet/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-servlets/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.ee10/jetty-ee10-webapp/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-core-client/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-core-common/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-core-server/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-jetty-api/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty.websocket/jetty-websocket-jetty-common/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-alpn-client/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-client/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-http/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-io/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-jndi/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-plus/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-security/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-server/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-session/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-util/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty +maven/mavencentral/org.eclipse.jetty/jetty-xml/12.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty maven/mavencentral/org.glassfish/jakarta.json/2.0.1, EPL-2.0 OR GPL-2.0-only with Classpath-exception-2.0, approved, ee4j.jsonp -maven/mavencentral/org.hibernate.orm/hibernate-core/6.4.1.Final, LGPL-2.1-or-later AND (EPL-2.0 OR BSD-3-Clause) AND MIT, approved, #12490 +maven/mavencentral/org.hibernate.orm/hibernate-core/6.4.4.Final, LGPL-2.1-or-later AND (EPL-2.0 OR BSD-3-Clause) AND MIT, approved, #12490 maven/mavencentral/org.hibernate.validator/hibernate-validator/8.0.1.Final, Apache-2.0, approved, clearlydefined maven/mavencentral/org.jboss.logging/jboss-logging/3.5.3.Final, Apache-2.0, approved, #9471 maven/mavencentral/org.liquibase/liquibase-core/4.19.1, Apache-2.0, approved, clearlydefined @@ -84,7 +85,7 @@ maven/mavencentral/org.openapitools/jackson-databind-nullable/0.1.0, Apache-2.0, maven/mavencentral/org.ow2.asm/asm-commons/9.6, BSD-3-Clause, approved, #10775 maven/mavencentral/org.ow2.asm/asm-tree/9.6, BSD-3-Clause, approved, #10773 maven/mavencentral/org.ow2.asm/asm/9.6, BSD-3-Clause, approved, #10776 -maven/mavencentral/org.postgresql/postgresql/42.5.1, BSD-2-Clause, approved, #3416 +maven/mavencentral/org.postgresql/postgresql/42.7.2, BSD-2-Clause AND Apache-2.0, approved, #11681 maven/mavencentral/org.projectlombok/lombok/1.18.24, MIT AND LicenseRef-Public-Domain, approved, CQ23907 maven/mavencentral/org.slf4j/jul-to-slf4j/2.0.7, MIT, approved, #7698 maven/mavencentral/org.slf4j/slf4j-api/2.0.7, MIT, approved, #5915 @@ -92,42 +93,42 @@ maven/mavencentral/org.slf4j/slf4j-simple/2.0.7, MIT, approved, #10372 maven/mavencentral/org.springdoc/springdoc-openapi-starter-common/2.0.2, Apache-2.0, approved, #5920 maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-api/2.0.2, Apache-2.0, approved, #5950 maven/mavencentral/org.springdoc/springdoc-openapi-starter-webmvc-ui/2.0.2, Apache-2.0, approved, #5923 -maven/mavencentral/org.springframework.boot/spring-boot-actuator-autoconfigure/3.2.2, Apache-2.0, approved, #11921 -maven/mavencentral/org.springframework.boot/spring-boot-actuator/3.2.2, Apache-2.0, approved, #11918 -maven/mavencentral/org.springframework.boot/spring-boot-autoconfigure/3.2.2, Apache-2.0, approved, #11751 -maven/mavencentral/org.springframework.boot/spring-boot-starter-actuator/3.2.2, Apache-2.0, approved, #12918 -maven/mavencentral/org.springframework.boot/spring-boot-starter-aop/3.2.2, Apache-2.0, approved, #11928 -maven/mavencentral/org.springframework.boot/spring-boot-starter-data-jpa/3.2.2, Apache-2.0, approved, #11926 -maven/mavencentral/org.springframework.boot/spring-boot-starter-jdbc/3.2.2, Apache-2.0, approved, #11878 -maven/mavencentral/org.springframework.boot/spring-boot-starter-jetty/3.2.2, Apache-2.0, approved, #13220 -maven/mavencentral/org.springframework.boot/spring-boot-starter-json/3.2.2, Apache-2.0, approved, #11894 -maven/mavencentral/org.springframework.boot/spring-boot-starter-logging/3.2.2, Apache-2.0, approved, #11890 -maven/mavencentral/org.springframework.boot/spring-boot-starter-oauth2-resource-server/3.2.2, Apache-2.0, approved, #11931 -maven/mavencentral/org.springframework.boot/spring-boot-starter-validation/3.2.2, Apache-2.0, approved, #12921 -maven/mavencentral/org.springframework.boot/spring-boot-starter-web/3.2.2, Apache-2.0, approved, #11916 -maven/mavencentral/org.springframework.boot/spring-boot-starter/3.2.2, Apache-2.0, approved, #11935 -maven/mavencentral/org.springframework.boot/spring-boot/3.2.2, Apache-2.0, approved, #11752 -maven/mavencentral/org.springframework.data/spring-data-commons/3.2.2, Apache-2.0, approved, #11917 -maven/mavencentral/org.springframework.data/spring-data-jpa/3.2.2, Apache-2.0, approved, #11882 -maven/mavencentral/org.springframework.security/spring-security-config/6.2.1, Apache-2.0, approved, #11896 -maven/mavencentral/org.springframework.security/spring-security-core/6.2.1, Apache-2.0, approved, #11904 -maven/mavencentral/org.springframework.security/spring-security-crypto/6.2.1, Apache-2.0 AND ISC, approved, #11908 -maven/mavencentral/org.springframework.security/spring-security-oauth2-core/6.2.1, Apache-2.0, approved, #11925 -maven/mavencentral/org.springframework.security/spring-security-oauth2-jose/6.2.1, Apache-2.0, approved, #11893 -maven/mavencentral/org.springframework.security/spring-security-oauth2-resource-server/6.2.1, Apache-2.0, approved, #11920 -maven/mavencentral/org.springframework.security/spring-security-web/6.2.1, Apache-2.0, approved, #11911 -maven/mavencentral/org.springframework/spring-aop/6.1.3, Apache-2.0, approved, #11755 -maven/mavencentral/org.springframework/spring-aspects/6.1.3, Apache-2.0, approved, #11905 -maven/mavencentral/org.springframework/spring-beans/6.1.3, Apache-2.0, approved, #11754 -maven/mavencentral/org.springframework/spring-context/6.1.3, Apache-2.0, approved, #11753 -maven/mavencentral/org.springframework/spring-core/6.1.3, Apache-2.0 AND BSD-3-Clause, approved, #11750 -maven/mavencentral/org.springframework/spring-expression/6.1.3, Apache-2.0, approved, #11747 -maven/mavencentral/org.springframework/spring-jcl/6.1.3, Apache-2.0, approved, #11749 -maven/mavencentral/org.springframework/spring-jdbc/6.1.3, Apache-2.0, approved, #11897 -maven/mavencentral/org.springframework/spring-orm/6.1.3, Apache-2.0, approved, #11924 -maven/mavencentral/org.springframework/spring-tx/6.1.3, Apache-2.0, approved, #11901 -maven/mavencentral/org.springframework/spring-web/6.1.3, Apache-2.0, approved, #11748 -maven/mavencentral/org.springframework/spring-webmvc/6.1.3, Apache-2.0, approved, #11879 +maven/mavencentral/org.springframework.boot/spring-boot-actuator-autoconfigure/3.2.3, Apache-2.0, approved, #11921 +maven/mavencentral/org.springframework.boot/spring-boot-actuator/3.2.3, Apache-2.0, approved, #11918 +maven/mavencentral/org.springframework.boot/spring-boot-autoconfigure/3.2.3, Apache-2.0, approved, #11751 +maven/mavencentral/org.springframework.boot/spring-boot-starter-actuator/3.2.3, Apache-2.0, approved, #12918 +maven/mavencentral/org.springframework.boot/spring-boot-starter-aop/3.2.3, Apache-2.0, approved, #11928 +maven/mavencentral/org.springframework.boot/spring-boot-starter-data-jpa/3.2.3, Apache-2.0, approved, #11926 +maven/mavencentral/org.springframework.boot/spring-boot-starter-jdbc/3.2.3, Apache-2.0, approved, #11878 +maven/mavencentral/org.springframework.boot/spring-boot-starter-jetty/3.2.3, Apache-2.0, approved, #13220 +maven/mavencentral/org.springframework.boot/spring-boot-starter-json/3.2.3, Apache-2.0, approved, #11894 +maven/mavencentral/org.springframework.boot/spring-boot-starter-logging/3.2.3, Apache-2.0, approved, #11890 +maven/mavencentral/org.springframework.boot/spring-boot-starter-oauth2-resource-server/3.2.3, Apache-2.0, approved, #11931 +maven/mavencentral/org.springframework.boot/spring-boot-starter-validation/3.2.3, Apache-2.0, approved, #12921 +maven/mavencentral/org.springframework.boot/spring-boot-starter-web/3.2.3, Apache-2.0, approved, #11916 +maven/mavencentral/org.springframework.boot/spring-boot-starter/3.2.3, Apache-2.0, approved, #11935 +maven/mavencentral/org.springframework.boot/spring-boot/3.2.3, Apache-2.0, approved, #11752 +maven/mavencentral/org.springframework.data/spring-data-commons/3.2.3, Apache-2.0, approved, #11917 +maven/mavencentral/org.springframework.data/spring-data-jpa/3.2.3, Apache-2.0, approved, #11882 +maven/mavencentral/org.springframework.security/spring-security-config/6.2.2, Apache-2.0, approved, #11896 +maven/mavencentral/org.springframework.security/spring-security-core/6.2.2, Apache-2.0, approved, #11904 +maven/mavencentral/org.springframework.security/spring-security-crypto/6.2.2, Apache-2.0 AND ISC, approved, #11908 +maven/mavencentral/org.springframework.security/spring-security-oauth2-core/6.2.2, Apache-2.0, approved, #11925 +maven/mavencentral/org.springframework.security/spring-security-oauth2-jose/6.2.2, Apache-2.0, approved, #11893 +maven/mavencentral/org.springframework.security/spring-security-oauth2-resource-server/6.2.2, Apache-2.0, approved, #11920 +maven/mavencentral/org.springframework.security/spring-security-web/6.2.2, Apache-2.0, approved, #11911 +maven/mavencentral/org.springframework/spring-aop/6.1.4, Apache-2.0, approved, #11755 +maven/mavencentral/org.springframework/spring-aspects/6.1.4, Apache-2.0, approved, #11905 +maven/mavencentral/org.springframework/spring-beans/6.1.4, Apache-2.0, approved, #11754 +maven/mavencentral/org.springframework/spring-context/6.1.4, Apache-2.0, approved, #11753 +maven/mavencentral/org.springframework/spring-core/6.1.4, Apache-2.0 AND BSD-3-Clause, approved, #11750 +maven/mavencentral/org.springframework/spring-expression/6.1.4, Apache-2.0, approved, #11747 +maven/mavencentral/org.springframework/spring-jcl/6.1.4, Apache-2.0, approved, #11749 +maven/mavencentral/org.springframework/spring-jdbc/6.1.4, Apache-2.0, approved, #11897 +maven/mavencentral/org.springframework/spring-orm/6.1.4, Apache-2.0, approved, #11924 +maven/mavencentral/org.springframework/spring-tx/6.1.4, Apache-2.0, approved, #11901 +maven/mavencentral/org.springframework/spring-web/6.1.4, Apache-2.0, approved, #11748 +maven/mavencentral/org.springframework/spring-webmvc/6.1.4, Apache-2.0, approved, #11879 maven/mavencentral/org.webjars/swagger-ui/4.15.5, Apache-2.0 AND MIT, approved, #5921 maven/mavencentral/org.webjars/webjars-locator-core/0.55, MIT, approved, clearlydefined maven/mavencentral/org.yaml/snakeyaml/2.0, Apache-2.0 AND (Apache-2.0 OR BSD-3-Clause OR EPL-1.0 OR GPL-2.0-or-later OR LGPL-2.1-or-later), approved, #7275 diff --git a/pom.xml b/pom.xml index 43e08140..2e9ac1dd 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ org.springframework.boot spring-boot-starter-parent - 3.2.2 + 3.2.3 @@ -66,7 +66,7 @@ - 3.2.2 + 3.2.3 1.6.14 1.18.24 1.5.20 @@ -91,7 +91,7 @@ 1.5.3.Final 0.2.0 - 42.5.1 + 42.7.2 2.2.220 4.19.1 From 5422fe4433cf9b540be35f9c2af7e348a804f673 Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Wed, 28 Feb 2024 13:04:22 +0100 Subject: [PATCH 14/26] Prepare helm 0.4.9-RC2 --- charts/registry/Chart.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml index 4915615d..71e9c22a 100644 --- a/charts/registry/Chart.yaml +++ b/charts/registry/Chart.yaml @@ -26,8 +26,8 @@ sources: - https://github.com/eclipse-tractusx/sldt-digital-twin-registry type: application -version: 0.4.9-RC1 -appVersion: 0.4.1-RC1 +version: 0.4.9-RC2 +appVersion: 0.4.1-RC2 dependencies: - repository: https://charts.bitnami.com/bitnami From 5fad920ac6a4feba8515f637b8b635093cfe6c5c Mon Sep 17 00:00:00 2001 From: Tunahan Cicek Date: Wed, 28 Feb 2024 13:44:25 +0100 Subject: [PATCH 15/26] Migrate diagrams in mermaid --- docs/README.md | 68 ++++++++++++++++----- docs/img/Architecture_dDTR.PNG | Bin 39417 -> 0 bytes docs/img/GET_Endpoint_simplified.PNG | Bin 50192 -> 0 bytes docs/img/Model_with_impotant_fields.PNG | Bin 17612 -> 0 bytes docs/img/POST_Register_Twin_simplified.PNG | Bin 18410 -> 0 bytes docs/img/image005.png | Bin 173587 -> 0 bytes 6 files changed, 53 insertions(+), 15 deletions(-) delete mode 100644 docs/img/Architecture_dDTR.PNG delete mode 100644 docs/img/GET_Endpoint_simplified.PNG delete mode 100644 docs/img/Model_with_impotant_fields.PNG delete mode 100644 docs/img/POST_Register_Twin_simplified.PNG delete mode 100644 docs/img/image005.png diff --git a/docs/README.md b/docs/README.md index 65a04df7..2b4353d1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -27,7 +27,7 @@ A consumer is searching for an endpoint for a Digital Twin. For this he uses the Because now the DTR is deployed decentralized on each Data Provider side. There are some new services to help to find twins. The whole search and the embedding of the now decentralized Digital Twin is shown below: -### decentralized Digital Twin Registry environment +### Architectural Overview- Decentralized Digital Twin Registry environment ```mermaid graph TD subgraph Consumer_Environment [Consumer Environment] @@ -50,15 +50,33 @@ graph TD Provider_EDC --> Central_Environment ``` -![](img/decentralEnviroment.PNG) - -### Architectural Overview -![](img/Architecture_dDTR.PNG) - ## Asset Administration Shell Domain Model The Asset Administration Shell Registry is an address book for Asset Administration Shell Descriptors. The diagram below, shows the domain model of the Asset Administration Shell Registry (AAS Registry).Only the main fields are shown. +```mermaid +classDiagram + AssetAdministrationShellDescriptor -- SubmodelDescriptor + AssetAdministrationShellDescriptor -- SpecificAssetId + SubmodelDescriptor -- Endpoint + Endpoint -- ProtocolInformation + + class AssetAdministrationShellDescriptor{ + +String id + } -![](img/Model_with_impotant_fields.PNG) + class SubmodelDescriptor{ + +String id + } + class Endpoint{ + -String id + } + class ProtocolInformation{ + +String href + } + class SpecificAssetId{ + +String name + +String value + } +``` The following table shows the synonyms for each of the domain objects above. @@ -230,7 +248,12 @@ To be able to register a DigitalTwin the following prerequisites must be met. #### Register Twins (simplified without token management by IDM) -![](img/POST_Register_Twin_simplified.PNG) +```mermaid +sequenceDiagram + Client->>+Decentralized digital Twin registry: POST /api/v3.0/shell-descriptors + Decentralized digital Twin registry->>+Client: 200 Ok Response success + Note left of Client: Registers the AAS Descriptor by providing
- assetIds to make discovery possible (e.g. VIN)
- Submodel Descriptor Endpoint +``` ### Data Provider @@ -351,9 +374,28 @@ At last both EDCs do the final negotiation and the consumer EDC receives the edr #### Search for Twins (simplified) - -![](img/GET_Endpoint_simplified.PNG) - +```mermaid +sequenceDiagram + participant Service + participant ConsumerEDC as Consumer EDC + participant ProviderEDC as Provider EDC + participant DecentralDigitalTwinRegistry as Decentral Digital Twin Registry + + Service->>+ConsumerEDC: GET EDR Token + ConsumerEDC->+ProviderEDC: EDC Negotiations + ConsumerEDC-->>-Service: EDR Token + Service->>+ProviderEDC: GET lookup/shells/assetIds + ProviderEDC->>+DecentralDigitalTwinRegistry: GET lookup/shells/assetIds + DecentralDigitalTwinRegistry-->>-ProviderEDC: AssetAdministrationShellIds + ProviderEDC-->>-Service: AssetAdministrationShellIds + Service->>+ProviderEDC: GET /shell-descriptors/{aasIdentifier} + ProviderEDC->>+DecentralDigitalTwinRegistry: GET /shell-descriptors/{aasIdentifier} + DecentralDigitalTwinRegistry-->>-ProviderEDC: AssetAdministrationShellDescriptor + ProviderEDC-->>-Service: AssetAdministrationShellDescriptor + Service->>Service: Extract Endpoint + Service->>ProviderEDC: Get Endpoint (from Submodel Descriptor) + ProviderEDC-->>-Service: Endpoint +``` ## 5 Deployment-view For Deployment needed: @@ -444,10 +486,6 @@ Depending on being a Data Provider or a Data Consumer there are different tokens ### Authentication on behalf of a user The AAS Registry can be accessed on behalf of a user. The token has to be obtained via the OpenID Connect flow. The AAS Registry will validate these tokens. - -#### Postman configuration -![](img/image005.png) - *Support contact* tractusx-dev@eclipse.org ### Access control to Digital Twins Based on the BPN (Business Partner Number)/ TenantId diff --git a/docs/img/Architecture_dDTR.PNG b/docs/img/Architecture_dDTR.PNG deleted file mode 100644 index 46a1a929bd822bb78fd2af3c42de8289ee14bf28..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 39417 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 P5s{9L`<#E%_15Ivd}6Ax0&wN06U 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 diff --git a/docs/img/GET_Endpoint_simplified.PNG b/docs/img/GET_Endpoint_simplified.PNG deleted file mode 100644 index 9ba3ffe57d5a2451a09c57e69c1f46d05f189eb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 50192 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%0A6kP;;~KkY*e(MZ=KXDhrG5um7aZ< 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

{!PK0h}CmaKnh-i{tsrH+Q5K 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 Q2;e_iNkxfbF}>IS3nkk_@c;k- From 2b99fafd62c913e0f318d70316ecd9213c7a1359 Mon Sep 17 00:00:00 2001 From: Rohan Krishnamurthy <99187454+RoKrish14@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:29:05 +0100 Subject: [PATCH 16/26] Create CodeQL.yml --- .github/workflows/CodeQL.yml | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 .github/workflows/CodeQL.yml diff --git a/.github/workflows/CodeQL.yml b/.github/workflows/CodeQL.yml new file mode 100644 index 00000000..d94cb7fa --- /dev/null +++ b/.github/workflows/CodeQL.yml @@ -0,0 +1,99 @@ +################################################################################ +# Copyright (c) 2024 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License, Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0. +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# SPDX-License-Identifier: Apache-2.0 +################################################################################ + +name: "CodeQL" + +on: + push: + branches: + - main + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: '17' + distribution: 'temurin' + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + queries: +security-extended,security-and-quality + + - name: Cache maven packages + uses: actions/cache@v3 + with: + path: ~/.m2 + key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-m2 + + - name: Build with Maven + run: mvn clean package -B -Dmaven.test.skip + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + #- name: Autobuild + # uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" From 4f606558474307a9f1ec4228a7f934bec2406295 Mon Sep 17 00:00:00 2001 From: Rohan Krishnamurthy <99187454+RoKrish14@users.noreply.github.com> Date: Mon, 4 Mar 2024 15:36:56 +0100 Subject: [PATCH 17/26] Rename CodeQL.yml to codeql.yml --- .github/workflows/{CodeQL.yml => codeql.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{CodeQL.yml => codeql.yml} (100%) diff --git a/.github/workflows/CodeQL.yml b/.github/workflows/codeql.yml similarity index 100% rename from .github/workflows/CodeQL.yml rename to .github/workflows/codeql.yml From 698b41bbe60681a365dfe98a7e0d5489be19dc00 Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Tue, 12 Mar 2024 16:10:45 +0100 Subject: [PATCH 18/26] post lookup api implementation --- .../AssetAdministrationShellApiDelegate.java | 16 +- .../registry/mapper/ShellMapper.java | 7 + .../registry/service/ShellService.java | 28 + .../static/aas-registry-openapi.yaml | 77 +++ .../AbstractAssetAdministrationShellApi.java | 1 + .../AssetAdministrationShellApiTest.java | 102 +++ .../tractusx/semantics/registry/TestUtil.java | 15 + .../tractusx-dtr-aas-3.1.0-collection.json | 609 ++++++++++++++++++ 8 files changed, 854 insertions(+), 1 deletion(-) create mode 100644 docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json 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 1a47a309..21a7f184 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 @@ -22,7 +22,6 @@ import java.util.Base64; import java.util.List; -import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -32,9 +31,11 @@ import org.eclipse.tractusx.semantics.aas.registry.api.ShellDescriptorsApiDelegate; import org.eclipse.tractusx.semantics.aas.registry.model.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.semantics.aas.registry.model.AssetKind; +import org.eclipse.tractusx.semantics.aas.registry.model.AssetLink; import org.eclipse.tractusx.semantics.aas.registry.model.GetAllAssetAdministrationShellIdsByAssetLink200Response; import org.eclipse.tractusx.semantics.aas.registry.model.GetAssetAdministrationShellDescriptorsResult; import org.eclipse.tractusx.semantics.aas.registry.model.GetSubmodelDescriptorsResult; +import org.eclipse.tractusx.semantics.aas.registry.model.SearchAllShellsByAssetLink200Response; import org.eclipse.tractusx.semantics.aas.registry.model.ServiceDescription; import org.eclipse.tractusx.semantics.aas.registry.model.SpecificAssetId; import org.eclipse.tractusx.semantics.aas.registry.model.SubmodelDescriptor; @@ -176,6 +177,19 @@ public ResponseEntity g return new ResponseEntity<>( result, HttpStatus.OK ); } + @Override + public ResponseEntity searchAllShellsByAssetLink( + Integer limit, String cursor, @RequestHeader String externalSubjectId,List assetIds) { + + if (assetIds == null || assetIds.isEmpty()) { + return new ResponseEntity<>(new SearchAllShellsByAssetLink200Response(), HttpStatus.OK); + } + + final var result = shellService.findExternalShellIdsByAssetLinkByExactMatch( + shellMapper.fromAssetLinkApiDto(assetIds), limit, cursor,getExternalSubjectIdOrEmpty(externalSubjectId)); + return new ResponseEntity<>( result, HttpStatus.OK ); + } + private SpecificAssetId decodeSAID(byte[] encodedId){ ObjectMapper mapper = new ObjectMapper(); mapper.setSerializationInclusion( JsonInclude.Include.NON_NULL); diff --git a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/mapper/ShellMapper.java b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/mapper/ShellMapper.java index 2a12695d..0e92e747 100644 --- a/backend/src/main/java/org/eclipse/tractusx/semantics/registry/mapper/ShellMapper.java +++ b/backend/src/main/java/org/eclipse/tractusx/semantics/registry/mapper/ShellMapper.java @@ -61,6 +61,13 @@ public interface ShellMapper { }) ShellIdentifier fromApiDto(SpecificAssetId apiDto); + @Mappings({ + @Mapping(target = "key", source = "name"), + }) + ShellIdentifier fromApiDto(AssetLink apiDto); + + Set fromAssetLinkApiDto(List apiDto); + ShellIdentifierSupplemSemanticReference maptoShellIdentifierSupplemSemanticReference ( Reference supplementalSemanticId ); ShellIdentifierSemanticReference maptoShellIdentifierSemanticReference ( Reference semanticId ); 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 4a7a86df..0b8c518e 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 @@ -42,6 +42,7 @@ import org.eclipse.tractusx.semantics.RegistryProperties; import org.eclipse.tractusx.semantics.aas.registry.model.GetAllAssetAdministrationShellIdsByAssetLink200Response; import org.eclipse.tractusx.semantics.aas.registry.model.PagedResultPagingMetadata; +import org.eclipse.tractusx.semantics.aas.registry.model.SearchAllShellsByAssetLink200Response; import org.eclipse.tractusx.semantics.accesscontrol.api.exception.DenyAccessException; import org.eclipse.tractusx.semantics.accesscontrol.api.model.SpecificAssetId; import org.eclipse.tractusx.semantics.registry.dto.BatchResultDto; @@ -296,6 +297,33 @@ public GetAllAssetAdministrationShellIdsByAssetLink200Response findExternalShell } } + @Transactional( readOnly = true ) + public SearchAllShellsByAssetLink200Response findExternalShellIdsByAssetLinkByExactMatch( Set shellIdentifiers, + Integer pageSize, String cursor, String externalSubjectId ) { + + pageSize = getPageSize( pageSize ); + final String cursorValue = getCursorDecoded( cursor ).orElse( DEFAULT_EXTERNAL_ID ); + try { + final List visibleAssetIds; + if ( shellAccessHandler.supportsGranularAccessControl() ) { + visibleAssetIds = fetchAPageOfAasIdsUsingGranularAccessControl( shellIdentifiers, externalSubjectId, cursorValue, pageSize ); + } else { + visibleAssetIds = fetchAPageOfAasIdsUsingLegacyAccessControl( shellIdentifiers, externalSubjectId, cursorValue, pageSize ); + } + + final var assetIdList = visibleAssetIds.stream().limit( pageSize ).toList(); + final String nextCursor = getCursorEncoded( visibleAssetIds, assetIdList ); + final var response = new SearchAllShellsByAssetLink200Response(); + response.setResult( assetIdList ); + response.setPagingMetadata( new PagedResultPagingMetadata().cursor( nextCursor ) ); + return response; + } catch ( DenyAccessException e ) { + final var response = new SearchAllShellsByAssetLink200Response(); + response.setResult( Collections.emptyList() ); + return response; + } + } + private List fetchAPageOfAasIdsUsingLegacyAccessControl( Set shellIdentifiers, String externalSubjectId, String cursorValue, int pageSize ) { final var fetchSize = pageSize + 1; diff --git a/backend/src/main/resources/static/aas-registry-openapi.yaml b/backend/src/main/resources/static/aas-registry-openapi.yaml index 6db40fd0..56befe9c 100644 --- a/backend/src/main/resources/static/aas-registry-openapi.yaml +++ b/backend/src/main/resources/static/aas-registry-openapi.yaml @@ -744,6 +744,59 @@ paths: $ref: '#/components/examples/lookup-shells-by-aas-identifier-response' x-semanticIds: - https://admin-shell.io/aas/API/GetAllAssetAdministrationShellIdsByAssetLink/1/0/RC02 + /lookup/shellsByAssetLink: + post: + tags: + - Registry and Discovery Interface + summary: Returns a list of Asset Administration Shell ids linked to specific Asset identifiers + operationId: SearchAllShellsByAssetLink + x-semanticIds: + - https://admin-shell.io/aas/API/SearchAllAssetAdministrationShellIdsByAssetLink/3/1 + parameters: + - name: limit + in: query + description: The maximum number of elements in the response array + required: false + schema: + type: integer + minimum: 1 + - name: cursor + in: query + description: A server-generated identifier retrieved from pagingMetadata that specifies from which position the result listing should continue + required: false + schema: + type: string + - $ref: '#/components/parameters/ExternalSubjectIdHeader' + requestBody: + description: Asset identifier key-value-pairs + content: + application/json: + schema: + type: array + maxItems: 10000 + items: + $ref: '#/components/schemas/AssetLink' + examples: + complete: + $ref: '#/components/examples/lookup-shells-by-aas-identifier-post' + responses: + '200': + description: Requested Asset Administration Shell ids + content: + application/json: + schema: + allOf: + - $ref: '#/components/schemas/PagedResult' + - type: object + properties: + result: + type: array + items: + type: string + '400': + $ref: 'https://api.swaggerhub.com/domains/Plattform_i40/Part2-API-Schemas/V3.1.0#/components/responses/bad-request' + default: + $ref: 'https://api.swaggerhub.com/domains/Plattform_i40/Part2-API-Schemas/V3.1.0#/components/responses/default' /lookup/shells/{aasIdentifier}: get: tags: @@ -1401,6 +1454,18 @@ components: maxItems: 10000 items: $ref: '#/components/schemas/ProtocolInformation_securityAttributes' + AssetLink: + properties: + name: + type: string + minLength: 1 + maxLength: 64 + pattern: "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" + value: + type: string + minLength: 1 + maxLength: 2000 + pattern: "^([\\t\\n\\r -\ud7ff\ue000-\ufffd]|\\ud800[\\udc00-\\udfff]|[\\ud801-\\udbfe][\\udc00-\\udfff]|\\udbff[\\udc00-\\udfff])*$" SpecificAssetId: type: object allOf: @@ -1653,6 +1718,18 @@ components: description: Incoming external_subject_id/bpn # format: byte examples: + lookup-shells-by-aas-identifier-post: + value: + [ + { + "name": "globalAssetId", + "value": "urn:uuid:882fc530-b69b-4707-95f6-5dbc5e9baaa8" + }, + { + "name": "PartInstanceID", + "value": "24975539203421" + } + ] lookup-shells-by-aas-identifier-query: value: [ 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 5d62b106..928a8a81 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 @@ -49,6 +49,7 @@ public abstract class AbstractAssetAdministrationShellApi { protected static final String SHELL_BASE_PATH = "/api/v3.0/shell-descriptors"; protected static final String SINGLE_SHELL_BASE_PATH = "/api/v3.0/shell-descriptors/{aasIdentifier}"; protected static final String LOOKUP_SHELL_BASE_PATH = "/api/v3.0/lookup/shells"; + protected static final String LOOKUP_SHELL_BASE_PATH_POST = "/api/v3.0/lookup/shellsByAssetLink"; protected static final String SINGLE_LOOKUP_SHELL_BASE_PATH = "/api/v3.0/lookup/shells/{aasIdentifier}"; protected static final String SUB_MODEL_BASE_PATH = "/api/v3.0/shell-descriptors/{aasIdentifier}/submodel-descriptors"; protected static final String SINGLE_SUB_MODEL_BASE_PATH = "/api/v3.0/shell-descriptors/{aasIdentifier}/submodel-descriptors/{submodelIdentifier}"; 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 b8e4d582..36ab98cf 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 @@ -24,12 +24,14 @@ 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; import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.tractusx.semantics.aas.registry.model.AssetAdministrationShellDescriptor; +import org.eclipse.tractusx.semantics.aas.registry.model.AssetLink; import org.eclipse.tractusx.semantics.aas.registry.model.LangStringTextType; import org.eclipse.tractusx.semantics.aas.registry.model.SpecificAssetId; import org.eclipse.tractusx.semantics.aas.registry.model.SubmodelDescriptor; @@ -774,6 +776,73 @@ public void testFindExternalShellIdsBySpecificAssetIdsExpectSuccess() throws Exc .andExpect( jsonPath( "$.result", hasSize( 0 ) ) ); } + + @Test + public void testFindExternalShellIdsByAssetLinkExpectSuccess() throws Exception { + + AssetAdministrationShellDescriptor shellPayload1 = TestUtil.createCompleteAasDescriptor(); + shellPayload1.setId( UUID.randomUUID().toString() ); + performShellCreateRequest( mapper.writeValueAsString( shellPayload1 ) ); + + AssetAdministrationShellDescriptor shellPayload2 = TestUtil.createCompleteAasDescriptor(); + shellPayload2.setId( UUID.randomUUID().toString() ); + performShellCreateRequest( mapper.writeValueAsString( shellPayload2 ) ); + + AssetLink assetLink1 = TestUtil.createAssetLink(); + List list1 = new ArrayList<>(); + list1.add( assetLink1 ); + + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH_POST ) + .queryParam( "limit", "1" ) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) + .accept( MediaType.APPLICATION_JSON ) + .contentType( MediaType.APPLICATION_JSON ) + .content( mapper.writeValueAsBytes(list1) ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "$.paging_metadata.cursor" ).exists() ); + + // Test first shell match with single assetLink + + AssetLink assetLink2 = TestUtil.createAssetLink( "identifier99KeyExample", "identifier99ValueExample" ); + List list2 = new ArrayList<>(); + list2.add( assetLink2 ); + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH_POST ) + .queryParam( "limit", "10" ) + .accept( MediaType.APPLICATION_JSON ) + .contentType( MediaType.APPLICATION_JSON ) + .content( mapper.writeValueAsBytes(list2) ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "$.paging_metadata.cursor" ).doesNotExist() ); + + // Test first and second shell match with common assetLink + + AssetLink assetLink3 = TestUtil.createAssetLink( "commonAssetIdKey", "commonAssetIdValue" ); + List list3 = new ArrayList<>(); + list3.add( assetLink3 ); + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH_POST ) + .queryParam( "limit", "10" ) + .accept( MediaType.APPLICATION_JSON ) + .contentType( MediaType.APPLICATION_JSON ) + .content( mapper.writeValueAsBytes(list3) ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "$.result", hasSize( 0 ) ) ); + } + @Test public void testFindExternalShellIdByGlobalAssetIdExpectSuccess() throws Exception { @@ -805,6 +874,39 @@ public void testFindExternalShellIdByGlobalAssetIdExpectSuccess() throws Excepti .andExpect( jsonPath( "$.result", contains( shellPayload.getId() ) ) ); } + @Test + public void testFindExternalShellIdByGlobalAssetIdAssetLinkExpectSuccess() throws Exception { + + String globalAssetId = UUID.randomUUID().toString(); + + AssetAdministrationShellDescriptor shellPayload = TestUtil.createCompleteAasDescriptor(); + shellPayload.setId( UUID.randomUUID().toString() ); + shellPayload.setGlobalAssetId( globalAssetId ); + String payload = mapper.writeValueAsString( shellPayload ); + performShellCreateRequest(payload ); + + // for lookup global asset id is handled as AssetLink + AssetLink SAGlobal = TestUtil.createAssetLink("globalAssetId",globalAssetId); + + List list = new ArrayList<>(); + list.add( SAGlobal ); + + mvc.perform( + MockMvcRequestBuilders + .post( LOOKUP_SHELL_BASE_PATH_POST ) + .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) + .accept( MediaType.APPLICATION_JSON ) + .contentType( MediaType.APPLICATION_JSON ) + .content( mapper.writeValueAsBytes(list) ) + .with( jwtTokenFactory.allRoles() ) + ) + .andDo( MockMvcResultHandlers.print() ) + .andExpect( status().isOk() ) + .andExpect( jsonPath( "$.result", hasSize( 1 ) ) ) + // ensure that only three results match + .andExpect( jsonPath( "$.result", contains( shellPayload.getId() ) ) ); + } + @Test public void testFindExternalShellIdsWithoutProvidingQueryParametersExpectEmptyResult() throws Exception { // prepare the data set diff --git a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/TestUtil.java b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/TestUtil.java index b49d5675..ab807f64 100644 --- a/backend/src/test/java/org/eclipse/tractusx/semantics/registry/TestUtil.java +++ b/backend/src/test/java/org/eclipse/tractusx/semantics/registry/TestUtil.java @@ -33,6 +33,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.eclipse.tractusx.semantics.aas.registry.model.AssetAdministrationShellDescriptor; import org.eclipse.tractusx.semantics.aas.registry.model.AssetKind; +import org.eclipse.tractusx.semantics.aas.registry.model.AssetLink; import org.eclipse.tractusx.semantics.aas.registry.model.Endpoint; import org.eclipse.tractusx.semantics.aas.registry.model.Key; import org.eclipse.tractusx.semantics.aas.registry.model.KeyTypes; @@ -242,6 +243,20 @@ public static SpecificAssetId createSpecificAssetId() { return specificAssetId1; } + public static AssetLink createAssetLink() { + AssetLink assetLink = new AssetLink(); + assetLink.setName( "identifier1KeyExample" ); + assetLink.setValue( "identifier1ValueExample" ); + return assetLink; + } + + public static AssetLink createAssetLink(String name, String value) { + AssetLink assetLink = new AssetLink(); + assetLink.setName( name ); + assetLink.setValue( value ); + return assetLink; + } + public static SpecificAssetId createSpecificAssetId( String name, String value, List externalSubjectIds ) { SpecificAssetId specificAssetId1 = new SpecificAssetId(); specificAssetId1.setName( name ); diff --git a/docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json b/docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json new file mode 100644 index 00000000..9fee6b35 --- /dev/null +++ b/docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json @@ -0,0 +1,609 @@ +{ + "info": { + "_postman_id": "a55b763d-91cc-43e4-a3b9-615edab5dc52", + "name": "tractusx-dtr-aas-3.0.0 Copy", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "28393378" + }, + "item": [ + { + "name": "Asset Administration Shell Registry", + "item": [ + { + "name": "Create Shell", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"idShort\": \"idShortExample\",\n \"id\": \"e1eba3d7-91f0-4dac-a730-eaa1d35e035c-2\", \n \"description\": [\n {\n \"language\": \"de\",\n \"text\": \"example text\"\n }\n ],\n \"displayName\": [\n {\n \"language\": \"de\",\n \"text\": \"this is an example description1\"\n }\n ],\n \"specificAssetIds\": [\n {\n \"name\": \"manufacturePartId\",\n \"value\": \"1234\"\n },\n {\n \"name\": \"oen\",\n \"value\": \"1239485\"\n },\n {\n \"name\": \"serialnr\",\n \"value\": \"12345\",\n \"externalSubjectId\":{\n \"type\": \"ExternalReference\",\n \"keys\": [\n {\n \"type\": \"Property\",\n \"value\": \"CONSUMER_BPN\"\n }\n ]\n }\n }\n ],\n \"submodelDescriptors\": [\n {\n \"endpoints\": [\n {\n \"interface\": \"interfaceNameExample\",\n \"protocolInformation\": {\n \"href\": \"endpointAddressExample\",\n \"endpointProtocol\": \"endpointProtocolExample\",\n \"endpointProtocolVersion\": [\n \"e\"\n ],\n \"subprotocol\": \"5hg\",\n \"subprotocolBody\":\"\",\n \"subprotocolBodyEncoding\": \"subprotocolBodyExample\",\n \"securityAttributes\": [\n {\n \"type\": \"NONE\",\n \"key\": \"sec\",\n \"value\": \"1\"\n }\n ]\n }\n }\n ],\n \"idShort\": \"idShortExample\",\n \"id\": \"cd47615b-daf3-4036-8670-d2f89349d388-2\",\n \"semanticId\": {\n \"type\": \"ExternalReference\",\n \"keys\": [\n {\n \"type\": \"Submodel\",\n \"value\": \"urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization\"\n }\n ]\n },\n \"description\": [\n {\n \"language\": \"de\",\n \"text\": \"hello text\"\n },\n {\n \"language\": \"en\",\n \"text\": \"hello s\"\n }\n ]\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors" + ] + } + }, + "response": [] + }, + { + "name": "Get All Shells", + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Edc-Bpn", + "value": "CONSUMER_BPN", + "description": "BPN (This is normally send via EDC)", + "type": "default" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors" + ], + "query": [ + { + "key": "limit", + "value": "100", + "disabled": true + }, + { + "key": "cursor", + "value": null, + "disabled": true + }, + { + "key": "", + "value": null, + "disabled": true + } + ] + } + }, + "response": [] + }, + { + "name": "Get Shell by Id", + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Edc-Bpn", + "value": "", + "description": "BPN (This is normally send via EDC)", + "type": "default" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Update Shell", + "request": { + "auth": { + "type": "noauth" + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"idShort\": \"idShortExample\",\n \"id\": \"e1eba3d7-91f0-4dac-a730-eaa1d35e035c-2\",\n \"description\": [\n {\n \"language\": \"de\",\n \"text\": \"example text\"\n }\n ],\n \"displayName\": [\n {\n \"language\": \"de\",\n \"text\": \"this is an example description1\"\n }\n ],\n \"endpoints\": [\n {\n \"interface\": \"interfaceNameExample\",\n \"protocolInformation\": {\n \"href\": \"endpointAddressExample\",\n \"endpointProtocol\": \"endpointProtocolExample\",\n \"endpointProtocolVersion\": [\n \"e\"\n ],\n \"subprotocol\": \"subprotocolExample\",\n \"subprotocolBody\": \"subprotocolBodyExample\",\n \"subprotocolBodyEncoding\": \"subprotocolBodyExample\",\n \"securityAttributes\": [\n {\n \"type\": \"NONE\"\n }\n ]\n }\n }\n ],\n \"specificAssetIds\": [\n {\n \"name\": \"manufacturePartId\",\n \"value\": \"12345\",\n \"externalSubjectId\":{\n \"type\": \"ExternalReference\",\n \"keys\": [\n {\n \"type\": \"Property\",\n \"value\": \"CONSUMER_BPN\"\n }\n ]\n }\n },\n {\n \"name\": \"oen\",\n \"value\": \"1239485\"\n },\n {\n \"name\": \"serialnr\",\n \"value\": \"1234\",\n \"externalSubjectId\":{\n \"type\": \"ExternalReference\",\n \"keys\": [\n {\n \"type\": \"Property\",\n \"value\": \"CONSUMER_BPN\"\n }\n ]\n }\n }\n ],\n \"submodelDescriptors\": [\n {\n \"endpoints\": [\n {\n \"interface\": \"interfaceNameExample\",\n \"protocolInformation\": {\n \"href\": \"endpointAddressExample\",\n \"endpointProtocol\": \"endpointProtocolExample\",\n \"endpointProtocolVersion\": [\n \"e\"\n ],\n \"subprotocol\": \"5hg\",\n \"subprotocolBody\":\"\",\n \"subprotocolBodyEncoding\": \"subprotocolBodyExample\",\n \"securityAttributes\": [\n {\n \"type\": \"NONE\"\n }\n ]\n }\n }\n ],\n \"idShort\": \"idShortExample\",\n \"id\": \"cd47615b-daf3-4036-8670-d2f89349d388-2\",\n \"semanticId\": {\n \"type\": \"ExternalReference\",\n \"keys\": [\n {\n \"type\": \"Submodel\",\n \"value\": \"urn:bamm:io.catenax.serial_part_typization:1.1.0#SerialPartTypization\"\n }\n ]\n },\n \"description\": [\n {\n \"language\": \"de\",\n \"text\": \"hello text\"\n },\n {\n \"language\": \"en\",\n \"text\": \"hello s\"\n }\n ]\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Delete Shell", + "request": { + "auth": { + "type": "noauth" + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Create new Submodel", + "request": { + "auth": { + "type": "noauth" + }, + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"endpoints\": [\n {\n \"interface\": \"interfaceNameExample\",\n \"protocolInformation\": {\n \"href\": \"endpointAddressExample\",\n \"endpointProtocol\": \"endpointProtocolExample\",\n \"endpointProtocolVersion\": [\n \"e\"\n ],\n \"subprotocol\": \"subprotocolExample\",\n \"subprotocolBody\": \"subprotocolBodyExample\",\n \"subprotocolBodyEncoding\": \"subprotocolBodyExample\",\n \"securityAttributes\": [\n {\n \"type\": \"NONE\"\n }\n ]\n }\n }\n ],\n \"idShort\": \"idShortExample\",\n \"id\": \"341f63de-728a-4b6d-9c72-c0d9ba4f8c7d\",\n \"semanticId\": {\n \"type\": \"ExternalReference\",\n \"keys\": [\n {\n \"type\": \"Submodel\",\n \"value\": \"semanticIdExample\"\n }\n ]\n },\n \"description\": [\n {\n \"language\": \"de\",\n \"text\": \"hello text\"\n },\n {\n \"language\": \"en\",\n \"text\": \"hello s\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier", + "submodel-descriptors" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Get Submodel by Id", + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [ + { + "key": "Edc-Bpn", + "value": "", + "description": "BPN (This is normally send via EDC)", + "type": "default" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier", + "submodel-descriptors" + ], + "query": [ + { + "key": "limit", + "value": null, + "disabled": true + }, + { + "key": "cursor", + "value": null, + "disabled": true + } + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Get specific Submodel by Id", + "request": { + "auth": { + "type": "noauth" + }, + "method": "GET", + "header": [], + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier", + "submodel-descriptors", + ":submodelIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + }, + { + "key": "submodelIdentifier", + "value": "Y2Q0NzYxNWItZGFmMy00MDM2LTg2NzAtZDJmODkzNDlkMzg4LTI=", + "description": "submodelIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Update specific Submodel by Id", + "request": { + "auth": { + "type": "noauth" + }, + "method": "PUT", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"endpoints\": [\n {\n \"interface\": \"interfaceNameExample\",\n \"protocolInformation\": {\n \"href\": \"endpointAddressExample\",\n \"endpointProtocol\": \"endpointProtocolExample\",\n \"endpointProtocolVersion\": [\n \"e\"\n ],\n \"subprotocol\": \"subprotocolExample\",\n \"subprotocolBody\": \"subprotocolBodyExample\",\n \"subprotocolBodyEncoding\": \"subprotocolBodyExample\",\n \"securityAttributes\": [\n {\n \"type\": \"NONE\"\n }\n ]\n }\n }\n ],\n \"idShort\": \"idShortExample\",\n \"id\": \"341f63de-728a-4b6d-9c72-c0d9ba4f8c7d\",\n \"semanticId\": {\n \"type\": \"ExternalReference\",\n \"keys\": [\n {\n \"type\": \"Submodel\",\n \"value\": \"semanticIdExample\"\n }\n ]\n },\n \"description\": [\n {\n \"language\": \"de\",\n \"text\": \"hello text\"\n },\n {\n \"language\": \"en\",\n \"text\": \"hello s\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier", + "submodel-descriptors", + ":submodelIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + }, + { + "key": "submodelIdentifier", + "value": "Y2Q0NzYxNWItZGFmMy00MDM2LTg2NzAtZDJmODkzNDlkMzg4LTI=", + "description": "submodelIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Delete specific Submodel by Id", + "request": { + "auth": { + "type": "noauth" + }, + "method": "DELETE", + "header": [], + "url": { + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "shell-descriptors", + ":aasIdentifier", + "submodel-descriptors", + ":submodelIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + }, + { + "key": "submodelIdentifier", + "value": "Y2Q0NzYxNWItZGFmMy00MDM2LTg2NzAtZDJmODkzNDlkMzg4LTI=", + "description": "submodelIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + } + ] + }, + { + "name": "Registry and Discovery", + "item": [ + { + "name": "Get list of ShellIds (exact match)", + "request": { + "method": "GET", + "header": [ + { + "key": "Edc-Bpn", + "value": "", + "description": "BPN (This is normally send via EDC)", + "type": "default" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v3.0/lookup/shells?assetIds=[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "lookup", + "shells" + ], + "query": [ + { + "key": "limit", + "value": null, + "disabled": true + }, + { + "key": "cursor", + "value": null, + "disabled": true + }, + { + "key": "assetIds", + "value": "[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]" + } + ] + } + }, + "response": [] + }, + { + "name": "Get SpecificAssetIds by aasIdentifier", + "request": { + "method": "GET", + "header": [ + { + "key": "Edc-Bpn", + "value": "", + "description": "BPN (This is normally send via EDC)", + "type": "default" + } + ], + "url": { + "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "lookup", + "shells", + ":aasIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Update SpecificAssetIds by aasIdentifier (overwrite existing List)", + "request": { + "method": "POST", + "header": [ + { + "key": "Edc-Bpn", + "value": "", + "description": "BPN (This is normally send via EDC)", + "type": "default" + } + ], + "body": { + "mode": "raw", + "raw": "[\n {\n \"name\": \"oen\",\n \"value\": \"1239485\"\n }\n ]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "lookup", + "shells", + ":aasIdentifier" + ], + "variable": [ + { + "key": "aasIdentifier", + "value": "ZTFlYmEzZDctOTFmMC00ZGFjLWE3MzAtZWFhMWQzNWUwMzVjLTI", + "description": "aasIdentifier as UTF8-BASE64-URL-encoded" + } + ] + } + }, + "response": [] + }, + { + "name": "Get list of ShellIds without encoding", + "request": { + "method": "POST", + "header": [ + { + "key": "Edc-Bpn", + "value": "TENANT_ONE", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "[\n {\n \"name\": \"manufacturePartId\",\n \"value\": \"1234\"\n },\n {\n \"name\": \"oen\",\n \"value\": \"1239485\"\n },\n {\n \"name\": \"serialnr\",\n \"value\": \"12345\"\n }\n] ", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseUrl}}/api/v3.0/lookup/shellsByAssetLink", + "host": [ + "{{baseUrl}}" + ], + "path": [ + "api", + "v3.0", + "lookup", + "shellsByAssetLink" + ], + "query": [ + { + "key": "limit", + "value": null, + "disabled": true + }, + { + "key": "cursor", + "value": null, + "disabled": true + } + ] + } + }, + "response": [] + } + ] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "baseUrl", + "value": "", + "type": "default" + } + ] +} \ No newline at end of file From 56e0b97eee97f293ea34223a0932d5ba793b3636 Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Tue, 12 Mar 2024 16:27:11 +0100 Subject: [PATCH 19/26] updated changelog.md file --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index becd6c93..c2bb6a50 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.4.1-RC3 +### Added +- Added API POST lookup/shellsByAssetLink to retrieve shell ids without base64 encryption. +- Added v3.1.0 postman collection for new API. +## fixed + ## 0.4.1-RC2 ### Added - Update Springboot to version 3.2.3 From e5da778762550138629543b46c085e7236309908 Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Mon, 18 Mar 2024 20:29:35 +0100 Subject: [PATCH 20/26] Changed API path from api/v3.0/ to api/v3/ --- CHANGELOG.md | 1 + .../static/access-control-openapi.yaml | 4 +- backend/loadtests/locust/locustfile.py | 6 +- .../static/aas-registry-openapi.yaml | 4 +- .../AbstractAssetAdministrationShellApi.java | 14 ++--- ...setAdministrationShellApiSecurityTest.java | 8 +-- .../AssetAdministrationShellApiTest.java | 2 +- ...setAdministrationShellApiSecurityTest.java | 50 ++++++++--------- .../tests/test-script-configmap.yaml | 8 +-- docs/README.md | 10 ++-- .../tractusx-dtr-aas-3.0.0-collection.json | 50 ++++++++--------- .../tractusx-dtr-aas-3.1.0-collection.json | 56 +++++++++---------- ...s-rule-management-api-v1.0-collection.json | 16 +++--- e2e-tests/aas-registry/test_api.tavern.yaml | 12 ++-- 14 files changed, 121 insertions(+), 120 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2bb6a50..68a553d6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added API POST lookup/shellsByAssetLink to retrieve shell ids without base64 encryption. - Added v3.1.0 postman collection for new API. ## fixed +- Changed API path from api/v3.0/ to api/v3/ ## 0.4.1-RC2 ### Added diff --git a/access-control-service-sql-impl/src/main/resources/static/access-control-openapi.yaml b/access-control-service-sql-impl/src/main/resources/static/access-control-openapi.yaml index faf7085f..bf7f8498 100644 --- a/access-control-service-sql-impl/src/main/resources/static/access-control-openapi.yaml +++ b/access-control-service-sql-impl/src/main/resources/static/access-control-openapi.yaml @@ -50,9 +50,9 @@ servers: - "443" - "4243" version_prefix: - default: v3.0 + default: v3 enum: - - v3.0 + - v3 context_root: description: Context root for the API default: "" diff --git a/backend/loadtests/locust/locustfile.py b/backend/loadtests/locust/locustfile.py index 203803af..2745368c 100644 --- a/backend/loadtests/locust/locustfile.py +++ b/backend/loadtests/locust/locustfile.py @@ -37,7 +37,7 @@ class AasRegistryTask(HttpUser): def createAndQueryAasDescriptor(self): shell = generate_shell() headers = { 'Content-Type' : 'application/json', 'Edc-Bpn' : 'BPN_COMPANY_1'} - with self.client.post("/api/v3.0/shell-descriptors", data=json.dumps(shell), headers= headers, catch_response=True) as response: + with self.client.post("/api/v3/shell-descriptors", data=json.dumps(shell), headers= headers, catch_response=True) as response: if response.status_code != 201: response.failure(f"Expected 201 but status code was {response.status_code}") raise RescheduleTask() @@ -45,14 +45,14 @@ def createAndQueryAasDescriptor(self): shell_id = shell['id'] shell_id_encoded = str(base64.urlsafe_b64encode(shell_id.encode("utf-8")), "utf-8") - with self.client.get(f"/api/v3.0/shell-descriptors/{shell_id_encoded}", name = "/api/v3.0/shell-descriptors/{id}", headers= headers, catch_response=True) as response: + with self.client.get(f"/api/v3/shell-descriptors/{shell_id_encoded}", name = "/api/v3/shell-descriptors/{id}", headers= headers, catch_response=True) as response: if response.status_code != 200: response.failure(f"Expected 200 but status code was {response.status_code}") raise RescheduleTask() specificAssetIds = shell['specificAssetIds'] decodedAssetIds = urllib.parse.quote_plus(json.dumps(specificAssetIds)) - with self.client.get(f"/api/v3.0/lookup/shells?assetIds={decodedAssetIds}", name = "/api/v3.0/lookup/shells?assetIds={assetIds}", headers= headers, catch_response=True) as response: + with self.client.get(f"/api/v3/lookup/shells?assetIds={decodedAssetIds}", name = "/api/v3/lookup/shells?assetIds={assetIds}", headers= headers, catch_response=True) as response: if response.status_code != 200: response.failure(f"Expected 200 but status code was {response.status_code}") raise RescheduleTask() diff --git a/backend/src/main/resources/static/aas-registry-openapi.yaml b/backend/src/main/resources/static/aas-registry-openapi.yaml index 56befe9c..caafc23e 100644 --- a/backend/src/main/resources/static/aas-registry-openapi.yaml +++ b/backend/src/main/resources/static/aas-registry-openapi.yaml @@ -53,9 +53,9 @@ servers: - "443" - "4243" version_prefix: - default: v3.0 + default: v3 enum: - - v3.0 + - v3 context_root: description: Context root for the API default: "" 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 928a8a81..0cb68581 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 @@ -46,13 +46,13 @@ @EnableConfigurationProperties( RegistryProperties.class) public abstract class AbstractAssetAdministrationShellApi { - protected static final String SHELL_BASE_PATH = "/api/v3.0/shell-descriptors"; - protected static final String SINGLE_SHELL_BASE_PATH = "/api/v3.0/shell-descriptors/{aasIdentifier}"; - protected static final String LOOKUP_SHELL_BASE_PATH = "/api/v3.0/lookup/shells"; - protected static final String LOOKUP_SHELL_BASE_PATH_POST = "/api/v3.0/lookup/shellsByAssetLink"; - protected static final String SINGLE_LOOKUP_SHELL_BASE_PATH = "/api/v3.0/lookup/shells/{aasIdentifier}"; - protected static final String SUB_MODEL_BASE_PATH = "/api/v3.0/shell-descriptors/{aasIdentifier}/submodel-descriptors"; - protected static final String SINGLE_SUB_MODEL_BASE_PATH = "/api/v3.0/shell-descriptors/{aasIdentifier}/submodel-descriptors/{submodelIdentifier}"; + protected static final String SHELL_BASE_PATH = "/api/v3/shell-descriptors"; + protected static final String SINGLE_SHELL_BASE_PATH = "/api/v3/shell-descriptors/{aasIdentifier}"; + protected static final String LOOKUP_SHELL_BASE_PATH = "/api/v3/lookup/shells"; + protected static final String LOOKUP_SHELL_BASE_PATH_POST = "/api/v3/lookup/shellsByAssetLink"; + protected static final String SINGLE_LOOKUP_SHELL_BASE_PATH = "/api/v3/lookup/shells/{aasIdentifier}"; + protected static final String SUB_MODEL_BASE_PATH = "/api/v3/shell-descriptors/{aasIdentifier}/submodel-descriptors"; + protected static final String SINGLE_SUB_MODEL_BASE_PATH = "/api/v3/shell-descriptors/{aasIdentifier}/submodel-descriptors/{submodelIdentifier}"; protected static final String EXTERNAL_SUBJECT_ID_HEADER = "Edc-Bpn"; 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 b99194ed..1a2ea719 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 @@ -1276,7 +1276,7 @@ class DescriptionApiTest { public void testGetDescriptionOnlyDeleteRoleExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/description" ) + .get( "/api/v3/description" ) .accept( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.deleteTwin() ) ) @@ -1288,7 +1288,7 @@ public void testGetDescriptionOnlyDeleteRoleExpectForbidden() throws Exception { public void testGetDescriptionNoRoleExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/description" ) + .get( "/api/v3/description" ) .accept( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.withoutRoles() ) ) @@ -1300,7 +1300,7 @@ public void testGetDescriptionNoRoleExpectForbidden() throws Exception { public void testGetDescriptionReadRoleExpectSuccess() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/description" ) + .get( "/api/v3/description" ) .accept( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.readTwin() ) ) @@ -1312,7 +1312,7 @@ public void testGetDescriptionReadRoleExpectSuccess() throws Exception { public void testGetDescriptionReadRoleExpectUnauthorized() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/description" ) + .get( "/api/v3/description" ) .accept( MediaType.APPLICATION_JSON ) ) .andDo( MockMvcResultHandlers.print() ) 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 36ab98cf..f0310e03 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 @@ -1231,7 +1231,7 @@ class DescriptionApiTest { public void testGetDescriptionExpectSuccess() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/description" ) + .get( "/api/v3/description" ) .accept( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.allRoles() ) ) 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 0242e492..809a76f9 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 @@ -345,7 +345,7 @@ class SubmodelEndpointAuthorizationApiTest { void testPostSubmodelDescriptorAuthorizedWithoutTokenExpectUnauthorized() throws Exception { mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .content( HTTP_EDC_DATA_PLANE_URL_REQUEST ) .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) @@ -358,7 +358,7 @@ void testPostSubmodelDescriptorAuthorizedWithoutTokenExpectUnauthorized() throws void testPostSubmodelDescriptorAuthorizedWithoutAppropriateRoleExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.readTwin() ) .content( HTTP_EDC_DATA_PLANE_URL_REQUEST ) @@ -372,7 +372,7 @@ void testPostSubmodelDescriptorAuthorizedWithoutAppropriateRoleExpectForbidden() void testPostSubmodelDescriptorAuthorizedWithoutContentExpectBadRequest() throws Exception { mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.tenantOne().submodelAccessControl() ) .header( EXTERNAL_SUBJECT_ID_HEADER, jwtTokenFactory.tenantOne().getTenantId() ) @@ -385,7 +385,7 @@ void testPostSubmodelDescriptorAuthorizedWithoutContentExpectBadRequest() throws void testPostSubmodelDescriptorAuthorizedWithoutTenantIdExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.tenantOne().submodelAccessControl() ) .content( HTTP_EDC_DATA_PLANE_URL_REQUEST ) @@ -398,7 +398,7 @@ void testPostSubmodelDescriptorAuthorizedWithoutTenantIdExpectForbidden() throws void testPostSubmodelDescriptorAuthorizedWithoutAnyShellsExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.tenantOne().submodelAccessControl() ) .content( HTTP_EDC_DATA_PLANE_URL_REQUEST ) @@ -432,7 +432,7 @@ void testPostSubmodelDescriptorAuthorizedWithoutMatchingSemanticIdExpectForbidde //Tenant two should not have access because the rule does not give access to any semanticIds mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.tenantTwo().submodelAccessControl() ) .content( getRequestForUrl( EXISTING_URL + randomId ) ) @@ -466,7 +466,7 @@ void testPostSubmodelDescriptorAuthorizedWithMatchingShellAndSemanticIdExpectSuc //Tenant two should have access due to the matching shell and semantic Id values mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.tenantTwo().submodelAccessControl() ) .content( getRequestForUrl( EXISTING_URL + randomId ) ) @@ -500,7 +500,7 @@ void testPostSubmodelDescriptorAuthorizedWithoutMatchingShellExpectForbidden() t //Tenant three should have access due to the non-visible shell (as it is only visible to tenantTwo mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/submodel-descriptor/authorized" ) + .post( "/api/v3/submodel-descriptor/authorized" ) .contentType( MediaType.APPLICATION_JSON ) .with( jwtTokenFactory.tenantThree().submodelAccessControl() ) .content( getRequestForUrl( EXISTING_URL + randomId ) ) @@ -537,7 +537,7 @@ class AccessRuleEndpointApiTest { void testGetAccessRulesWithoutTokenExpectUnauthorized() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules" ) + .get( "/api/v3/access-controls/rules" ) ) .andDo( MockMvcResultHandlers.print() ) .andExpect( status().isUnauthorized() ); @@ -547,7 +547,7 @@ void testGetAccessRulesWithoutTokenExpectUnauthorized() throws Exception { void testPostAccessRuleWithoutTokenExpectUnauthorized() throws Exception { mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/access-controls/rules" ) + .post( "/api/v3/access-controls/rules" ) .contentType( MediaType.APPLICATION_JSON ) .content( objectMapper.writeValueAsString( new CreateAccessRule() .policyType( PolicyType.AAS ) @@ -562,7 +562,7 @@ void testPostAccessRuleWithoutTokenExpectUnauthorized() throws Exception { void testGetAnAccessRuleWithoutTokenExpectUnauthorized() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules/1" ) + .get( "/api/v3/access-controls/rules/1" ) ) .andDo( MockMvcResultHandlers.print() ) .andExpect( status().isUnauthorized() ); @@ -572,7 +572,7 @@ void testGetAnAccessRuleWithoutTokenExpectUnauthorized() throws Exception { void testPutAnAccessRuleWithoutTokenExpectUnauthorized() throws Exception { mvc.perform( MockMvcRequestBuilders - .put( "/api/v3.0/access-controls/rules/1" ) + .put( "/api/v3/access-controls/rules/1" ) .contentType( MediaType.APPLICATION_JSON ) .content( objectMapper.writeValueAsString( new ReadUpdateAccessRule() .id( 1L ) @@ -589,7 +589,7 @@ void testPutAnAccessRuleWithoutTokenExpectUnauthorized() throws Exception { void testDeleteAnAccessRuleWithoutTokenExpectUnauthorized() throws Exception { mvc.perform( MockMvcRequestBuilders - .delete( "/api/v3.0/access-controls/rules/1" ) + .delete( "/api/v3/access-controls/rules/1" ) ) .andDo( MockMvcResultHandlers.print() ) .andExpect( status().isUnauthorized() ); @@ -599,7 +599,7 @@ void testDeleteAnAccessRuleWithoutTokenExpectUnauthorized() throws Exception { void testGetAccessRulesWithWrongTokenExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules" ) + .get( "/api/v3/access-controls/rules" ) .with( jwtTokenFactory.tenantOne().writeAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) @@ -610,7 +610,7 @@ void testGetAccessRulesWithWrongTokenExpectForbidden() throws Exception { void testPostAccessRuleWithWrongTokenExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/access-controls/rules" ) + .post( "/api/v3/access-controls/rules" ) .contentType( MediaType.APPLICATION_JSON ) .content( objectMapper.writeValueAsString( new CreateAccessRule() .policyType( PolicyType.AAS ) @@ -626,7 +626,7 @@ void testPostAccessRuleWithWrongTokenExpectForbidden() throws Exception { void testGetAnAccessRuleWithWrongTokenExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules/1" ) + .get( "/api/v3/access-controls/rules/1" ) .with( jwtTokenFactory.tenantOne().writeAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) @@ -637,7 +637,7 @@ void testGetAnAccessRuleWithWrongTokenExpectForbidden() throws Exception { void testPutAnAccessRuleWithWrongTokenExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .put( "/api/v3.0/access-controls/rules/1" ) + .put( "/api/v3/access-controls/rules/1" ) .contentType( MediaType.APPLICATION_JSON ) .content( objectMapper.writeValueAsString( new ReadUpdateAccessRule() .id( 1L ) @@ -655,7 +655,7 @@ void testPutAnAccessRuleWithWrongTokenExpectForbidden() throws Exception { void testDeleteAnAccessRuleWithWrongTokenExpectForbidden() throws Exception { mvc.perform( MockMvcRequestBuilders - .delete( "/api/v3.0/access-controls/rules/1" ) + .delete( "/api/v3/access-controls/rules/1" ) .with( jwtTokenFactory.tenantOne().readAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) @@ -666,7 +666,7 @@ void testDeleteAnAccessRuleWithWrongTokenExpectForbidden() throws Exception { void testGetAccessRulesWithTokenExpectSuccess() throws Exception { mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules" ) + .get( "/api/v3/access-controls/rules" ) .with( jwtTokenFactory.tenantOne().readAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) @@ -679,7 +679,7 @@ void testPostAccessRuleWithTokenExpectSuccess() throws Exception { String description = UUID.randomUUID().toString(); String responseBody = mvc.perform( MockMvcRequestBuilders - .post( "/api/v3.0/access-controls/rules" ) + .post( "/api/v3/access-controls/rules" ) .contentType( MediaType.APPLICATION_JSON ) .content( objectMapper.writeValueAsString( new CreateAccessRule() .policyType( PolicyType.AAS ) @@ -710,7 +710,7 @@ void testGetAnAccessRuleWithTokenExpectSuccess() throws Exception { AccessRule saved = saveDefaultRule( description ); String responseBody = mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules/" + saved.getId() ) + .get( "/api/v3/access-controls/rules/" + saved.getId() ) .with( jwtTokenFactory.tenantOne().readAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) @@ -742,7 +742,7 @@ void testPutAnAccessRuleWithTokenExpectSuccess() throws Exception { String description = UUID.randomUUID().toString(); String responseBody = mvc.perform( MockMvcRequestBuilders - .put( "/api/v3.0/access-controls/rules/" + saved.getId() ) + .put( "/api/v3/access-controls/rules/" + saved.getId() ) .contentType( MediaType.APPLICATION_JSON ) .content( objectMapper.writeValueAsString( new ReadUpdateAccessRule() .id( saved.getId() ) @@ -775,7 +775,7 @@ void testDeleteAnAccessRuleWithTokenExpectSuccess() throws Exception { //verify that it exists mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules/" + saved.getId() ) + .get( "/api/v3/access-controls/rules/" + saved.getId() ) .with( jwtTokenFactory.tenantOne().readAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) @@ -783,7 +783,7 @@ void testDeleteAnAccessRuleWithTokenExpectSuccess() throws Exception { //delete mvc.perform( MockMvcRequestBuilders - .delete( "/api/v3.0/access-controls/rules/" + saved.getId() ) + .delete( "/api/v3/access-controls/rules/" + saved.getId() ) .with( jwtTokenFactory.tenantOne().writeAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) @@ -791,7 +791,7 @@ void testDeleteAnAccessRuleWithTokenExpectSuccess() throws Exception { //verify that it does not exist mvc.perform( MockMvcRequestBuilders - .get( "/api/v3.0/access-controls/rules/" + saved.getId() ) + .get( "/api/v3/access-controls/rules/" + saved.getId() ) .with( jwtTokenFactory.tenantOne().readAccessRules() ) ) .andDo( MockMvcResultHandlers.print() ) diff --git a/charts/registry/templates/tests/test-script-configmap.yaml b/charts/registry/templates/tests/test-script-configmap.yaml index faf3a753..ea94bc24 100644 --- a/charts/registry/templates/tests/test-script-configmap.yaml +++ b/charts/registry/templates/tests/test-script-configmap.yaml @@ -50,7 +50,7 @@ data: stages: - name: Create shell descriptor expect success request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors" method: POST headers: Content-Type: application/json @@ -65,7 +65,7 @@ data: - name: Get shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: GET headers: Content-Type: application/json @@ -84,7 +84,7 @@ data: - name: Update shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: PUT headers: Content-Type: application/json @@ -97,7 +97,7 @@ data: - name: Delete shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: DELETE headers: Content-Type: application/json diff --git a/docs/README.md b/docs/README.md index a71c04c4..3bd1335c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -250,7 +250,7 @@ To be able to register a DigitalTwin the following prerequisites must be met. ```mermaid sequenceDiagram - Client->>+Decentralized digital Twin registry: POST /api/v3.0/shell-descriptors + Client->>+Decentralized digital Twin registry: POST /api/v3/shell-descriptors Decentralized digital Twin registry->>+Client: 200 Ok Response success Note left of Client: Registers the AAS Descriptor by providing
- assetIds to make discovery possible (e.g. VIN)
- Submodel Descriptor Endpoint ``` @@ -533,7 +533,7 @@ _________________ Example to create a *Digital Twin* with `specificAssetIds`: ``` POST Method: -{{registry-baseurl}}/api/v3.0/shell-descriptors +{{registry-baseurl}}/api/v3/shell-descriptors ``` ```json @@ -823,7 +823,7 @@ flowchart LR showFiltered-->END ``` -###### Lookup shells - `GET {{baseUrl}}/api/v3.0/lookup/shells?assetIds=...` +###### Lookup shells - `GET {{baseUrl}}/api/v3/lookup/shells?assetIds=...` In case of the lookup shells, the filtering and access control of the *Digital Twins* is done using the following steps: @@ -832,7 +832,7 @@ In case of the lookup shells, the filtering and access control of the *Digital T 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 AAS Ids of the visible *Digital Twins* are returned. -###### Get all shells - `GET {{baseUrl}}/api/v3.0/shell-descriptors` +###### Get all shells - `GET {{baseUrl}}/api/v3/shell-descriptors` The process is similar to the lookup shells, the filtering and access control of the *Digital Twins* is done as follows: @@ -841,7 +841,7 @@ The process is similar to the lookup shells, the filtering and access control of 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. -###### Get Shell by AAS Id - `GET {{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier` +###### 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/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json b/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json index 084ea29a..93ba363d 100644 --- a/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json +++ b/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json @@ -25,7 +25,7 @@ } } }, - "url": "{{baseUrl}}/api/v3.0/shell-descriptors" + "url": "{{baseUrl}}/api/v3/shell-descriptors" }, "response": [] }, @@ -45,13 +45,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors", + "raw": "{{baseUrl}}/api/v3/shell-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors" ], "query": [ @@ -91,13 +91,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier" ], @@ -130,13 +130,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier" ], @@ -160,13 +160,13 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier" ], @@ -199,13 +199,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors" @@ -237,13 +237,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors" @@ -280,13 +280,13 @@ "method": "GET", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -326,13 +326,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -363,13 +363,13 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -409,13 +409,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/lookup/shells?assetIds=[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]", + "raw": "{{baseUrl}}/api/v3/lookup/shells?assetIds=[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "lookup", "shells" ], @@ -452,13 +452,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/lookup/shells/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "lookup", "shells", ":aasIdentifier" @@ -496,13 +496,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/lookup/shells/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "lookup", "shells", ":aasIdentifier" diff --git a/docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json b/docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json index 9fee6b35..d09774e4 100644 --- a/docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json +++ b/docs/development/postman/tractusx-dtr-aas-3.1.0-collection.json @@ -27,13 +27,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors", + "raw": "{{baseUrl}}/api/v3/shell-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors" ] } @@ -56,13 +56,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors", + "raw": "{{baseUrl}}/api/v3/shell-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors" ], "query": [ @@ -102,13 +102,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier" ], @@ -141,13 +141,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier" ], @@ -171,13 +171,13 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier" ], @@ -210,13 +210,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors" @@ -248,13 +248,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors" @@ -291,13 +291,13 @@ "method": "GET", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -337,13 +337,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -374,13 +374,13 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -420,13 +420,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/lookup/shells?assetIds=[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]", + "raw": "{{baseUrl}}/api/v3/lookup/shells?assetIds=[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "lookup", "shells" ], @@ -463,13 +463,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/lookup/shells/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "lookup", "shells", ":aasIdentifier" @@ -507,13 +507,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3/lookup/shells/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "lookup", "shells", ":aasIdentifier" @@ -550,13 +550,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/lookup/shellsByAssetLink", + "raw": "{{baseUrl}}/api/v3/lookup/shellsByAssetLink", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "lookup", "shellsByAssetLink" ], diff --git a/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json b/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json index f2f77b2b..958f427a 100644 --- a/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json +++ b/docs/development/postman/tractusx-dtr-access-rule-management-api-v1.0-collection.json @@ -10,7 +10,7 @@ "request": { "method": "GET", "header": [], - "url": "{{baseUrl}}/api/v3.0/access-controls/rules" + "url": "{{baseUrl}}/api/v3/access-controls/rules" }, "response": [] }, @@ -28,7 +28,7 @@ } } }, - "url": "{{baseUrl}}/api/v3.0/access-controls/rules" + "url": "{{baseUrl}}/api/v3/access-controls/rules" }, "response": [] }, @@ -38,13 +38,13 @@ "method": "GET", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/access-controls/rules/:ruleId", + "raw": "{{baseUrl}}/api/v3/access-controls/rules/:ruleId", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "access-controls", "rules", ":ruleId" @@ -74,13 +74,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3.0/access-controls/rules/:ruleId", + "raw": "{{baseUrl}}/api/v3/access-controls/rules/:ruleId", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "access-controls", "rules", ":ruleId" @@ -101,13 +101,13 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3.0/access-controls/rules/:ruleId", + "raw": "{{baseUrl}}/api/v3/access-controls/rules/:ruleId", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3.0", + "v3", "access-controls", "rules", ":ruleId" diff --git a/e2e-tests/aas-registry/test_api.tavern.yaml b/e2e-tests/aas-registry/test_api.tavern.yaml index 8c5404ea..fcbb0aea 100644 --- a/e2e-tests/aas-registry/test_api.tavern.yaml +++ b/e2e-tests/aas-registry/test_api.tavern.yaml @@ -27,7 +27,7 @@ includes: stages: - name: Test get shell descriptors without access token request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors" method: GET response: status_code: 401 @@ -37,7 +37,7 @@ stages: - name: Authenticated request request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors" method: GET headers: Content-Type: application/json @@ -61,7 +61,7 @@ stages: - name: Create shell descriptor expect success request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors" method: POST headers: Content-Type: application/json @@ -77,7 +77,7 @@ stages: - name: Get shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: GET headers: Content-Type: application/json @@ -97,7 +97,7 @@ stages: - name: Update shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: PUT headers: Content-Type: application/json @@ -111,7 +111,7 @@ stages: - name: Delete shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: DELETE headers: Content-Type: application/json From 5fda818513801df51cc51d6bef208bc68814c802 Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Mon, 18 Mar 2024 20:41:53 +0100 Subject: [PATCH 21/26] Helm version version bump up --- charts/registry/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml index 71e9c22a..b4eb2082 100644 --- a/charts/registry/Chart.yaml +++ b/charts/registry/Chart.yaml @@ -26,7 +26,7 @@ sources: - https://github.com/eclipse-tractusx/sldt-digital-twin-registry type: application -version: 0.4.9-RC2 +version: 0.4.9-RC3 appVersion: 0.4.1-RC2 dependencies: From abd1e8e9d77b46afeec3c225c81a6b829c9b6631 Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Tue, 19 Mar 2024 10:46:23 +0100 Subject: [PATCH 22/26] revert helm chart changes --- charts/registry/Chart.yaml | 2 +- .../registry/templates/tests/test-script-configmap.yaml | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml index b4eb2082..71e9c22a 100644 --- a/charts/registry/Chart.yaml +++ b/charts/registry/Chart.yaml @@ -26,7 +26,7 @@ sources: - https://github.com/eclipse-tractusx/sldt-digital-twin-registry type: application -version: 0.4.9-RC3 +version: 0.4.9-RC2 appVersion: 0.4.1-RC2 dependencies: diff --git a/charts/registry/templates/tests/test-script-configmap.yaml b/charts/registry/templates/tests/test-script-configmap.yaml index ea94bc24..faf3a753 100644 --- a/charts/registry/templates/tests/test-script-configmap.yaml +++ b/charts/registry/templates/tests/test-script-configmap.yaml @@ -50,7 +50,7 @@ data: stages: - name: Create shell descriptor expect success request: - url: "{aas_registry_api_url:s}/api/v3/shell-descriptors" + url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors" method: POST headers: Content-Type: application/json @@ -65,7 +65,7 @@ data: - name: Get shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" method: GET headers: Content-Type: application/json @@ -84,7 +84,7 @@ data: - name: Update shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" method: PUT headers: Content-Type: application/json @@ -97,7 +97,7 @@ data: - name: Delete shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" method: DELETE headers: Content-Type: application/json From e4c6722b97e858f87e750ac0450f7618029ca58f Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Wed, 20 Mar 2024 11:05:45 +0100 Subject: [PATCH 23/26] revert dtr-collection v3.0.0 --- .../tractusx-dtr-aas-3.0.0-collection.json | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json b/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json index 93ba363d..084ea29a 100644 --- a/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json +++ b/docs/development/postman/tractusx-dtr-aas-3.0.0-collection.json @@ -25,7 +25,7 @@ } } }, - "url": "{{baseUrl}}/api/v3/shell-descriptors" + "url": "{{baseUrl}}/api/v3.0/shell-descriptors" }, "response": [] }, @@ -45,13 +45,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors" ], "query": [ @@ -91,13 +91,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier" ], @@ -130,13 +130,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier" ], @@ -160,13 +160,13 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier" ], @@ -199,13 +199,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier", "submodel-descriptors" @@ -237,13 +237,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier", "submodel-descriptors" @@ -280,13 +280,13 @@ "method": "GET", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -326,13 +326,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -363,13 +363,13 @@ "method": "DELETE", "header": [], "url": { - "raw": "{{baseUrl}}/api/v3/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", + "raw": "{{baseUrl}}/api/v3.0/shell-descriptors/:aasIdentifier/submodel-descriptors/:submodelIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "shell-descriptors", ":aasIdentifier", "submodel-descriptors", @@ -409,13 +409,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3/lookup/shells?assetIds=[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]", + "raw": "{{baseUrl}}/api/v3.0/lookup/shells?assetIds=[{\"name\":\"oen\",\"value\":\"1239485\"},{\"name\":\"manufacturePartId\",\"value\":\"1234\"}]", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "lookup", "shells" ], @@ -452,13 +452,13 @@ } ], "url": { - "raw": "{{baseUrl}}/api/v3/lookup/shells/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "lookup", "shells", ":aasIdentifier" @@ -496,13 +496,13 @@ } }, "url": { - "raw": "{{baseUrl}}/api/v3/lookup/shells/:aasIdentifier", + "raw": "{{baseUrl}}/api/v3.0/lookup/shells/:aasIdentifier", "host": [ "{{baseUrl}}" ], "path": [ "api", - "v3", + "v3.0", "lookup", "shells", ":aasIdentifier" From b759e21cdf23095d9cf676a1d45cc2d885d4aa2e Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Wed, 20 Mar 2024 14:43:51 +0100 Subject: [PATCH 24/26] version update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 68a553d6..c39a0ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ 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.4.1-RC3 +## 0.4.1 ### Added - Added API POST lookup/shellsByAssetLink to retrieve shell ids without base64 encryption. - Added v3.1.0 postman collection for new API. From 3d5507f2550233a436e2dfb162d14a81cb9a1166 Mon Sep 17 00:00:00 2001 From: Sahil Aggarwal Date: Wed, 20 Mar 2024 15:09:56 +0100 Subject: [PATCH 25/26] helm version upgrade --- charts/registry/Chart.yaml | 4 ++-- .../registry/templates/tests/test-script-configmap.yaml | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/charts/registry/Chart.yaml b/charts/registry/Chart.yaml index 71e9c22a..436352a7 100644 --- a/charts/registry/Chart.yaml +++ b/charts/registry/Chart.yaml @@ -26,8 +26,8 @@ sources: - https://github.com/eclipse-tractusx/sldt-digital-twin-registry type: application -version: 0.4.9-RC2 -appVersion: 0.4.1-RC2 +version: 0.4.9 +appVersion: 0.4.1 dependencies: - repository: https://charts.bitnami.com/bitnami diff --git a/charts/registry/templates/tests/test-script-configmap.yaml b/charts/registry/templates/tests/test-script-configmap.yaml index faf3a753..ea94bc24 100644 --- a/charts/registry/templates/tests/test-script-configmap.yaml +++ b/charts/registry/templates/tests/test-script-configmap.yaml @@ -50,7 +50,7 @@ data: stages: - name: Create shell descriptor expect success request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors" method: POST headers: Content-Type: application/json @@ -65,7 +65,7 @@ data: - name: Get shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: GET headers: Content-Type: application/json @@ -84,7 +84,7 @@ data: - name: Update shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: PUT headers: Content-Type: application/json @@ -97,7 +97,7 @@ data: - name: Delete shell descriptor by id request: - url: "{aas_registry_api_url:s}/api/v3.0/shell-descriptors/{encoded_shell_id:s}" + url: "{aas_registry_api_url:s}/api/v3/shell-descriptors/{encoded_shell_id:s}" method: DELETE headers: Content-Type: application/json From 35e65fa2db1d0f989b40b149c1a13e6ea6f14a9f Mon Sep 17 00:00:00 2001 From: Istvan Zoltan Nagy Date: Tue, 26 Mar 2024 11:51:31 +0100 Subject: [PATCH 26/26] EDC extension - Creates new library for the granular access control extension - Adds extension configuration - Implements OAuth2 token client Updates #352 Signed-off-by: Istvan Zoltan Nagy --- libraries/edc-extension/pom.xml | 112 ++++++++ .../AccessControlServiceException.java | 32 +++ ...ataPlaneAccessControlServiceExtension.java | 97 +++++++ .../HttpAccessControlCheckClientConfig.java | 54 ++++ ...HttpAccessControlCheckDtrClientConfig.java | 74 ++++++ .../client/DtrOauth2TokenClient.java | 122 +++++++++ .../client/Oauth2TokenClient.java | 26 ++ ...ttpAccessControlCheckClientConfigTest.java | 69 +++++ ...AccessControlCheckDtrClientConfigTest.java | 111 ++++++++ .../client/DtrOauth2TokenClientTest.java | 251 ++++++++++++++++++ pom.xml | 26 ++ 11 files changed, 974 insertions(+) create mode 100644 libraries/edc-extension/pom.xml create mode 100644 libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/AccessControlServiceException.java create mode 100644 libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java create mode 100644 libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java create mode 100644 libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java create mode 100644 libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClient.java create mode 100644 libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/Oauth2TokenClient.java create mode 100644 libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java create mode 100644 libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java create mode 100644 libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClientTest.java diff --git a/libraries/edc-extension/pom.xml b/libraries/edc-extension/pom.xml new file mode 100644 index 00000000..f3ac2e7b --- /dev/null +++ b/libraries/edc-extension/pom.xml @@ -0,0 +1,112 @@ + + + + + 4.0.0 + + org.eclipse.tractusx + digital-twin-registry + DEV-SNAPSHOT + ../../pom.xml + + + org.eclipse.tractusx.digital_twin_registry + dtr-edc-access-control-extension + Tractus-X Semantic Layer Digital Twin Registry Access Control Extension for Eclipse Dataspace Connector Dataplane + Module contains the EDC extension triggering access control calls to the Semantic Layer Digital Twin Registry Service's relevant API endpoint. + jar + + + ${organization} + ${url} + + + + + ${licence_name} + ${licence_url} + ${licence_distribution} + ${licence_comments} + + + + + + org.eclipse.edc + connector-core + + + org.eclipse.edc + data-plane-spi + + + org.eclipse.edc + data-plane-http-spi + + + org.apache.commons + commons-lang3 + + + com.github.ben-manes.caffeine + caffeine + + + org.junit.jupiter + junit-jupiter + + + org.mockito + mockito-core + test + + + org.assertj + assertj-core + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.github.git-commit-id + git-commit-id-maven-plugin + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.plugins + maven-deploy-plugin + + + + diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/AccessControlServiceException.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/AccessControlServiceException.java new file mode 100644 index 00000000..cf856e73 --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/AccessControlServiceException.java @@ -0,0 +1,32 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +public class AccessControlServiceException extends RuntimeException { + + public AccessControlServiceException( final String message ) { + super( message ); + } + + public AccessControlServiceException( final Throwable cause ) { + super( cause ); + } +} diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java new file mode 100644 index 00000000..72e2837b --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/DtrDataPlaneAccessControlServiceExtension.java @@ -0,0 +1,97 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import org.eclipse.edc.connector.dataplane.spi.iam.DataPlaneAccessTokenService; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.runtime.metamodel.annotation.Setting; +import org.eclipse.edc.spi.http.EdcHttpClient; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.eclipse.edc.spi.types.TypeManager; + +@Extension( value = "Data Plane HTTP Access Control" ) +public class DtrDataPlaneAccessControlServiceExtension implements ServiceExtension { + + @Setting( value = "Contains the base URL of the EDC data plane endpoint where the data plane requests are sent by the end users." ) + public static final String EDC_DATA_PLANE_BASE_URL = "edc.granular.access.verification.edc.data.plane.baseUrl"; + @Setting( value = "Comma separated list of DTR configuration names used as keys for DTR clients." ) + public static final String EDC_DTR_CONFIG_NAMES = "edc.granular.access.verification.dtr.names"; + /** + * Prefix for individual DTR configurations. + */ + public static final String EDC_DTR_CONFIG_PREFIX = "edc.granular.access.verification.dtr.config."; + /** + * Configuration property suffix for the configuration of DTR decision cache. The cache is turned off if set to 0. + */ + public static final String DTR_DECISION_CACHE_MINUTES = "dtr.decision.cache.duration.minutes"; + /** + * Configuration property suffix for the pattern to allow for the recognition of aspect model requests which need + * to be handled by DTR access control. + */ + public static final String ASPECT_MODEL_URL_PATTERN = "aspect.model.url.pattern"; + /** + * Configuration property suffix for the URL where DTR can be reached. + */ + public static final String DTR_ACCESS_VERIFICATION_URL = "dtr.access.verification.endpoint.url"; + /** + * Configuration property suffix for the URL where OAUTH2 tokens can be obtained for the DTR requests. + */ + public static final String OAUTH2_TOKEN_ENDPOINT_URL = "oauth2.token.endpoint.url"; + /** + * Configuration property suffix for the scope we need to use for OAUTH2 token requests when we need to access DTR. + */ + public static final String OAUTH2_TOKEN_SCOPE = "oauth2.token.scope"; + /** + * Configuration property suffix for the client id we need to use for OAUTH2 token requests when we need to access DTR. + */ + public static final String OAUTH2_TOKEN_CLIENT_ID = "oauth2.token.clientId"; + + /** + * Configuration property suffix for the path where we can find the client secret in vault for the OAUTH2 token requests when we need to access DTR. + */ + public static final String OAUTH2_TOKEN_CLIENT_SECRET_PATH = "oauth2.token.clientSecret.path"; + @Inject + private Monitor monitor; + @Inject + private EdcHttpClient httpClient; + @Inject + private TypeManager typeManager; + @Inject + private Vault vault; + @Inject + private DataPlaneAccessTokenService dataPlaneAccessTokenService; + private HttpAccessControlCheckClientConfig config; + + @Override + public String name() { + return "DTR Data Plane Access Control Service"; + } + + @Override + public void initialize( final ServiceExtensionContext context ) { + monitor.info( "Initializing " + name() ); + config = new HttpAccessControlCheckClientConfig( context ); + } +} diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java new file mode 100644 index 00000000..547821ee --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfig.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; + +import java.util.Arrays; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +public class HttpAccessControlCheckClientConfig { + + private final Map dtrClientConfigMap; + private final String edcDataPlaneBaseUrl; + + public HttpAccessControlCheckClientConfig( final ServiceExtensionContext context ) { + dtrClientConfigMap = Arrays.stream( context.getSetting( EDC_DTR_CONFIG_NAMES, "" ).split( "," ) ) + .filter( StringUtils::isNotBlank ) + .collect( Collectors.toUnmodifiableMap( Function.identity(), + name -> new HttpAccessControlCheckDtrClientConfig( + context.getConfig( EDC_DTR_CONFIG_PREFIX + name ) ) ) ); + edcDataPlaneBaseUrl = context.getSetting( EDC_DATA_PLANE_BASE_URL, null ); + } + + public Map getDtrClientConfigMap() { + return dtrClientConfigMap; + } + + public String getEdcDataPlaneBaseUrl() { + return edcDataPlaneBaseUrl; + } +} diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java new file mode 100644 index 00000000..d1cf2ca1 --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfig.java @@ -0,0 +1,74 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; + +import org.eclipse.edc.spi.system.configuration.Config; + +public class HttpAccessControlCheckDtrClientConfig { + + private final String aspectModelUrlPattern; + private final String dtrAccessVerificationUrl; + private final String oauth2TokenEndpointUrl; + private final String oauth2TokenScope; + private final String oauth2ClientId; + private final String oauth2ClientSecretPath; + private final int decisionCacheDurationMinutes; + + public HttpAccessControlCheckDtrClientConfig( final Config context ) { + aspectModelUrlPattern = context.getString( ASPECT_MODEL_URL_PATTERN, null ); + dtrAccessVerificationUrl = context.getString( DTR_ACCESS_VERIFICATION_URL, null ); + oauth2TokenEndpointUrl = context.getString( OAUTH2_TOKEN_ENDPOINT_URL, null ); + oauth2TokenScope = context.getString( OAUTH2_TOKEN_SCOPE, null ); + oauth2ClientId = context.getString( OAUTH2_TOKEN_CLIENT_ID, null ); + oauth2ClientSecretPath = context.getString( OAUTH2_TOKEN_CLIENT_SECRET_PATH, null ); + decisionCacheDurationMinutes = context.getInteger( DTR_DECISION_CACHE_MINUTES, 0 ); + } + + public String getAspectModelUrlPattern() { + return aspectModelUrlPattern; + } + + public String getDtrAccessVerificationUrl() { + return dtrAccessVerificationUrl; + } + + public String getOauth2TokenEndpointUrl() { + return oauth2TokenEndpointUrl; + } + + public String getOauth2TokenScope() { + return oauth2TokenScope; + } + + public String getOauth2ClientId() { + return oauth2ClientId; + } + + public String getOauth2ClientSecretPath() { + return oauth2ClientSecretPath; + } + + public int getDecisionCacheDurationMinutes() { + return decisionCacheDurationMinutes; + } +} diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClient.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClient.java new file mode 100644 index 00000000..17e9980c --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClient.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client; + +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; + +import org.eclipse.edc.spi.http.EdcHttpClient; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.AccessControlServiceException; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.HttpAccessControlCheckDtrClientConfig; +import org.jetbrains.annotations.NotNull; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.github.benmanes.caffeine.cache.LoadingCache; + +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +public class DtrOauth2TokenClient implements Oauth2TokenClient { + + static final TypeReference> MAP_TYPE_REFERENCE = new TypeReference<>() { + }; + static final String CONTENT_TYPE = "Content-Type"; + static final String ACCESS_TOKEN = "access_token"; + private final Monitor monitor; + private final EdcHttpClient httpClient; + private final HttpAccessControlCheckDtrClientConfig dtrConfig; + private final TypeManager typeManager; + private final LoadingCache secretCache; + + public DtrOauth2TokenClient( + final Monitor monitor, + final EdcHttpClient httpClient, + final TypeManager typeManager, + final Vault vault, + final HttpAccessControlCheckDtrClientConfig dtrConfig ) { + this.monitor = monitor; + this.httpClient = httpClient; + this.typeManager = typeManager; + this.dtrConfig = dtrConfig; + this.secretCache = Caffeine.newBuilder() + .maximumSize( 1 ) + .expireAfterWrite( Duration.ofMinutes( 30 ) ) + .refreshAfterWrite( Duration.ofMinutes( 15 ) ) + .build( vault::resolveSecret ); + } + + @Override + public String getBearerToken( final String scope ) { + final Request request = createTokenRequest( scope ); + try ( Response response = httpClient.execute( request ) ) { + if ( !response.isSuccessful() ) { + throw new IllegalStateException( "OAuth2 authentication error. Response code=" + response.code() ); + } + final ResponseBody body = response.body(); + if ( body == null ) { + throw new IllegalStateException( "OAuth2 response body is empty." ); + } + final var map = typeManager.readValue( body.string(), MAP_TYPE_REFERENCE ); + if ( !map.containsKey( ACCESS_TOKEN ) ) { + throw new IllegalStateException( "OAuth2 response body had no token." ); + } + return map.get( ACCESS_TOKEN ); + } catch ( final Exception e ) { + monitor.severe( "Failed to obtain Bearer token: " + e.getMessage() ); + return null; + } + } + + @NotNull + private String fetchClientSecret() { + final String secret = secretCache.get( dtrConfig.getOauth2ClientSecretPath() ); + if ( secret == null ) { + throw new AccessControlServiceException( "Cannot resolve authentication credentials." ); + } + return secret; + } + + @NotNull + private Request createTokenRequest( final String scope ) { + final String secret = fetchClientSecret(); + final FormBody formBody = new FormBody( + List.of( "grant_type", "client_id", "client_secret", "scope" ), + List.of( "client_credentials", + URLEncoder.encode( dtrConfig.getOauth2ClientId(), StandardCharsets.UTF_8 ), + URLEncoder.encode( secret, StandardCharsets.UTF_8 ), + URLEncoder.encode( scope, StandardCharsets.UTF_8 ) ) ); + return new Request.Builder() + .url( dtrConfig.getOauth2TokenEndpointUrl() ) + .addHeader( "Accept", "application/json" ) + .addHeader( "Content-Type", formBody.contentType().toString() ) + .post( formBody ) + .build(); + } +} diff --git a/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/Oauth2TokenClient.java b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/Oauth2TokenClient.java new file mode 100644 index 00000000..4e5440e4 --- /dev/null +++ b/libraries/edc-extension/src/main/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/Oauth2TokenClient.java @@ -0,0 +1,26 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client; + +public interface Oauth2TokenClient { + + String getBearerToken( String scope ); +} diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java new file mode 100644 index 00000000..57cda353 --- /dev/null +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckClientConfigTest.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import java.util.Map; + +import org.eclipse.edc.spi.system.ServiceExtensionContext; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class HttpAccessControlCheckClientConfigTest { + private ServiceExtensionContext serviceExtensionContext; + private HttpAccessControlCheckClientConfig underTest; + + @BeforeEach + void setUp() { + serviceExtensionContext = mock(); + } + + @Test + void test_GetDtrClientConfigMap_ShouldReturnEmptyMap_WhenConfigurationIsNotSet() { + //given + final int expected = 1; + when( serviceExtensionContext.getSetting( EDC_DTR_CONFIG_NAMES, "" ) ).thenReturn( "" ); + underTest = new HttpAccessControlCheckClientConfig( serviceExtensionContext ); + //when + final var actual = underTest.getDtrClientConfigMap(); + //then + assertThat( actual ).isEqualTo( Map.of() ); + verify( serviceExtensionContext, never() ).getConfig( anyString() ); + } + + @Test + void test_GetEdcDataPlaneBaseUrl_ShouldReturnExpectedValue_WhenConfigurationWasSet() { + //given + final String expected = "http://edc-data-plane/proxy"; + when( serviceExtensionContext.getSetting( EDC_DTR_CONFIG_NAMES, "" ) ).thenReturn( "name" ); + when( serviceExtensionContext.getSetting( eq( EDC_DATA_PLANE_BASE_URL ), isNull() ) ).thenReturn( expected ); + when( serviceExtensionContext.getConfig( EDC_DTR_CONFIG_PREFIX + "name" ) ).thenReturn( mock() ); + underTest = new HttpAccessControlCheckClientConfig( serviceExtensionContext ); + //when + final String actual = underTest.getEdcDataPlaneBaseUrl(); + //then + assertThat( actual ).isEqualTo( expected ); + } +} diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java new file mode 100644 index 00000000..3916a022 --- /dev/null +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/HttpAccessControlCheckDtrClientConfigTest.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.DtrDataPlaneAccessControlServiceExtension.*; +import static org.mockito.Mockito.*; + +import org.eclipse.edc.spi.system.configuration.Config; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class HttpAccessControlCheckDtrClientConfigTest { + private Config config; + private HttpAccessControlCheckDtrClientConfig underTest; + + @BeforeEach + void setUp() { + config = mock(); + } + + @Test + void test_GetAspectModelUrlPattern_ShouldReturnExpectedValue_WhenConfigurationWasSet() { + //given + final String expected = "http://aspec-model/api"; + when( config.getString( eq( ASPECT_MODEL_URL_PATTERN ), isNull() ) ).thenReturn( expected ); + underTest = new HttpAccessControlCheckDtrClientConfig( config ); + //when + final String actual = underTest.getAspectModelUrlPattern(); + //then + assertThat( actual ).isEqualTo( expected ); + } + + @Test + void test_GetDtrAccessVerificationUrl_ShouldReturnExpectedValue_WhenConfigurationWasSet() { + //given + final String expected = "http://dtr/submodel-descriptor/authorized"; + when( config.getString( eq( DTR_ACCESS_VERIFICATION_URL ), isNull() ) ).thenReturn( expected ); + underTest = new HttpAccessControlCheckDtrClientConfig( config ); + //when + final String actual = underTest.getDtrAccessVerificationUrl(); + //then + assertThat( actual ).isEqualTo( expected ); + } + + @Test + void test_GetOauth2TokenEndpointUrl_ShouldReturnExpectedValue_WhenConfigurationWasSet() { + //given + final String expected = "http://oauth2/token"; + when( config.getString( eq( OAUTH2_TOKEN_ENDPOINT_URL ), isNull() ) ).thenReturn( expected ); + underTest = new HttpAccessControlCheckDtrClientConfig( config ); + //when + final String actual = underTest.getOauth2TokenEndpointUrl(); + //then + assertThat( actual ).isEqualTo( expected ); + } + + @Test + void test_GetOauth2TokenScope_ShouldReturnExpectedValue_WhenConfigurationWasSet() { + //given + final String expected = "aud:dtr-id"; + when( config.getString( eq( OAUTH2_TOKEN_SCOPE ), isNull() ) ).thenReturn( expected ); + underTest = new HttpAccessControlCheckDtrClientConfig( config ); + //when + final String actual = underTest.getOauth2TokenScope(); + //then + assertThat( actual ).isEqualTo( expected ); + } + + @Test + void test_GetOauth2ClientId_ShouldReturnExpectedValue_WhenConfigurationWasSet() { + //given + final String expected = "edc-client-id"; + when( config.getString( eq( OAUTH2_TOKEN_CLIENT_ID ), isNull() ) ).thenReturn( expected ); + underTest = new HttpAccessControlCheckDtrClientConfig( config ); + //when + final String actual = underTest.getOauth2ClientId(); + //then + assertThat( actual ).isEqualTo( expected ); + } + + @Test + void test_GetOauth2ClientSecretPath_ShouldReturnExpectedValue_WhenConfigurationWasSet() { + //given + final String expected = "edc-client-secret-path"; + when( config.getString( eq( OAUTH2_TOKEN_CLIENT_SECRET_PATH ), isNull() ) ).thenReturn( expected ); + underTest = new HttpAccessControlCheckDtrClientConfig( config ); + //when + final String actual = underTest.getOauth2ClientSecretPath(); + //then + assertThat( actual ).isEqualTo( expected ); + } +} diff --git a/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClientTest.java b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClientTest.java new file mode 100644 index 00000000..0f1ae482 --- /dev/null +++ b/libraries/edc-extension/src/test/java/org/eclipse/tractusx/semantics/edc/dataplane/http/accesscontrol/client/DtrOauth2TokenClientTest.java @@ -0,0 +1,251 @@ +/******************************************************************************* + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH and others + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ******************************************************************************/ + +package org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client; + +import static org.assertj.core.api.Assertions.*; +import static org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.client.DtrOauth2TokenClient.*; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.*; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.edc.spi.http.EdcHttpClient; +import org.eclipse.edc.spi.monitor.Monitor; +import org.eclipse.edc.spi.security.Vault; +import org.eclipse.edc.spi.types.TypeManager; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.AccessControlServiceException; +import org.eclipse.tractusx.semantics.edc.dataplane.http.accesscontrol.HttpAccessControlCheckDtrClientConfig; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import okhttp3.FormBody; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +class DtrOauth2TokenClientTest { + static final String POST = "POST"; + static final String SCOPE = "aud:dtr"; + static final String ENCODED_SCOPE = URLEncoder.encode( SCOPE, StandardCharsets.UTF_8 ); + static final String CLIENT_ID = "client:id"; + static final String ENCODED_CLIENT_ID = URLEncoder.encode( CLIENT_ID, StandardCharsets.UTF_8 ); + static final String CLIENT_SECRET_VAULT_PATH = "client_secret"; + static final String CLIENT_SECRET = "client:secret"; + static final String ENCODED_CLIENT_SECRET = URLEncoder.encode( CLIENT_SECRET, StandardCharsets.UTF_8 ); + static final String DUMMY_TOKEN = "dummy_token_value"; + static final String RESPONSE_BODY = "{\"" + ACCESS_TOKEN + "\": \"" + DUMMY_TOKEN + "\"}"; + static final String LOCALHOST_TOKEN_ENDPOINT = "https://localhost/token"; + @Mock + private Response httpResponse; + @Mock + private ResponseBody responseBody; + @Mock + private Monitor monitor; + @Mock + private EdcHttpClient httpClient; + @Mock + private Vault vault; + @Mock + private TypeManager typeManager; + @Mock + private HttpAccessControlCheckDtrClientConfig dtrConfig; + @InjectMocks + private DtrOauth2TokenClient underTest; + private AutoCloseable mocks; + + @BeforeEach + void setUp() { + mocks = MockitoAnnotations.openMocks( this ); + } + + @AfterEach + void tearDown() throws Exception { + mocks.close(); + } + + @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } ) + @Test + void test_GetBearerToken_ShouldReturnToken_WhenOauth2ResponseContainsOne() throws IOException { + //given + when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID ); + when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE ); + when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH ); + when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT ); + when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET ); + final var requestMatcher = new RequestMatcher( + LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE ); + when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse ); + when( httpResponse.isSuccessful() ).thenReturn( true ); + when( httpResponse.body() ).thenReturn( responseBody ); + when( responseBody.string() ).thenReturn( RESPONSE_BODY ); + when( typeManager.readValue( RESPONSE_BODY, MAP_TYPE_REFERENCE ) ) + .thenReturn( new HashMap<>( Map.of( ACCESS_TOKEN, DUMMY_TOKEN ) ) ); + //when + final String actual = underTest.getBearerToken( SCOPE ); + //then + assertThat( actual ).isEqualTo( DUMMY_TOKEN ); + final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor ); + inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH ); + inOrder.verify( httpClient ).execute( argThat( requestMatcher ) ); + inOrder.verify( httpResponse ).isSuccessful(); + inOrder.verify( httpResponse, atLeastOnce() ).body(); + inOrder.verify( responseBody ).string(); + inOrder.verify( typeManager ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) ); + } + + @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } ) + @Test + void test_GetBearerToken_ShouldReturnNull_WhenOauth2ResponseIsNotSuccessful() throws IOException { + //given + when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID ); + when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE ); + when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH ); + when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT ); + when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET ); + final var requestMatcher = new RequestMatcher( + LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE ); + when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse ); + when( httpResponse.isSuccessful() ).thenReturn( false ); + //when + final String actual = underTest.getBearerToken( SCOPE ); + //then + assertThat( actual ).isNull(); + final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor ); + inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH ); + inOrder.verify( httpClient ).execute( argThat( requestMatcher ) ); + inOrder.verify( httpResponse ).isSuccessful(); + inOrder.verify( monitor ).severe( anyString() ); + inOrder.verify( httpResponse, never() ).body(); + inOrder.verify( responseBody, never() ).string(); + inOrder.verify( typeManager, never() ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) ); + } + + @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } ) + @Test + void test_GetBearerToken_ShouldReturnNull_WhenOauth2ResponseHasNoBody() throws IOException { + //given + when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID ); + when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE ); + when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH ); + when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT ); + when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET ); + final var requestMatcher = new RequestMatcher( + LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE ); + when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse ); + when( httpResponse.isSuccessful() ).thenReturn( true ); + when( httpResponse.body() ).thenReturn( null ); + //when + final String actual = underTest.getBearerToken( SCOPE ); + //then + assertThat( actual ).isNull(); + final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor ); + inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH ); + inOrder.verify( httpClient ).execute( argThat( requestMatcher ) ); + inOrder.verify( httpResponse ).isSuccessful(); + inOrder.verify( httpResponse ).body(); + inOrder.verify( monitor ).severe( anyString() ); + inOrder.verify( responseBody, never() ).string(); + inOrder.verify( typeManager, never() ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) ); + } + + @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } ) + @Test + void test_GetBearerToken_ShouldReturnNull_WhenOauth2ResponseHasNoTokenBody() throws IOException { + //given + when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID ); + when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE ); + when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH ); + when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT ); + when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( CLIENT_SECRET ); + final var requestMatcher = new RequestMatcher( + LOCALHOST_TOKEN_ENDPOINT, ENCODED_CLIENT_ID, ENCODED_CLIENT_SECRET, ENCODED_SCOPE ); + when( httpClient.execute( argThat( requestMatcher ) ) ).thenReturn( httpResponse ); + when( httpResponse.isSuccessful() ).thenReturn( true ); + when( httpResponse.body() ).thenReturn( responseBody ); + when( responseBody.string() ).thenReturn( "{}" ); + when( typeManager.readValue( "{}", MAP_TYPE_REFERENCE ) ) + .thenReturn( new HashMap<>() ); + //when + final String actual = underTest.getBearerToken( SCOPE ); + //then + assertThat( actual ).isNull(); + final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor ); + inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH ); + inOrder.verify( httpClient ).execute( argThat( requestMatcher ) ); + inOrder.verify( httpResponse ).isSuccessful(); + inOrder.verify( httpResponse ).body(); + inOrder.verify( responseBody ).string(); + inOrder.verify( typeManager ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) ); + inOrder.verify( monitor ).severe( anyString() ); + } + + @SuppressWarnings( { "resource", "ResultOfMethodCallIgnored" } ) + @Test + void test_GetBearerToken_ShouldThrowException_WhenSecretCannotBeFetchedFromVault() throws IOException { + //given + when( dtrConfig.getOauth2ClientId() ).thenReturn( CLIENT_ID ); + when( dtrConfig.getOauth2TokenScope() ).thenReturn( SCOPE ); + when( dtrConfig.getOauth2ClientSecretPath() ).thenReturn( CLIENT_SECRET_VAULT_PATH ); + when( dtrConfig.getOauth2TokenEndpointUrl() ).thenReturn( LOCALHOST_TOKEN_ENDPOINT ); + when( vault.resolveSecret( CLIENT_SECRET_VAULT_PATH ) ).thenReturn( null ); + //when + assertThatExceptionOfType( AccessControlServiceException.class ).isThrownBy( () -> underTest.getBearerToken( SCOPE ) ); + //then + final var inOrder = inOrder( vault, httpClient, httpResponse, responseBody, typeManager, monitor ); + inOrder.verify( vault ).resolveSecret( CLIENT_SECRET_VAULT_PATH ); + inOrder.verify( httpClient, never() ).execute( any() ); + inOrder.verify( httpResponse, never() ).isSuccessful(); + inOrder.verify( httpResponse, never() ).body(); + inOrder.verify( responseBody, never() ).string(); + inOrder.verify( typeManager, never() ).readValue( anyString(), eq( MAP_TYPE_REFERENCE ) ); + } + + private record RequestMatcher(String url, String clientId, String clientSecret, String scope) implements ArgumentMatcher { + @SuppressWarnings( "DataFlowIssue" ) + @Override + public boolean matches( final Request request ) { + final boolean methodMatched = POST.equals( request.method() ); + final boolean urlMatched = url.equals( request.url().url().toString() ); + final FormBody body = (FormBody) request.body(); + final boolean contentTypeHeaderFound = body.contentType().toString().equals( request.header( CONTENT_TYPE ) ); + final Map expectedParameterMap = Map.of( + "client_id", clientId, + "client_secret", clientSecret, + "scope", scope, + "grant_type", "client_credentials" ); + boolean bodyMatched = body.size() == expectedParameterMap.size(); + for ( int i = 0; i < body.size(); i++ ) { + bodyMatched &= expectedParameterMap.get( body.name( i ) ).equals( body.encodedValue( i ) ); + } + return methodMatched && urlMatched && contentTypeHeaderFound && bodyMatched; + } + } + +} diff --git a/pom.xml b/pom.xml index 2e9ac1dd..383985cb 100644 --- a/pom.xml +++ b/pom.xml @@ -95,6 +95,9 @@ 2.2.220 4.19.1 + + 0.5.2-20240321-SNAPSHOT + 3.24.2 5.9.3 @@ -107,6 +110,7 @@ backend access-control-service-interface access-control-service-sql-impl + libraries/edc-extension @@ -280,6 +284,23 @@ ${snakeyaml.version} + + + org.eclipse.edc + connector-core + ${edc.version} + + + org.eclipse.edc + data-plane-spi + ${edc.version} + + + org.eclipse.edc + data-plane-http-spi + ${edc.version} + + org.postgresql @@ -413,6 +434,11 @@ maven-central https://repo1.maven.org/maven2/ + + Maven Snapshots + maven-snapshots + https://oss.sonatype.org/content/repositories/snapshots/ +

cvJZVAuE=fR=DJ!aoKt-O&XrY*oX%@-Ct6YJb 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