From 7fc21d2297daf95ac150383fc967f89a89fd7234 Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Fri, 7 Aug 2026 14:01:23 +0200 Subject: [PATCH 01/11] feat: initial draft version --- .../build.gradle.kts | 2 +- .../build.gradle.kts | 2 +- .../environments/local-con-x-env.bru | 35 +++--- .../Get Consumer DID Doc.bru | 2 +- .../RequestConsumerDevMemCredential.bru | 4 +- .../RequestCustomCredential.bru | 34 ++++++ .../Prepare Consumer ID/VaultSecret test.bru | 2 +- .../Prepare Consumer ID/VaultSecret.bru | 2 +- .../createCustomAttestation.bru | 40 +++++++ .../createCustomCredentialDef.bru | 42 +++++++ .../Prepare Issuer/createDevAttestation.bru | 20 +--- .../createDevMemCredentialDef.bru | 4 +- .../Get Provider DID Doc.bru | 2 +- .../RequestCustomCredential.bru | 35 ++++++ .../RequestProviderDevMemCredential.bru | 4 +- .../Prepare Provider ID/VaultSecret test.bru | 2 +- .../Prepare Provider ID/VaultSecret.bru | 2 +- .../Simulated DCP Flow/Consumer Token.bru | 2 +- .../Simulated DCP Flow/Get Credential.bru | 2 +- .../consumer/InitiateNegotiation.bru | 3 +- .../consumer/RequestProviderCatalog.bru | 35 ++++-- .../provider/CreateContractDefinition.bru | 28 ----- .../CreateAccessPolicy.bru} | 19 +++- .../{ => createAssetOne}/CreateAsset.bru | 2 +- .../CreateContractDefinition.bru | 35 ++++++ .../createAssetOne/CreateContractPolicy.bru | 51 +++++++++ .../provider/createAssetOne/folder.bru | 8 ++ .../createAssetTwo/CreateAccessPolicy.bru | 46 ++++++++ .../provider/createAssetTwo/CreateAsset.bru | 39 +++++++ .../CreateContractDefinition.bru | 35 ++++++ .../createAssetTwo/CreateContractPolicy.bru | 51 +++++++++ .../provider/createAssetTwo/folder.bru | 8 ++ edc-extensions/basic-abac/README.md | 99 +++++++++++++++++ edc-extensions/basic-abac/build.gradle.kts | 42 +++++++ ...BasicAbacCredentialConstraintFunction.java | 103 ++++++++++++++++++ .../basic_abac/dev/BasicAbacExtension.java | 97 +++++++++++++++++ .../dev/BasicAbacPolicyPostValidator.java | 76 +++++++++++++ .../basic_abac/dev/BasicAbacUtils.java | 91 ++++++++++++++++ ...rg.eclipse.edc.spi.system.ServiceExtension | 20 ++++ .../src/test/java/BasicAbacTest.java | 23 ++++ settings.gradle.kts | 1 + 41 files changed, 1057 insertions(+), 93 deletions(-) create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru delete mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreateContractDefinition.bru rename edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/{CreatePolicy.bru => createAssetOne/CreateAccessPolicy.bru} (51%) rename edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/{ => createAssetOne}/CreateAsset.bru (95%) create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractDefinition.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/folder.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAsset.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractDefinition.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru create mode 100644 edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/folder.bru create mode 100644 edc-extensions/basic-abac/README.md create mode 100644 edc-extensions/basic-abac/build.gradle.kts create mode 100644 edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java create mode 100644 edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java create mode 100644 edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java create mode 100644 edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java create mode 100644 edc-extensions/basic-abac/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension create mode 100644 edc-extensions/basic-abac/src/test/java/BasicAbacTest.java diff --git a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts index 8349f2fc7c..90b4b120a7 100644 --- a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-hashicorp-vault/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { implementation("org.eclipse.tractusx.edc:agreements:$txVersion") implementation("org.eclipse.tractusx.edc:retirement-evaluation-store-sql:$txVersion") implementation("org.eclipse.tractusx.edc:control-plane-migration:$txVersion") - implementation("org.eclipse.tractusx.edc:tx-dcp:$txVersion") + implementation(project(":edc-extensions:basic-abac")) } tasks.withType { diff --git a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts index d0cd959721..22faf56d94 100644 --- a/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts +++ b/edc-controlplane/edc-controlplane-construct-x/con-x-controlplane-postgresql-vault/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { implementation("org.eclipse.tractusx.edc:agreements:$txVersion") implementation("org.eclipse.tractusx.edc:retirement-evaluation-store-sql:$txVersion") implementation("org.eclipse.tractusx.edc:control-plane-migration:$txVersion") - implementation("org.eclipse.tractusx.edc:tx-dcp:$txVersion") + implementation(project(":edc-extensions:basic-abac")) } tasks.withType { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru index df66b443df..a7fd70aaf0 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru @@ -6,12 +6,12 @@ vars { CONSUMER_IDHUB_ID_API: http://localhost:20100/api/identity CONSUMER_IDHUB_STS_API: http://localhost:20500/api/sts CONSUMER_IDHUB_CREDS_API: http://localhost:20600/api/credentials - ISSUER_APIKEY: ZGlkOndlYjpsb2NhbC1pc3N1ZXItc2VydmljZTpmeC1pc3N1ZXI=.CmYgVcuzneJXqzcbj7vmld5feejy6OguIylflSrz6WTjG1HNQRvO62EafFrQjS/lVlZAwiwz2rwwZgXrbpyhcg== - CONSUMER_IH_APIKEY: ZGlkOndlYjpsb2NhbC11c2VyLWlkaHViOnVzZXI6Y29uc3VtZXI=.ObFly02OtymUNRE43uH9SblWVtsZH0NMddgm1dFYQXRekO3qXX+rHFV7NvM+DUW3lcA2PbILt5rwWYEqm7WNgw== - PROVIDER_IH_APIKEY: ZGlkOndlYjpsb2NhbC11c2VyLWlkaHViOnVzZXI6cHJvdmlkZXI=.Knip+hedL63qedBQfOvZhhrF2ooSCfP2YgjONvtmehofor2ejdw/en0MAXDBZEcXrCwYNppDMNFDsRlG5rB/Mw== + ISSUER_APIKEY: Y29uLXgtaXNzdWVy.1aCFefNebS1lGLNLoUzy08QX/sbFoBbRQYDUwbiMjX4dmWeaUFK22ZPzuDnsbDnevkNjqMBS9ZyiwI1aotkkvg== + CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.hUC2B8tRxG2UjB61EEdYSsiM/4FR28JnaxHQF8ysFSuoxWXEiFOtoFbryehp1ROL9ZV7bw8I38pMSUpeTAE5Hw== + PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.eYTCKEe5yXxYAeMt0d4T4iZMlT1c9avZWm28t7BhBf6VExe0VDO46DW0PCsDqr9q38IVvS3STKeZUxmSCrMGCw== VAULTURL: http://localhost:8200 - CONSUMER_STS_SECRET: VD0q6jOEyslSeFV2 - PROVIDER_STS_SECRET: XjDk9ncaJSrSfkQW + CONSUMER_STS_SECRET: 9IbwKpY5xlssMges + PROVIDER_STS_SECRET: rGBrdq1atB6ecJhv PROVIDER_MANAGEMENT: http://localhost:39010/management CONSUMER_MANAGEMENT: http://localhost:29010/management PROVIDER_DATAPLANE_PUBLIC: http://localhost:9500/public @@ -22,14 +22,19 @@ vars { ISS_ID: did:web:local-issuer-wallet:con-x-issuer CONS_ID: did:web:consumer-wallet:user:consumer PROV_ID: did:web:provider-wallet:user:provider - cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIja2V5LTEiLCJhbGciOiJFZDI1NTE5In0.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIiLCJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLWlkaHViOnVzZXI6cHJvdmlkZXIiLCJuYmYiOjE3NzAyNzg5NTQsInNjb3BlIjoib3JnLmVjbGlwc2UudHJhY3R1c3gudmMudHlwZTpNZW1iZXJzaGlwQ3JlZGVudGlhbDpyZWFkIiwiaXNzIjoiZGlkOndlYjpjb25zdW1lci1pZGh1Yjp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzcwMjc5MjU0LCJpYXQiOjE3NzAyNzg5NTQsImp0aSI6ImFjY2Vzc3Rva2VuLTcxYThmNTA5LTgwYTktNDMwZC1iMjU0LTMxNGFiYTBkNjY5OSJ9.ipRpdi_Ekh7y4IIqhqLgHU35Fn7NNkv6e6hILXy8pZObdy33y3MKppI61424eyHwqzmh7X2kwV2S5gDy3aOKCQ - prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLWlkaHViOnVzZXI6cHJvdmlkZXIja2V5LTEiLCJhbGciOiJFZDI1NTE5In0.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLWlkaHViOnVzZXI6cHJvdmlkZXIiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIiLCJuYmYiOjE3NzAyNzg5NTUsImlzcyI6ImRpZDp3ZWI6cHJvdmlkZXItaWRodWI6dXNlcjpwcm92aWRlciIsImV4cCI6MTc3MDI3OTI1NSwiaWF0IjoxNzcwMjc4OTU1LCJqdGkiOiJkNjA0MTVjOS1kMGM0LTRiNWQtYjI4My01ZmNmYjhlMDY2OGQiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFdsa2FIVmlPblZ6WlhJNlkyOXVjM1Z0WlhJamEyVjVMVEVpTENKaGJHY2lPaUpGWkRJMU5URTVJbjAuZXlKaGRXUWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMV2xrYUhWaU9uVnpaWEk2WTI5dWMzVnRaWElpTENKemRXSWlPaUprYVdRNmQyVmlPbkJ5YjNacFpHVnlMV2xrYUhWaU9uVnpaWEk2Y0hKdmRtbGtaWElpTENKdVltWWlPakUzTnpBeU56ZzVOVFFzSW5OamIzQmxJam9pYjNKbkxtVmpiR2x3YzJVdWRISmhZM1IxYzNndWRtTXVkSGx3WlRwTlpXMWlaWEp6YUdsd1EzSmxaR1Z1ZEdsaGJEcHlaV0ZrSWl3aWFYTnpJam9pWkdsa09uZGxZanBqYjI1emRXMWxjaTFwWkdoMVlqcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94Tnpjd01qYzVNalUwTENKcFlYUWlPakUzTnpBeU56ZzVOVFFzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFRjeFlUaG1OVEE1TFRnd1lUa3RORE13WkMxaU1qVTBMVE14TkdGaVlUQmtOalk1T1NKOS5pcFJwZGlfRWtoN3k0SUlxaHFMZ0hVMzVGbjdOTmt2NmU2aElMWHk4cFpPYmR5MzN5M01LcHBJNjE0MjRleUh3cXptaDdYMmt3VjJTNWdEeTNhT0tDUSJ9.42YDTnuzZ0RprqLjFw6hUoAXrgpPxyzKNFrqLdbWz6HXAiujkq32QAFU-M9gtQ0hMNcjshRZUX6DryBWxhGNDw - offerId: MQ==:YXNzZXRJZA==:MDFhN2ZjYWYtODgzOS00N2JmLTllZDAtM2Y0YjliMTFiOWM5 - negotiation-id: 3745ad50-6b99-4142-bf1a-509b3d0e313d - contractId: 3345f7f1-f735-4c92-8aa2-6e137203b2f9 - transferId: 3ebe55c6-900a-4c03-b213-20c5c3f0274d - pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLWlkaHViOnVzZXI6Y29uc3VtZXIiLCJzdWIiOiJhbm9ueW1vdXMiLCJpYXQiOjE3NzAyNzg5ODgsImp0aSI6IjUwYWQxMjk3LWZhN2QtNDI0ZC1hNjBhLTg5M2MwMGE4OTZhYyJ9.GkSz0qXhFmqPaLQpfPLkAvODX-iekoAQvLh3Kglhm7DApNF3PsGnv-Qzm7m8eNAqTUTWB9XXkRng_XqWmuAd-FWvzwG8d7ZaAahuykkOgX1W7vHWBMdJa-zvNm0cnzm-TQLWYCU-tDSKk_g_UrDUaFf9Jdq-avCoer3wcZrEmrf0K4o_WWs-l5hZEfDIOYHRsgoCY3P8pMcZYRjV57zdLUDl9SvLuCRR0ex0fKxJ2pb7mlaCL5ooD6fRaqWyrLvrIKZaDYfwKrX7IRJT9ePKyls9VKA9JBakh676L0jBr5-2TYG3uE9Xhyv4CZlqyck-_NyiL4Jao8-lL5FVCbPDVQ - ISS_PART_CONT: localissuer - CONS_PART_CONT: consumer - PROV_PART_CONT: provider + cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NjEwMzk3Miwic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg2MTA0MjcyLCJpYXQiOjE3ODYxMDM5NzIsImp0aSI6ImFjY2Vzc3Rva2VuLTI3MGY5NmFhLTM3NjMtNDJmMC05MTRjLWM3NWYxMjQ0YTM1ZSJ9.ARGJEYOXtQyj69n7khwJ6yJR2GdyUjxSTri82i0siQpnPhMtRekyvEBgKaDTM5_fkV__qNyBJjMcY54OczDxAw + prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NjEwMzk3MiwiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NjEwNDI3MiwiaWF0IjoxNzg2MTAzOTcyLCJqdGkiOiI4OWQ5MWQxYi01MWNmLTQ3MDgtYmU3Mi1iYmQxYjMxZWFkYTkiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROakV3TXprM01pd2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnMk1UQTBNamN5TENKcFlYUWlPakUzT0RZeE1ETTVOeklzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFRJM01HWTVObUZoTFRNM05qTXROREptTUMwNU1UUmpMV00zTldZeE1qUTBZVE0xWlNKOS5BUkdKRVlPWHRReWo2OW43a2h3SjZ5SlIyR2R5VWp4U1RyaTgyaTBzaVFwblBoTXRSZWt5dkVCZ0thRFRNNV9ma1ZfX3FOeUJKak1jWTU0T2N6RHhBdyJ9.luqjYsJK4o6nf7U4LGTKmhWWChkC3UP1UCfnvzTHphkHGvO2t1IErw0hZhw4Dch7x-VN25BUw50Yi3EvkXZbDg + offerId: Y29udHJhY3REZWZPbmU=:ZGVtb0Fzc2V0SWRPbmU=:MGM2NDMwYzEtYjkyMy00YzljLWIwZmQtZTkxY2JiZDY1OTQw + transferId: 111f4071-aa07-4ec5-b4b1-d6b707a0492c + pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg2MTA0MDQzLCJqdGkiOiIwMWFiZDViMC0zMDVhLTRkNjMtYjFiOS01OWEwZGE3YzM4YzcifQ.EDjnsI8btqpyoR-s6BLE-zUBUtkKxQ_yZOxI1jv5mDGR3pJcHmdvQDEnI0xm96DAnljp_MmxxMKVa6J5icDN6d58oT8Q96R-YV_ulQSBj9zEi051-_5doUtL3xwHUceqMF8audEhZqAcoshZvY68UzK8RlYL5qL7OrS_p2m39m_2PFPcxJVzoCMCR36whZdIRMTBQ_MhdQUCWFVrfuTnMRlQWLNltRmJSpgRr9jh2MSUff6bb3xk31enB76R1lJbhWTSo2CpUoVaf7SlJEYRKE2O9Ae26BZQySZWAkdWxADJY34H3EarbjuPLq5d_y76h9O3YfAtXmlnN9xsc-jXRQ + ISS_PART_CONT: con-x-issuer + CONS_PART_CONT: user-consumer + PROV_PART_CONT: user-provider + MEM_CRED_ID: https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential + CUSTOM_CRED_ID: https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential + catalogPermission: [{"action":"use","constraint":[{"and":[{"leftOperand":"https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.isMember","operator":"eq","rightOperand":"true"},{"leftOperand":"https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential.accessLevels.fooLevel","operator":"gteq","rightOperand":"2"}]}]}] + TARGET_ASSET_ID: demoAssetIdOne + negotiation-id: e1af3be4-cd54-492e-a5cd-c4783faf68fb + CRFORMAT: VC1_0_JWT + contractId: 36a3e2f4-6568-428c-b0f9-1d0dda89e467 } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/Get Consumer DID Doc.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/Get Consumer DID Doc.bru index 557b4b54a5..5a64175482 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/Get Consumer DID Doc.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/Get Consumer DID Doc.bru @@ -1,7 +1,7 @@ meta { name: Get Consumer DID Doc type: http - seq: 3 + seq: 4 } get { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru index 922b9e7b11..34ea31ccc1 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru @@ -14,8 +14,8 @@ body:json { { "issuerDid": "{{ISS_ID}}", "credentials": [{ - "format": "VC1_0_JWT", - "type": "MembershipCredential", + "format": "{{CRFORMAT}}", + "type": "{{MEM_CRED_ID}}", "id": "dev-credential-def-1" }] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru new file mode 100644 index 0000000000..035cd8b845 --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru @@ -0,0 +1,34 @@ +meta { + name: RequestCustomCredential + type: http + seq: 3 +} + +post { + url: {{CONSUMER_IDHUB_ID_API}}/v1alpha/participants/{{CONS_PART_CONT}}/credentials/request + body: json + auth: none +} + +body:json { + { + "issuerDid": "{{ISS_ID}}", + "credentials": [{ + "format": "{{CRFORMAT}}", + "type": "{{CUSTOM_CRED_ID}}", + "id": "customCredentialTypeId" + }] + } +} + +script:pre-request { + const cons_id = bru.getEnvVar("CONS_ID"); + const participantContext = cons_id.split(":").slice(3).join(":").replace(/:/g, "-"); + bru.setEnvVar("CONS_PART_CONT", participantContext); + req.setHeader("x-api-key", bru.getEnvVar("CONSUMER_IH_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret test.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret test.bru index 6e3a7833dc..62a9d72f22 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret test.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret test.bru @@ -1,7 +1,7 @@ meta { name: VaultSecret test type: http - seq: 5 + seq: 6 } get { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret.bru index b12519e226..bd91536f39 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/VaultSecret.bru @@ -1,7 +1,7 @@ meta { name: VaultSecret type: http - seq: 4 + seq: 5 } post { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru new file mode 100644 index 0000000000..6b414f9d47 --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru @@ -0,0 +1,40 @@ +meta { + name: createCustomAttestation + type: http + seq: 7 +} + +post { + url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/attestations + body: json + auth: none +} + +headers { + ~x-api-key: {{ISSUER_APIKEY}} +} + +body:json { + { + "attestationType": "dev", + "id": "customAttestationID", + "configuration": { + "default": { + "accessLevels": { + "fooLevel": "3", + "barLevel" : "1" + } + }, + "blackList": [] + } + } +} + +script:pre-request { + req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru new file mode 100644 index 0000000000..44c52954f7 --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru @@ -0,0 +1,42 @@ +meta { + name: createCustomCredentialDef + type: http + seq: 8 +} + +post { + url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/credentialdefinitions + body: json + auth: inherit +} + +body:json { + { + "attestations": [ + "customAttestationID" + ], + "credentialType": "{{CUSTOM_CRED_ID}}", + "id": "customCredentialTypeId", + "jsonSchema": "{}", + "jsonSchemaUrl": "https://example.com/schema/dev-credential.json", + "mappings": [ + { + "input": "content", + "output": "credentialSubject", + "required": true + } + ], + "rules": [], + "format": "{{CRFORMAT}}", + "validity": 15552000 + } +} + +script:pre-request { + req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru index 1e9801e71c..b037ec5a44 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru @@ -19,26 +19,8 @@ body:json { "attestationType": "dev", "id": "dev-def-1", "configuration": { - "{{CONS_ID}}": { - "isConsumer": true, - "isProvider": false, - "foo": { - "bar": 123 - } - }, - "{{PROV_ID}}": { - "isConsumer": false, - "isProvider": true, - "foo": { - "bar": 789 - } - }, "default": { - "isConsumer": false, - "isProvider": false, - "foo": { - "bar": 0 - } + "isMember": "true" }, "blackList": [] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru index fe36fe1f4e..f1f0b17da6 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru @@ -15,7 +15,7 @@ body:json { "attestations": [ "dev-def-1" ], - "credentialType": "MembershipCredential", + "credentialType": "{{MEM_CRED_ID}}", "id": "dev-credential-def-1", "jsonSchema": "{}", "jsonSchemaUrl": "https://example.com/schema/dev-credential.json", @@ -27,7 +27,7 @@ body:json { } ], "rules": [], - "format": "VC1_0_JWT", + "format": "{{CRFORMAT}}", "validity": 15552000 } } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/Get Provider DID Doc.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/Get Provider DID Doc.bru index 0187613b95..f8e5ded11c 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/Get Provider DID Doc.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/Get Provider DID Doc.bru @@ -1,7 +1,7 @@ meta { name: Get Provider DID Doc type: http - seq: 3 + seq: 4 } get { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru new file mode 100644 index 0000000000..dc360942ce --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru @@ -0,0 +1,35 @@ +meta { + name: RequestCustomCredential + type: http + seq: 3 +} + +post { + url: {{PROVIDER_IDHUB_ID_API}}/v1alpha/participants/{{PROV_PART_CONT}}/credentials/request + body: json + auth: none +} + +body:json { + { + "issuerDid": "{{ISS_ID}}", + "credentials": [{ + "format": "{{CRFORMAT}}", + "type": "{{CUSTOM_CRED_ID}}", + "id": "customCredentialTypeId" + }] + } +} + +script:pre-request { + const prov_id = bru.getEnvVar("PROV_ID"); + const participantContext = prov_id.split(":").slice(3).join(":").replace(/:/g, "-"); + bru.setEnvVar("PROV_PART_CONT", participantContext); + + req.setHeader("x-api-key", bru.getEnvVar("PROVIDER_IH_APIKEY")); +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru index b71fd56e0c..9ddde8ec3e 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru @@ -14,8 +14,8 @@ body:json { { "issuerDid": "{{ISS_ID}}", "credentials": [{ - "format": "VC1_0_JWT", - "type": "MembershipCredential", + "format": "{{CRFORMAT}}", + "type": "{{MEM_CRED_ID}}", "id": "dev-credential-def-1" }] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret test.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret test.bru index 03f3b64282..e2b7afd47b 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret test.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret test.bru @@ -1,7 +1,7 @@ meta { name: VaultSecret test type: http - seq: 5 + seq: 6 } get { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret.bru index b05fd6d48a..b68a9df128 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/VaultSecret.bru @@ -1,7 +1,7 @@ meta { name: VaultSecret type: http - seq: 4 + seq: 5 } post { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru index 16b2325aa7..a4dbd74c7b 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru @@ -15,7 +15,7 @@ body:form-urlencoded { client_secret: {{CONSUMER_STS_SECRET}} client_id: {{CONS_ID}} audience: {{PROV_ID}} - bearer_access_scope: org.eclipse.dspace.dcp.vc.type:MembershipCredential:read + bearer_access_scope: org.eclipse.dspace.dcp.vc.type:{{MEM_CRED_ID}}:read } script:post-response { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Get Credential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Get Credential.bru index 8ea42d7663..57d6e22f29 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Get Credential.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Get Credential.bru @@ -23,7 +23,7 @@ body:json { "type": "PresentationQueryMessage", "presentationDefinition": null, "scope": [ - "org.eclipse.dspace.dcp.vc.type:MembershipCredential:read" + "org.eclipse.dspace.dcp.vc.type:{{MEM_CRED_ID}}:read" ] } } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru index 80e72fa844..1b5b920f0a 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru @@ -28,9 +28,10 @@ body:json { "@context": "http://www.w3.org/ns/odrl.jsonld", "@id": "{{offerId}}", "@type": "Offer", + "permission": {{catalogPermission}}, "assigner": "{{PROV_ID}}", "assignee": "{{CONS_ID}}", - "target": "assetId" + "target": "{{TARGET_ASSET_ID}}" } } } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru index 128f08a1c7..eb6d6a38f0 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru @@ -22,17 +22,32 @@ body:json { } script:post-response { - const offerArray = res.getBody().dataset[0].hasPolicy; - var offerId; - res.getBody().dataset[0].hasPolicy.forEach(item => { - const type = item['@type']; - const id = item['@id']; - if (type != null && type == "Offer" && id != null) { - offerId = id; - } - }); - bru.setEnvVar("offerId", offerId); + const targetAssetId = bru.getEnvVar("TARGET_ASSET_ID"); + + var offerId; + var permissionArray; + var foundIt = false; + + res.getBody().dataset.forEach(dataset => { + if (dataset['@id'] == targetAssetId) { + dataset.hasPolicy.forEach(item => { + const type = item['@type']; + const id = item['@id']; + if (type == "Offer" && id != null) { + offerId = id; + permissionArray = item.permission; + foundIt = true; + } + }); + } + }); + if (!foundIt) { + offerId = ""; + permissionArray = []; + } + bru.setEnvVar("offerId", offerId); + bru.setEnvVar("catalogPermission", JSON.stringify(permissionArray)); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreateContractDefinition.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreateContractDefinition.bru deleted file mode 100644 index e7b3b069fa..0000000000 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreateContractDefinition.bru +++ /dev/null @@ -1,28 +0,0 @@ -meta { - name: CreateContractDefinition - type: http - seq: 3 -} - -post { - url: {{PROVIDER_MANAGEMENT}}/v3/contractdefinitions - body: json - auth: inherit -} - -body:json { - { - "@context": { - "@vocab": "https://w3id.org/edc/v0.0.1/ns/" - }, - "@id": "1", - "accessPolicyId": "aPolicy", - "contractPolicyId": "aPolicy", - "assetsSelector": [] - } -} - -settings { - encodeUrl: true - timeout: 0 -} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreatePolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru similarity index 51% rename from edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreatePolicy.bru rename to edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru index ede9c2a378..4db4cab5be 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreatePolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru @@ -1,5 +1,5 @@ meta { - name: CreatePolicy + name: CreateAccessPolicy type: http seq: 2 } @@ -16,11 +16,24 @@ body:json { "@vocab": "https://w3id.org/edc/v0.0.1/ns/", "odrl": "http://www.w3.org/ns/odrl/2/" }, - "@id": "aPolicy", + "@id": "accessPolicyOne", "policy": { "@context": "http://www.w3.org/ns/odrl.jsonld", "@type": "Set", - "permission": [], + "permission": [ + { + "action": "http://www.w3.org/ns/odrl/2/use", + "constraint": { + "and": [ + { + "leftOperand": "{{MEM_CRED_ID}}.isMember", + "operator": "eq", + "rightOperand": "true" + } + ] + } + } + ], "prohibition": [], "obligation": [] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreateAsset.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAsset.bru similarity index 95% rename from edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreateAsset.bru rename to edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAsset.bru index df3fea69fc..d30aeddc22 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/CreateAsset.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAsset.bru @@ -15,7 +15,7 @@ body:json { "@context": { "@vocab": "https://w3id.org/edc/v0.0.1/ns/" }, - "@id": "assetId", + "@id": "demoAssetIdOne", "properties": { "name": "product description", "contenttype": "application/json" diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractDefinition.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractDefinition.bru new file mode 100644 index 0000000000..d5a812fac5 --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractDefinition.bru @@ -0,0 +1,35 @@ +meta { + name: CreateContractDefinition + type: http + seq: 4 +} + +post { + url: {{PROVIDER_MANAGEMENT}}/v3/contractdefinitions + body: json + auth: inherit +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@id": "contractDefOne", + "accessPolicyId": "accessPolicyOne", + "contractPolicyId": "contractPolicyOne", + "assetsSelector": { + "@type": "Criterion", + "operandLeft": "https://w3id.org/edc/v0.0.1/ns/id", + "operator": "in", + "operandRight": [ + "demoAssetIdOne" + ] + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru new file mode 100644 index 0000000000..fcbccae65e --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru @@ -0,0 +1,51 @@ +meta { + name: CreateContractPolicy + type: http + seq: 3 +} + +post { + url: {{PROVIDER_MANAGEMENT}}/v3/policydefinitions + body: json + auth: inherit +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "contractPolicyOne", + "policy": { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@type": "Set", + "permission": [ + { + "action": "http://www.w3.org/ns/odrl/2/use", + "constraint": { + "and": [ + { + "leftOperand": "{{MEM_CRED_ID}}.isMember", + "operator": "eq", + "rightOperand": "true" + }, + { + "leftOperand": "{{CUSTOM_CRED_ID}}.accessLevels.fooLevel", + "operator": "gteq", + "rightOperand": "2" + } + ] + } + } + ], + "prohibition": [], + "obligation": [] + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/folder.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/folder.bru new file mode 100644 index 0000000000..577a231927 --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/folder.bru @@ -0,0 +1,8 @@ +meta { + name: createAssetOne + seq: 1 +} + +auth { + mode: inherit +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru new file mode 100644 index 0000000000..27589989a8 --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru @@ -0,0 +1,46 @@ +meta { + name: CreateAccessPolicy + type: http + seq: 2 +} + +post { + url: {{PROVIDER_MANAGEMENT}}/v3/policydefinitions + body: json + auth: inherit +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "accessPolicyTwo", + "policy": { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@type": "Set", + "permission": [ + { + "action": "http://www.w3.org/ns/odrl/2/use", + "constraint": { + "and": [ + { + "leftOperand": "{{MEM_CRED_ID}}.isMember", + "operator": "eq", + "rightOperand": "true" + } + ] + } + } + ], + "prohibition": [], + "obligation": [] + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAsset.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAsset.bru new file mode 100644 index 0000000000..03e3fae9ee --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAsset.bru @@ -0,0 +1,39 @@ +meta { + name: CreateAsset + type: http + seq: 1 +} + +post { + url: {{PROVIDER_MANAGEMENT}}/v3/assets + body: json + auth: inherit +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@id": "demoAssetIdTwo", + "properties": { + "name": "product description", + "contenttype": "application/json" + }, + "dataAddress": { + "type": "HttpData", + "name": "Test asset", + "baseUrl": "https://jsonplaceholder.typicode.com/users", + "proxyPath": "true", + "proxyMethod": "true", + "proxyBody": "true", + "authKey": "x-api-key", + "authCode": "someAuthCode" + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractDefinition.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractDefinition.bru new file mode 100644 index 0000000000..f35c4f1e6c --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractDefinition.bru @@ -0,0 +1,35 @@ +meta { + name: CreateContractDefinition + type: http + seq: 4 +} + +post { + url: {{PROVIDER_MANAGEMENT}}/v3/contractdefinitions + body: json + auth: inherit +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/" + }, + "@id": "contractDefTwo", + "accessPolicyId": "accessPolicyTwo", + "contractPolicyId": "contractPolicyTwo", + "assetsSelector": { + "@type": "Criterion", + "operandLeft": "https://w3id.org/edc/v0.0.1/ns/id", + "operator": "in", + "operandRight": [ + "demoAssetIdTwo" + ] + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru new file mode 100644 index 0000000000..fd6b125c29 --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru @@ -0,0 +1,51 @@ +meta { + name: CreateContractPolicy + type: http + seq: 3 +} + +post { + url: {{PROVIDER_MANAGEMENT}}/v3/policydefinitions + body: json + auth: inherit +} + +body:json { + { + "@context": { + "@vocab": "https://w3id.org/edc/v0.0.1/ns/", + "odrl": "http://www.w3.org/ns/odrl/2/" + }, + "@id": "contractPolicyTwo", + "policy": { + "@context": "http://www.w3.org/ns/odrl.jsonld", + "@type": "Set", + "permission": [ + { + "action": "http://www.w3.org/ns/odrl/2/use", + "constraint": { + "and": [ + { + "leftOperand": "{{MEM_CRED_ID}}.isMember", + "operator": "eq", + "rightOperand": "true" + }, + { + "leftOperand": "{{CUSTOM_CRED_ID}}.accessLevels.fooLevel", + "operator": "gteq", + "rightOperand": "4" + } + ] + } + } + ], + "prohibition": [], + "obligation": [] + } + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/folder.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/folder.bru new file mode 100644 index 0000000000..b15fc1c55a --- /dev/null +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/folder.bru @@ -0,0 +1,8 @@ +meta { + name: createAssetTwo + seq: 2 +} + +auth { + mode: inherit +} diff --git a/edc-extensions/basic-abac/README.md b/edc-extensions/basic-abac/README.md new file mode 100644 index 0000000000..7b7dd92cd5 --- /dev/null +++ b/edc-extensions/basic-abac/README.md @@ -0,0 +1,99 @@ +# Basic Abac Extension (draft version) + +This extension allows you to create policy definitions that enable attribute-based access control. + +That means, you have to specify a credential type by setting a leftOperand that designates a fully qualified credential +type identifier, and that needs to be appended by a "pointer" that allows navigating through the structure of the credential +subject that is used in that credential type. + +Consider the following example: + +``` + "permission": [ + { + "action": "use", + "constraint": [ + { + "and": [ + { + "leftOperand": "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.isMember", + "operator": "eq", + "rightOperand": "true" + }, + { + "leftOperand": "https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.accessLevel", + "operator": "gteq", + "rightOperand": "4" + } + ] + } + ] + } + ] +``` + +Here, we have "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.isMember" first. The last +path segment must have a "pointer" suffix (see more on that below). In this case, the suffix would be ".isMember" + + +### Pattern matching + +If the leftOperand of a policy definition matches a certain regex pattern (current defined in the [BasicAbacUtils](./src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java) - class). + +```java + public static final String BASIC_ABAC_REGEX = + "^(?=.*/credentials)https://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?::[0-9]+)?/.*/[A-Z][^\\s/.]*(?:\\.[^\\s/.]+)+$"; +``` + +In essence, this regex matches if the target string +- is a valid https-url and +- that has the "magic word" "/credentials" somewhere in its path segments and +- whose last path segment starts with a capital letter and +- the suffix of the last path segment must provide a "pointer" to a value in the credential subject + of the corresponding verifiable credential. + +The suffix of the last path segment can and should be used to provide a "pointer" to a value in the credential subject +of the corresponding verifiable credential. + +#### Examples (matching the regex) + +- "https://w3id.org/constructx/credentials/v1.0/Foo.fooLevel" +- "https://w3id.org/constructx/credentials/v1.0/Bar.nestedObject.barLevel" +- "https://my-domain.com:8080/credentials/Foo.fooLevel" + + +#### Examples (NOT matching the regex) + +- "https://w3id.org/constructx/credentials/v1.0/Foo" (missing "pointer" suffix) +- "http://example.org/credentials/Foo.fooLevel" (http is not https) +- "https://w3id.org/constructx/credentials/v1.0/moo.mooLevel" (no capital letter at start of last path segment) +- "https://w3id.org/constructx/policies/v1.0/Foo.fooLevel" (missing "/credentials" magic word) + +#### Understanding the "pointer suffix" + +In order to create sensible ABAC policies, you need to be aware of the given structure of the credential subject in the +verifiable credential. Continuing the above-mentioned example, let's suppose that the credential subject of the "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential" is structured like this: + +``` + { + "id": "did:web:consumer-wallet:user:consumer", + "isMember": "true" + } +``` + +Then, the given suffix (".isMember") is pointing at value of the "isMember" field that JSON object, i.e. the string "true". +Since our policy definition (see above again) is expecting the value "true" to be equal to whatever is found in the value +of the credential subject, everything is fine here. + +Now let's expand this further to the second ABAC policy condition. Here, we see "https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.accessLevel". Assuming that our consumer-participant +does have this credential, but the relevant trusted issuer gave it to him with this credential subject ... + +``` + { + "id": "did:web:consumer-wallet:user:consumer", + "accessLevel": "3" + } +``` + +... then the evaluation would find that an accessLevel of 4 was required, but only level 3 was granted to him. Which means, +that this consumer participant is out of luck here, and he will be denied if he attempts to negotiate for this contract offer. diff --git a/edc-extensions/basic-abac/build.gradle.kts b/edc-extensions/basic-abac/build.gradle.kts new file mode 100644 index 0000000000..b78ccc964f --- /dev/null +++ b/edc-extensions/basic-abac/build.gradle.kts @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 + */ + +plugins { + id("java") + id("application") +} + +repositories { mavenCentral() } + +val edcVersion = "0.15.1" + +dependencies { + implementation("org.eclipse.edc:core-spi:${edcVersion}") + implementation("org.eclipse.edc:policy-engine-spi:${edcVersion}") + implementation("org.eclipse.edc:policy-spi:${edcVersion}") + implementation("org.eclipse.edc:participant-spi:${edcVersion}") + implementation("org.eclipse.edc:request-policy-context-spi:${edcVersion}") + implementation("org.eclipse.edc:catalog-spi:${edcVersion}") + implementation("org.eclipse.edc:contract-spi:${edcVersion}") + implementation("org.eclipse.edc:verifiable-credentials-spi:${edcVersion}") + + testImplementation("org.eclipse.edc:junit:${edcVersion}") { + exclude(group = "org.junit.jupiter") + exclude(group = "org.junit.platform") + exclude(group = "org.junit") + } +} + diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java new file mode 100644 index 0000000000..73123e358f --- /dev/null +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java @@ -0,0 +1,103 @@ +package de.fraunhofer.isst.edc.extension.basic_abac.dev; + +import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; +import org.eclipse.edc.participant.spi.ParticipantAgentPolicyContext; +import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; +import org.eclipse.edc.policy.model.Operator; +import org.eclipse.edc.policy.model.Permission; + +import java.util.List; +import java.util.function.Predicate; + +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.BASIC_ABAC_PATTERN; +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.getPathObject; +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.truncateLastPathSegment; + +public class BasicAbacCredentialConstraintFunction implements DynamicAtomicConstraintRuleFunction { + + + @Override + public boolean evaluate(Object leftValue, Operator operator, Object rightValue, Permission rule, C context) { + double expectedNumber = 0; + boolean isNumericCondition = false; + try { + expectedNumber = Double.parseDouble(rightValue.toString()); + isNumericCondition = true; + } catch (Exception e) { + } + final double finalExpectedNumber = expectedNumber; + Predicate numericPredicate = switch (operator) { + case EQ -> n -> n == finalExpectedNumber; + case NEQ -> n -> n != finalExpectedNumber; + case GEQ -> n -> n >= finalExpectedNumber; + case LEQ -> n -> n <= finalExpectedNumber; + case GT -> n -> n > finalExpectedNumber; + case LT -> n -> n < finalExpectedNumber; + + default -> n -> false; + }; + + List verifiableCredentialList = null; + String potentialProblem = "No Credential Claims found"; + try { + var participantAgent = context.participantAgent(); + var supposedToBeCredentialList = participantAgent.getClaims().get("vc"); + if (supposedToBeCredentialList instanceof List credentialList) { + if (credentialList.stream().allMatch(it -> it instanceof VerifiableCredential)) { + verifiableCredentialList = (List) credentialList; + } + } + } catch (Exception e) { + context.reportProblem(potentialProblem); + return false; + } + if (verifiableCredentialList == null || verifiableCredentialList.isEmpty()) { + context.reportProblem(potentialProblem); + return false; + } + + String credentialFullyQualifiedName = truncateLastPathSegment(leftValue); + + for (var verifiableCredential : verifiableCredentialList) { + if (verifiableCredential.getType().contains(credentialFullyQualifiedName)) { + for (var credentialSubject : verifiableCredential.getCredentialSubject()) { + var claims = credentialSubject.getClaims(); + Object relevantClaim = getPathObject(leftValue, claims); + if (isNumericCondition) { + try { + double claimInteger = Double.parseDouble(relevantClaim.toString()); + return numericPredicate.test(claimInteger); + } catch (Exception e) { + } + } else { + // can handle only equal or not equal at the moment + if (relevantClaim instanceof String) { + // probably unsafe for anything other than String type + return switch (operator) { + case EQ -> rightValue.equals(relevantClaim); + case NEQ -> !rightValue.equals(relevantClaim); + default -> false; + }; + } + } + } + } + } + return false; + } + + @Override + public boolean canHandle(Object leftValue) { + if (leftValue instanceof String leftValueString) { + return BASIC_ABAC_PATTERN.matcher(leftValueString).matches(); + } + return false; + } + + + + @Override + public String name() { + return this.getClass().getSimpleName() + "-Rule"; + } +} diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java new file mode 100644 index 0000000000..f69c1511f2 --- /dev/null +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 de.fraunhofer.isst.edc.extension.basic_abac.dev; + +import org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext; +import org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext; +import org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext; +import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; +import org.eclipse.edc.policy.model.Permission; +import org.eclipse.edc.runtime.metamodel.annotation.Extension; +import org.eclipse.edc.runtime.metamodel.annotation.Inject; +import org.eclipse.edc.spi.system.ServiceExtension; +import org.eclipse.edc.spi.system.ServiceExtensionContext; + +import java.util.Set; + +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.BASIC_ABAC_PATTERN; +import static org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext.CATALOG_SCOPE; +import static org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext.NEGOTIATION_SCOPE; +import static org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext.TRANSFER_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestCatalogPolicyContext.CATALOGING_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestContractNegotiationPolicyContext.CONTRACT_NEGOTIATION_REQUEST_SCOPE; +import static org.eclipse.edc.policy.context.request.spi.RequestTransferProcessPolicyContext.TRANSFER_PROCESS_REQUEST_SCOPE; +import static org.eclipse.edc.policy.model.OdrlNamespace.ODRL_SCHEMA; + + +@Extension("Basic Abac Extension") +public class BasicAbacExtension implements ServiceExtension { + + @Inject + private PolicyEngine policyEngine; + + @Inject + private RuleBindingRegistry ruleBindingRegistry; + + + @Override + public String name() { + return "Basic Abac Extension"; + } + + @Override + public void initialize(ServiceExtensionContext context) { + var monitor = context.getMonitor(); + policyEngine.registerPostValidator(RequestCatalogPolicyContext.class, new BasicAbacPolicyPostValidator<>(monitor)); + policyEngine.registerPostValidator(RequestContractNegotiationPolicyContext.class, new BasicAbacPolicyPostValidator<>(monitor)); + policyEngine.registerPostValidator(RequestTransferProcessPolicyContext.class, new BasicAbacPolicyPostValidator<>(monitor)); + + for (var clazz : new Class[]{ + CatalogPolicyContext.class, + ContractNegotiationPolicyContext.class, + TransferProcessPolicyContext.class}) { + + policyEngine.registerFunction(clazz, Permission.class, new BasicAbacCredentialConstraintFunction<>()); + } + + ruleBindingRegistry.dynamicBind(str -> { + if (BASIC_ABAC_PATTERN.matcher(str).matches()) { + return Set.of( + CATALOGING_REQUEST_SCOPE, + CONTRACT_NEGOTIATION_REQUEST_SCOPE, + TRANSFER_PROCESS_REQUEST_SCOPE, + CATALOG_SCOPE, + NEGOTIATION_SCOPE, + TRANSFER_SCOPE + ); + } + return Set.of(); + }); + + String ODRL_USE = ODRL_SCHEMA + "use"; + ruleBindingRegistry.bind(ODRL_USE, CATALOG_SCOPE); + ruleBindingRegistry.bind(ODRL_USE, NEGOTIATION_SCOPE); + ruleBindingRegistry.bind(ODRL_USE, TRANSFER_SCOPE); + } +} diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java new file mode 100644 index 0000000000..be9e3c7b4d --- /dev/null +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java @@ -0,0 +1,76 @@ +package de.fraunhofer.isst.edc.extension.basic_abac.dev; + +import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; +import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; +import org.eclipse.edc.policy.model.AtomicConstraint; +import org.eclipse.edc.policy.model.Constraint; +import org.eclipse.edc.policy.model.LiteralExpression; +import org.eclipse.edc.policy.model.MultiplicityConstraint; +import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.spi.monitor.Monitor; + +import java.util.HashSet; +import java.util.Set; + +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.BASIC_ABAC_PATTERN; +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.CONX_MEMBERSHIP_SCOPE; +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.truncateLastPathSegment; + +public class BasicAbacPolicyPostValidator implements PolicyValidatorRule { + private final Monitor monitor; + + public BasicAbacPolicyPostValidator(Monitor monitor) { + this.monitor = monitor.withPrefix(this.getClass().getSimpleName()); + } + + + @Override + public Boolean apply(Policy policy, C requestPolicyContext) { + var foundAbacCredentialTypeIdentifiers = explorePolicy(policy); + requestPolicyContext.requestScopeBuilder().scopes(foundAbacCredentialTypeIdentifiers); + monitor.debug("Found credential type identifiers " + foundAbacCredentialTypeIdentifiers); + + // Minimally require CON-X Membership Credential + requestPolicyContext.requestScopeBuilder().scope(CONX_MEMBERSHIP_SCOPE); + return true; + } + + private Set explorePolicy(Policy policy) { + Set output = new HashSet<>(); + for (var permission : policy.getPermissions()) { + for (var constraint : permission.getConstraints()) { + output.addAll(exploreConstraint(constraint)); + } + } + return output; + } + + private Set exploreConstraint(Constraint constraint) { + Set output = new HashSet<>(); + if (constraint instanceof MultiplicityConstraint multiplicityConstraint) { + for (var multiConstraint : multiplicityConstraint.getConstraints()) { + output.addAll(exploreConstraint(multiConstraint)); + } + } else if (constraint instanceof AtomicConstraint atomicConstraint) { + if (atomicConstraint.getLeftExpression() instanceof LiteralExpression literalExpression) { + if (isCredentialConstraint(literalExpression.getValue())) { + output.add("org.eclipse.dspace.dcp.vc.type:" + truncateLastPathSegment(literalExpression.getValue()) + ":read"); + } + } + } + return output; + } + + private boolean isCredentialConstraint(Object leftExpression) { + if (leftExpression instanceof String leftExpressionString) { + return BASIC_ABAC_PATTERN.matcher(leftExpressionString).matches(); + } + return false; + } + + + @Override + public String name() { + return this.getClass().getName() + "-Rule"; + } +} diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java new file mode 100644 index 0000000000..5c6c75f267 --- /dev/null +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java @@ -0,0 +1,91 @@ +package de.fraunhofer.isst.edc.extension.basic_abac.dev; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class BasicAbacUtils { + + public static final String BASIC_ABAC_REGEX = + "^(?=.*/credentials)https://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?::[0-9]+)?/.*/[A-Z][^\\s/.]*(?:\\.[^\\s/.]+)+$"; + public static final Pattern BASIC_ABAC_PATTERN = Pattern.compile(BASIC_ABAC_REGEX); + + public static final String CONX_MEMBERSHIP_SCOPE = "org.eclipse.dspace.dcp.vc.type:https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential:read"; + + /** + * This method takes as input + *

+ * - the leftValue Parameter of the evaluate method from the DynamicAtomicConstraintRuleFunction interface. + *

+ * - the credential subject of a verifiable credential (as processed by the edc framework, i.e. as a java.util.Map) + *

+ * The leftValue is expected to have matched the BASIC_ABAC_REGEX, containing a pointer suffix at the end. + * That suffix will be interpreted by this method to navigate through the credential subject (also see the readme). + * + * @param leftValue + * @param map + * @return the object (which might be a string, a list, a map) or null if nothing could be found + */ + public static Object getPathObject(Object leftValue, Map map) { + String jsonPath = leftValue.toString().replace(truncateLastPathSegment(leftValue) + ".", ""); + if (jsonPath.isBlank()) return null; + Object current = map; + String[] segments = jsonPath.split("\\."); + for (int i = 0; i < segments.length; i++) { + String fieldName = segments[i].strip(); + Integer arrayIndex = null; + int bracketStart = fieldName.indexOf("["); + int bracketEnd = fieldName.indexOf("]"); + if (bracketStart >= 0 && bracketEnd > bracketStart) { + String indexString = fieldName.substring(bracketStart + 1, bracketEnd); + fieldName = fieldName.substring(0, bracketStart); + arrayIndex = Integer.parseInt(indexString); + } + if (!fieldName.isBlank() && current instanceof Map nestedMap) { + current = nestedMap.get(fieldName); + } + if (current == null) { + return null; + } + if (arrayIndex != null) { + if (current instanceof List nestedList && arrayIndex >= 0 && arrayIndex < nestedList.size()) { + try { + current = nestedList.get(arrayIndex); + } catch (Exception e) { + return null; + } + } else { + return null; + } + } + + } + return current; + } + + /** + * This method expects the leftValue Parameter of the evaluate method from the DynamicAtomicConstraintRuleFunction + * interface as input. It is also expected that the left expression was matched by the BASIC_ABAC_REGEX. + * + * It will remove the pointer suffix, effectively returning the fully qualified name of the credential. + * + * @param leftExpression + * @return + */ + public static String truncateLastPathSegment(Object leftExpression) { + if (leftExpression instanceof String url) { + int lastSlashIndex = url.lastIndexOf('/'); + if (lastSlashIndex == -1) { + return url; + } + String beforeLastSegment = url.substring(0, lastSlashIndex + 1); + String lastSegment = url.substring(lastSlashIndex + 1); + int firstDotIndex = lastSegment.indexOf('.'); + if (firstDotIndex == -1) { + return url; + } + return beforeLastSegment + lastSegment.substring(0, firstDotIndex); + } + return null; + } +} diff --git a/edc-extensions/basic-abac/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/edc-extensions/basic-abac/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension new file mode 100644 index 0000000000..6dcb9a0e46 --- /dev/null +++ b/edc-extensions/basic-abac/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -0,0 +1,20 @@ +################################################################################# +# Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) +# +# 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 +################################################################################# + +de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacExtension \ No newline at end of file diff --git a/edc-extensions/basic-abac/src/test/java/BasicAbacTest.java b/edc-extensions/basic-abac/src/test/java/BasicAbacTest.java new file mode 100644 index 0000000000..1ceb34a441 --- /dev/null +++ b/edc-extensions/basic-abac/src/test/java/BasicAbacTest.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 + */ + +import static org.mockito.Mockito.mock; + +public class BasicAbacTest { + + // TODO: unit tests for util methods and the regex + +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 16e7eb06b3..82f4d72e88 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -104,6 +104,7 @@ include(":edc-extensions:agreements:retirement-evaluation-core") include(":edc-extensions:agreements:retirement-evaluation-api") include(":edc-extensions:agreements:retirement-evaluation-spi") include(":edc-extensions:agreements:retirement-evaluation-store-sql") +include(":edc-extensions:basic-abac") // extensions - data plane include(":edc-extensions:dataplane:dataplane-proxy:edc-dataplane-proxy-consumer-api") From 38ab8460f9871812674f8777dd081d4afed4271d Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Thu, 13 Aug 2026 09:17:50 +0200 Subject: [PATCH 02/11] feat: added draft for list operation handling --- .../environments/local-con-x-env.bru | 28 +-- .../createCustomAttestation.bru | 6 +- .../Prepare Issuer/createDevAttestation.bru | 2 +- .../createAssetOne/CreateAccessPolicy.bru | 4 +- .../createAssetOne/CreateContractPolicy.bru | 9 +- .../createAssetTwo/CreateAccessPolicy.bru | 2 +- .../createAssetTwo/CreateContractPolicy.bru | 11 +- ...BasicAbacCredentialConstraintFunction.java | 221 ++++++++++++------ .../dev/BasicAbacPolicyPostValidator.java | 14 +- .../basic_abac/dev/BasicAbacUtils.java | 124 +++++++++- 10 files changed, 305 insertions(+), 116 deletions(-) diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru index a7fd70aaf0..b626be7df9 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru @@ -6,12 +6,12 @@ vars { CONSUMER_IDHUB_ID_API: http://localhost:20100/api/identity CONSUMER_IDHUB_STS_API: http://localhost:20500/api/sts CONSUMER_IDHUB_CREDS_API: http://localhost:20600/api/credentials - ISSUER_APIKEY: Y29uLXgtaXNzdWVy.1aCFefNebS1lGLNLoUzy08QX/sbFoBbRQYDUwbiMjX4dmWeaUFK22ZPzuDnsbDnevkNjqMBS9ZyiwI1aotkkvg== - CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.hUC2B8tRxG2UjB61EEdYSsiM/4FR28JnaxHQF8ysFSuoxWXEiFOtoFbryehp1ROL9ZV7bw8I38pMSUpeTAE5Hw== - PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.eYTCKEe5yXxYAeMt0d4T4iZMlT1c9avZWm28t7BhBf6VExe0VDO46DW0PCsDqr9q38IVvS3STKeZUxmSCrMGCw== + ISSUER_APIKEY: Y29uLXgtaXNzdWVy.WpbGU5rGkKAXdl7Te2cRvWOqeqOk14IC7DqjJ0Ehuxh4eLE3QdT839DalHJAxQjtBtRUXTNhDpH592K9xayIIg== + CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.w0q1kjBtayaZzTuBhwg4RlaXj35MVu6xzjiSrWca6GmktFbeFW6Jo8H5v72mZj3eR77jG/9XMmFeEfLQOD8D8Q== + PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.whxO1D7Nrgmro7r+jVV4oEsLYtIUK6P+bDP3Yi3/zAbdgFh53hEYoRh5VuIwMS/206Cdrjm7uSTCRzWyGPL7bQ== VAULTURL: http://localhost:8200 - CONSUMER_STS_SECRET: 9IbwKpY5xlssMges - PROVIDER_STS_SECRET: rGBrdq1atB6ecJhv + CONSUMER_STS_SECRET: DYgOAkYJxg1EiBUi + PROVIDER_STS_SECRET: FpGv2Gfb1wHnUSOn PROVIDER_MANAGEMENT: http://localhost:39010/management CONSUMER_MANAGEMENT: http://localhost:29010/management PROVIDER_DATAPLANE_PUBLIC: http://localhost:9500/public @@ -22,19 +22,19 @@ vars { ISS_ID: did:web:local-issuer-wallet:con-x-issuer CONS_ID: did:web:consumer-wallet:user:consumer PROV_ID: did:web:provider-wallet:user:provider - cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NjEwMzk3Miwic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg2MTA0MjcyLCJpYXQiOjE3ODYxMDM5NzIsImp0aSI6ImFjY2Vzc3Rva2VuLTI3MGY5NmFhLTM3NjMtNDJmMC05MTRjLWM3NWYxMjQ0YTM1ZSJ9.ARGJEYOXtQyj69n7khwJ6yJR2GdyUjxSTri82i0siQpnPhMtRekyvEBgKaDTM5_fkV__qNyBJjMcY54OczDxAw - prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NjEwMzk3MiwiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NjEwNDI3MiwiaWF0IjoxNzg2MTAzOTcyLCJqdGkiOiI4OWQ5MWQxYi01MWNmLTQ3MDgtYmU3Mi1iYmQxYjMxZWFkYTkiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROakV3TXprM01pd2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnMk1UQTBNamN5TENKcFlYUWlPakUzT0RZeE1ETTVOeklzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFRJM01HWTVObUZoTFRNM05qTXROREptTUMwNU1UUmpMV00zTldZeE1qUTBZVE0xWlNKOS5BUkdKRVlPWHRReWo2OW43a2h3SjZ5SlIyR2R5VWp4U1RyaTgyaTBzaVFwblBoTXRSZWt5dkVCZ0thRFRNNV9ma1ZfX3FOeUJKak1jWTU0T2N6RHhBdyJ9.luqjYsJK4o6nf7U4LGTKmhWWChkC3UP1UCfnvzTHphkHGvO2t1IErw0hZhw4Dch7x-VN25BUw50Yi3EvkXZbDg - offerId: Y29udHJhY3REZWZPbmU=:ZGVtb0Fzc2V0SWRPbmU=:MGM2NDMwYzEtYjkyMy00YzljLWIwZmQtZTkxY2JiZDY1OTQw - transferId: 111f4071-aa07-4ec5-b4b1-d6b707a0492c - pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg2MTA0MDQzLCJqdGkiOiIwMWFiZDViMC0zMDVhLTRkNjMtYjFiOS01OWEwZGE3YzM4YzcifQ.EDjnsI8btqpyoR-s6BLE-zUBUtkKxQ_yZOxI1jv5mDGR3pJcHmdvQDEnI0xm96DAnljp_MmxxMKVa6J5icDN6d58oT8Q96R-YV_ulQSBj9zEi051-_5doUtL3xwHUceqMF8audEhZqAcoshZvY68UzK8RlYL5qL7OrS_p2m39m_2PFPcxJVzoCMCR36whZdIRMTBQ_MhdQUCWFVrfuTnMRlQWLNltRmJSpgRr9jh2MSUff6bb3xk31enB76R1lJbhWTSo2CpUoVaf7SlJEYRKE2O9Ae26BZQySZWAkdWxADJY34H3EarbjuPLq5d_y76h9O3YfAtXmlnN9xsc-jXRQ + cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NjYwNTI3OSwic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg2NjA1NTc5LCJpYXQiOjE3ODY2MDUyNzksImp0aSI6ImFjY2Vzc3Rva2VuLTI5MWJkN2EwLThjNTUtNDQzNC1iZWI2LTFiNDE3NWVlOWIxZSJ9.SLt47nnayETtX6g_FBnGSwulRZp905etacpOhQGquF0sdPOSW8VdvJqU0ZYKAlLGK_nNJr26HQvNR5P9fXemCA + prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyIiwiYXVkIjoiZGlkOndlYjpjb25zdW1lci13YWxsZXQ6dXNlcjpjb25zdW1lciIsIm5iZiI6MTc4NjYwNTI3OSwiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NjYwNTU3OSwiaWF0IjoxNzg2NjA1Mjc5LCJqdGkiOiJkYzMwNzYxNC02YzkyLTQ3ZDgtOTJkMi01YmIxNDBjMjU5MjciLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROall3TlRJM09Td2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnMk5qQTFOVGM1TENKcFlYUWlPakUzT0RZMk1EVXlOemtzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFRJNU1XSmtOMkV3TFRoak5UVXRORFF6TkMxaVpXSTJMVEZpTkRFM05XVmxPV0l4WlNKOS5TTHQ0N25uYXlFVHRYNmdfRkJuR1N3dWxSWnA5MDVldGFjcE9oUUdxdUYwc2RQT1NXOFZkdkpxVTBaWUtBbExHS19uTkpyMjZIUXZOUjVQOWZYZW1DQSJ9.XvOy59jw8vNfcZMxSckSA2Auj8O4bMIyyTLorQJamgAwOHz7MXYBLzFAMiCpBCkHNLggtO7CK6CQfxqF2Wd6Cg + offerId: Y29udHJhY3REZWZUd28=:ZGVtb0Fzc2V0SWRUd28=:ZWJhMjNhNDQtM2ZiYy00MDQwLWE4ZjYtOTk0ZGViZjUwNWYw + transferId: 48728072-cf84-4311-b141-e9558c58785e + pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg2NjA1NDAwLCJqdGkiOiJkNTQ3OTI3Mi04Mjg2LTQ5NzMtOWVjMS00YTk2Y2VjMTY5ODEifQ.IU-Ki9Z7_RjgFrHWsuDUu4XL25mMJ932emgTg7WVl1VIHvGQduMAN8lWiUoDxiisUS0Cp6m1BO-8TJ3Z52DDrwovQ3WftvS-7eat2-cgFZNDuTOYiDZy4R1hAQdJrYmcLiyX_WbwIwF-CSq4wH5XHtB4sP4OV7vQC09uzMllaEfvbHVTkc2EA0l0abqZ_5qzefb93ZYn2U9Qz4z53vDla3MwsDe1f4b8DsustHCGQtUgb4Hpb1-DJUO44TgqTJdmJbNn_dt1X2UrCEJcEWJdUS_cPLw6XhJmWjf6BkNo_nxvCVDNCZEnYdthgS0Scxeeh4g7S4Zz-DVDM4ib1yoo8Q ISS_PART_CONT: con-x-issuer CONS_PART_CONT: user-consumer PROV_PART_CONT: user-provider MEM_CRED_ID: https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential CUSTOM_CRED_ID: https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential - catalogPermission: [{"action":"use","constraint":[{"and":[{"leftOperand":"https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.isMember","operator":"eq","rightOperand":"true"},{"leftOperand":"https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential.accessLevels.fooLevel","operator":"gteq","rightOperand":"2"}]}]}] - TARGET_ASSET_ID: demoAssetIdOne - negotiation-id: e1af3be4-cd54-492e-a5cd-c4783faf68fb + catalogPermission: [{"action":"use","constraint":[{"and":{"leftOperand":"https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential.accessLevels.barLevel.role","operator":"isAnyOf","rightOperand":"[\"Sheriff\", \"Marshal\"]"}}]}] + TARGET_ASSET_ID: demoAssetIdTwo CRFORMAT: VC1_0_JWT - contractId: 36a3e2f4-6568-428c-b0f9-1d0dda89e467 + contractId: 808ae7f3-ddf3-4d3c-bf2f-7acccb073518 + negotiation-id: 199c56ad-1ec7-46b2-934c-1d2060a931bc } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru index 6b414f9d47..33021772a4 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru @@ -21,8 +21,10 @@ body:json { "configuration": { "default": { "accessLevels": { - "fooLevel": "3", - "barLevel" : "1" + "fooLevel": 3, + "barLevel": { + "role": "Sheriff" + } } }, "blackList": [] diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru index b037ec5a44..7725a5901a 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru @@ -20,7 +20,7 @@ body:json { "id": "dev-def-1", "configuration": { "default": { - "isMember": "true" + "isMember": true }, "blackList": [] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru index 4db4cab5be..bc6b58a83b 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru @@ -27,8 +27,8 @@ body:json { "and": [ { "leftOperand": "{{MEM_CRED_ID}}.isMember", - "operator": "eq", - "rightOperand": "true" + "operator": "isAnyOf", + "rightOperand": "[true]" } ] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru index fcbccae65e..db1b0d27ae 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru @@ -25,15 +25,10 @@ body:json { "action": "http://www.w3.org/ns/odrl/2/use", "constraint": { "and": [ - { - "leftOperand": "{{MEM_CRED_ID}}.isMember", - "operator": "eq", - "rightOperand": "true" - }, { "leftOperand": "{{CUSTOM_CRED_ID}}.accessLevels.fooLevel", - "operator": "gteq", - "rightOperand": "2" + "operator": "isAnyOf", + "rightOperand": "[2, 3, 4, 5]" } ] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru index 27589989a8..de32c21392 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru @@ -28,7 +28,7 @@ body:json { { "leftOperand": "{{MEM_CRED_ID}}.isMember", "operator": "eq", - "rightOperand": "true" + "rightOperand": true } ] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru index fd6b125c29..6236ff41cd 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru @@ -26,14 +26,9 @@ body:json { "constraint": { "and": [ { - "leftOperand": "{{MEM_CRED_ID}}.isMember", - "operator": "eq", - "rightOperand": "true" - }, - { - "leftOperand": "{{CUSTOM_CRED_ID}}.accessLevels.fooLevel", - "operator": "gteq", - "rightOperand": "4" + "leftOperand": "{{CUSTOM_CRED_ID}}.accessLevels.barLevel.role", + "operator": "isAnyOf", + "rightOperand": "[\"Sheriff\", \"Marshal\"]" } ] } diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java index 73123e358f..0b30ce935b 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java @@ -1,3 +1,22 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 de.fraunhofer.isst.edc.extension.basic_abac.dev; import org.eclipse.edc.iam.verifiablecredentials.spi.model.VerifiableCredential; @@ -5,38 +24,150 @@ import org.eclipse.edc.policy.engine.spi.DynamicAtomicConstraintRuleFunction; import org.eclipse.edc.policy.model.Operator; import org.eclipse.edc.policy.model.Permission; +import org.jetbrains.annotations.Nullable; +import java.util.Collections; import java.util.List; -import java.util.function.Predicate; - -import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.BASIC_ABAC_PATTERN; -import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.getPathObject; -import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.truncateLastPathSegment; -public class BasicAbacCredentialConstraintFunction implements DynamicAtomicConstraintRuleFunction { +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.*; +public class BasicAbacCredentialConstraintFunction implements DynamicAtomicConstraintRuleFunction { @Override public boolean evaluate(Object leftValue, Operator operator, Object rightValue, Permission rule, C context) { - double expectedNumber = 0; - boolean isNumericCondition = false; + if (leftValue == null || rightValue == null || operator == null || context == null) { + return false; + } + + // case: is rightValue a list? + List rightValueList; + if (rightValue instanceof List list) { + rightValueList = normalizeNumericToDouble(list); + } else { + rightValueList = convertJsonToList(rightValue.toString()); + } + if (rightValueList != null) { + return handleRightValueList(leftValue, operator, rightValueList, rule, context); + } + + + // case: is rightValue a (single) numeric? try { - expectedNumber = Double.parseDouble(rightValue.toString()); - isNumericCondition = true; - } catch (Exception e) { + double numericValue = Double.parseDouble(rightValue.toString()); + return handleRightValueNumeric(leftValue, operator, numericValue, rule, context); + } catch (NumberFormatException e) { } - final double finalExpectedNumber = expectedNumber; - Predicate numericPredicate = switch (operator) { - case EQ -> n -> n == finalExpectedNumber; - case NEQ -> n -> n != finalExpectedNumber; - case GEQ -> n -> n >= finalExpectedNumber; - case LEQ -> n -> n <= finalExpectedNumber; - case GT -> n -> n > finalExpectedNumber; - case LT -> n -> n < finalExpectedNumber; - - default -> n -> false; + + // case: rightValue is neither list nor numeric, but could be e.g. a Boolean or non-numeric String value + // or some unexpected type... + Object claimValue = extractValueFromCredentialSubject(context, leftValue); + if (claimValue == null) return false; + + return switch (operator) { + case EQ -> rightValue.equals(claimValue); + case NEQ -> !rightValue.equals(claimValue); + case HAS_PART -> claimValue instanceof List claimList && claimList.contains(rightValue); + default -> false; + }; + } + + @Override + public boolean canHandle(Object leftValue) { + return leftValue instanceof String leftValueString && BASIC_ABAC_PATTERN.matcher(leftValueString).matches(); + } + + private boolean handleRightValueList(Object leftValue, Operator operator, List rightValueList, Permission rule, C context) { + Object claimValue = extractValueFromCredentialSubject(context, leftValue); + if (claimValue == null) return false; + + return switch (operator) { + case IN -> !(claimValue instanceof List) && rightValueList.contains(claimValue); + + case IS_ANY_OF -> { + if (claimValue instanceof List claimList) { + yield !Collections.disjoint(claimList, rightValueList); + } + yield rightValueList.contains(claimValue); + } + + case IS_ALL_OF -> { + if (rightValueList.isEmpty()) { + yield true; + } + if (claimValue instanceof List claimList) { + yield claimList.containsAll(rightValueList); + } + yield rightValueList.size() == 1 + && rightValueList.contains(claimValue); + } + + case IS_NONE_OF -> { + if (claimValue instanceof List claimList) { + yield Collections.disjoint(claimList, rightValueList); + } + yield !rightValueList.contains(claimValue); + } + + case HAS_PART -> { + if (claimValue instanceof List claimList) { + yield claimList.containsAll(rightValueList); + } + yield false; + } + + case EQ -> claimValue instanceof List claimList && listsEqualAsSets(claimList, rightValueList); + + case NEQ -> !(claimValue instanceof List claimList) || !listsEqualAsSets(claimList, rightValueList); + + default -> false; }; + } + + private boolean listsEqualAsSets(List left, List right) { + return left.containsAll(right) && right.containsAll(left); + } + + private boolean handleRightValueNumeric(Object leftValue, Operator operator, double expectedNumber, Permission rule, C context) { + Object valueFromCredentialClaims = extractValueFromCredentialSubject(context, leftValue); + try { + if (valueFromCredentialClaims instanceof List claimList && operator.equals(Operator.HAS_PART)) { + return normalizeNumericToDouble(claimList).contains(expectedNumber); + } + + double numericFromCredentialClaims = Double.parseDouble(valueFromCredentialClaims.toString()); + return switch (operator) { + case EQ -> numericFromCredentialClaims == expectedNumber; + case NEQ -> numericFromCredentialClaims != expectedNumber; + case GEQ -> numericFromCredentialClaims >= expectedNumber; + case LEQ -> numericFromCredentialClaims <= expectedNumber; + case GT -> numericFromCredentialClaims > expectedNumber; + case LT -> numericFromCredentialClaims < expectedNumber; + default -> false; + }; + } catch (Exception e) { + return false; + } + } + + private @Nullable Object extractValueFromCredentialSubject(C context, Object leftValue) { + var verifiableCredentialList = getVerifiableCredentialList(context); + if (verifiableCredentialList == null) return null; + String requiredCredentialType = truncateLastPathSegment(leftValue); + for (var credential : verifiableCredentialList) { + if (credential.getType() == null || !credential.getType().contains(requiredCredentialType)) { + continue; + } + for (var credentialSubject : credential.getCredentialSubject()) { + Object value = getPathObject(leftValue, credentialSubject.getClaims()); + if (value != null) { + return value; + } + } + } + return null; + } + private static @Nullable List getVerifiableCredentialList(C context) { List verifiableCredentialList = null; String potentialProblem = "No Credential Claims found"; try { @@ -49,55 +180,13 @@ public boolean evaluate(Object leftValue, Operator operator, Object rightValue, } } catch (Exception e) { context.reportProblem(potentialProblem); - return false; + return null; } if (verifiableCredentialList == null || verifiableCredentialList.isEmpty()) { context.reportProblem(potentialProblem); - return false; + return null; } - - String credentialFullyQualifiedName = truncateLastPathSegment(leftValue); - - for (var verifiableCredential : verifiableCredentialList) { - if (verifiableCredential.getType().contains(credentialFullyQualifiedName)) { - for (var credentialSubject : verifiableCredential.getCredentialSubject()) { - var claims = credentialSubject.getClaims(); - Object relevantClaim = getPathObject(leftValue, claims); - if (isNumericCondition) { - try { - double claimInteger = Double.parseDouble(relevantClaim.toString()); - return numericPredicate.test(claimInteger); - } catch (Exception e) { - } - } else { - // can handle only equal or not equal at the moment - if (relevantClaim instanceof String) { - // probably unsafe for anything other than String type - return switch (operator) { - case EQ -> rightValue.equals(relevantClaim); - case NEQ -> !rightValue.equals(relevantClaim); - default -> false; - }; - } - } - } - } - } - return false; + return verifiableCredentialList; } - @Override - public boolean canHandle(Object leftValue) { - if (leftValue instanceof String leftValueString) { - return BASIC_ABAC_PATTERN.matcher(leftValueString).matches(); - } - return false; - } - - - - @Override - public String name() { - return this.getClass().getSimpleName() + "-Rule"; - } } diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java index be9e3c7b4d..2b70b38d6c 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java @@ -2,19 +2,13 @@ import org.eclipse.edc.policy.context.request.spi.RequestPolicyContext; import org.eclipse.edc.policy.engine.spi.PolicyValidatorRule; -import org.eclipse.edc.policy.model.AtomicConstraint; -import org.eclipse.edc.policy.model.Constraint; -import org.eclipse.edc.policy.model.LiteralExpression; -import org.eclipse.edc.policy.model.MultiplicityConstraint; -import org.eclipse.edc.policy.model.Policy; +import org.eclipse.edc.policy.model.*; import org.eclipse.edc.spi.monitor.Monitor; import java.util.HashSet; import java.util.Set; -import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.BASIC_ABAC_PATTERN; -import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.CONX_MEMBERSHIP_SCOPE; -import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.truncateLastPathSegment; +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.*; public class BasicAbacPolicyPostValidator implements PolicyValidatorRule { private final Monitor monitor; @@ -48,8 +42,8 @@ private Set explorePolicy(Policy policy) { private Set exploreConstraint(Constraint constraint) { Set output = new HashSet<>(); if (constraint instanceof MultiplicityConstraint multiplicityConstraint) { - for (var multiConstraint : multiplicityConstraint.getConstraints()) { - output.addAll(exploreConstraint(multiConstraint)); + for (var nestedConstraint : multiplicityConstraint.getConstraints()) { + output.addAll(exploreConstraint(nestedConstraint)); } } else if (constraint instanceof AtomicConstraint atomicConstraint) { if (atomicConstraint.getLeftExpression() instanceof LiteralExpression literalExpression) { diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java index 5c6c75f267..bf30239ad5 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java @@ -1,5 +1,32 @@ +/* + * Copyright (c) 2026 Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V. (represented by Fraunhofer ISST) + * + * 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 de.fraunhofer.isst.edc.extension.basic_abac.dev; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -12,6 +39,8 @@ public class BasicAbacUtils { public static final String CONX_MEMBERSHIP_SCOPE = "org.eclipse.dspace.dcp.vc.type:https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential:read"; + private static final ObjectMapper MAPPER = new ObjectMapper(); + /** * This method takes as input *

@@ -21,10 +50,11 @@ public class BasicAbacUtils { *

* The leftValue is expected to have matched the BASIC_ABAC_REGEX, containing a pointer suffix at the end. * That suffix will be interpreted by this method to navigate through the credential subject (also see the readme). + * If the found value is numeric, then -for the sake of normalization- it will be converted to a Double value. * * @param leftValue * @param map - * @return the object (which might be a string, a list, a map) or null if nothing could be found + * @return the object (which might be a string, a list, a map or a Double) or null if nothing could be found */ public static Object getPathObject(Object leftValue, Map map) { String jsonPath = leftValue.toString().replace(truncateLastPathSegment(leftValue) + ".", ""); @@ -41,7 +71,7 @@ public static Object getPathObject(Object leftValue, Map map) { fieldName = fieldName.substring(0, bracketStart); arrayIndex = Integer.parseInt(indexString); } - if (!fieldName.isBlank() && current instanceof Map nestedMap) { + if (!fieldName.isBlank() && current instanceof Map nestedMap) { current = nestedMap.get(fieldName); } if (current == null) { @@ -58,19 +88,27 @@ public static Object getPathObject(Object leftValue, Map map) { return null; } } + } + if (current instanceof List foundList) { + return normalizeNumericToDouble(foundList); + } + try { + return Double.parseDouble(current.toString()); + } catch (Exception e) { + return current; } - return current; + } /** * This method expects the leftValue Parameter of the evaluate method from the DynamicAtomicConstraintRuleFunction * interface as input. It is also expected that the left expression was matched by the BASIC_ABAC_REGEX. - * + *

* It will remove the pointer suffix, effectively returning the fully qualified name of the credential. * * @param leftExpression - * @return + * @return the fully qualified name of the credential */ public static String truncateLastPathSegment(Object leftExpression) { if (leftExpression instanceof String url) { @@ -88,4 +126,80 @@ public static String truncateLastPathSegment(Object leftExpression) { } return null; } + + /** + * Convert the string representation of a JSON list into a Java list. + * + * @param jsonString the string representation of a JSON list + * @return a (Java) list of objects (of unspecified types) + */ + public static @Nullable List convertJsonToList(String jsonString) { + try { + var parsedJson = MAPPER.readTree(jsonString); + if (parsedJson.isArray()) { + return (List) convert(parsedJson); + } + return null; + } catch (Exception e) { + return null; + } + } + + private static Object convert(JsonNode node) { + if (node == null || node.isNull()) { + return null; + } + if (node.isObject()) { + return convertObject((ObjectNode) node); + } + if (node.isArray()) { + return convertArray((ArrayNode) node); + } + if (node.isBoolean()) { + return node.asBoolean(); + } + if (node.isNumber()) { + return node.asDouble(); + } + return node.asText(); + } + + private static Map convertObject(ObjectNode objNode) { + Map map = new LinkedHashMap<>(); + objNode.propertyStream().forEach(entry -> { + map.put(entry.getKey(), convert(entry.getValue())); + }); + return map; + } + + private static List convertArray(ArrayNode arrNode) { + List list = new ArrayList<>(); + for (JsonNode child : arrNode) { + list.add(convert(child)); + } + return list; + } + + /** + * Returns a copy of the input list, where contained objects are parsed into + * Double values, if possible. I.e. Integer, Long or String values + * (with numeric content) will be converted into Doubles. + * + * @param list + * @return a new list + */ + public static List normalizeNumericToDouble(List list) { + return list.stream() + .map(it -> { + if (it instanceof List nestedList) { + return normalizeNumericToDouble(nestedList); + } + // Note: Nested maps unsupported, doubtful if it's needed + try { + return Double.parseDouble(it.toString()); + } catch (NumberFormatException e) { + return it; + } + }).toList(); + } } From 4378368a598fca256c4c7d41d3838f2603c06482 Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Fri, 21 Aug 2026 09:17:44 +0200 Subject: [PATCH 03/11] feat: boolean normalization --- .../environments/local-con-x-env.bru | 14 +++++++------- .../Prepare Issuer/createDevAttestation.bru | 2 +- .../BasicAbacCredentialConstraintFunction.java | 11 +++++++---- .../dev/BasicAbacPolicyPostValidator.java | 2 +- .../extension/basic_abac/dev/BasicAbacUtils.java | 16 ++++++++++++---- 5 files changed, 28 insertions(+), 17 deletions(-) diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru index b626be7df9..f8b4565723 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru @@ -6,12 +6,12 @@ vars { CONSUMER_IDHUB_ID_API: http://localhost:20100/api/identity CONSUMER_IDHUB_STS_API: http://localhost:20500/api/sts CONSUMER_IDHUB_CREDS_API: http://localhost:20600/api/credentials - ISSUER_APIKEY: Y29uLXgtaXNzdWVy.WpbGU5rGkKAXdl7Te2cRvWOqeqOk14IC7DqjJ0Ehuxh4eLE3QdT839DalHJAxQjtBtRUXTNhDpH592K9xayIIg== - CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.w0q1kjBtayaZzTuBhwg4RlaXj35MVu6xzjiSrWca6GmktFbeFW6Jo8H5v72mZj3eR77jG/9XMmFeEfLQOD8D8Q== - PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.whxO1D7Nrgmro7r+jVV4oEsLYtIUK6P+bDP3Yi3/zAbdgFh53hEYoRh5VuIwMS/206Cdrjm7uSTCRzWyGPL7bQ== + ISSUER_APIKEY: Y29uLXgtaXNzdWVy.yD3eAX6YzmDyg7LM6ISgLtUkhCY8lVPkCU/P4Q3awvCcQ99MRFWkBWZdviXczMNpN4/oquTSDtGK3hgcI4iPBQ== + CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.AyoATgP9P0Y4XYvNcLs+OTdb0bzwvSnLAkDtQbNaO1LwQ8+ahEXIKDwxWOaQOMCAdWM/y6b3YRuvy4mX7/92dg== + PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.+223WGFK96KzlPdu9IfcC6XIheca6M57vQEiKdNklyZYfzTrYq/xufN7vOdBqJ3LqlhPxQ/Zq92PfM5IdHNxFA== VAULTURL: http://localhost:8200 - CONSUMER_STS_SECRET: DYgOAkYJxg1EiBUi - PROVIDER_STS_SECRET: FpGv2Gfb1wHnUSOn + CONSUMER_STS_SECRET: 0Rs3SyOEIbTtzWC5 + PROVIDER_STS_SECRET: GmJCHGaXnryWVzvU PROVIDER_MANAGEMENT: http://localhost:39010/management CONSUMER_MANAGEMENT: http://localhost:29010/management PROVIDER_DATAPLANE_PUBLIC: http://localhost:9500/public @@ -22,8 +22,8 @@ vars { ISS_ID: did:web:local-issuer-wallet:con-x-issuer CONS_ID: did:web:consumer-wallet:user:consumer PROV_ID: did:web:provider-wallet:user:provider - cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NjYwNTI3OSwic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg2NjA1NTc5LCJpYXQiOjE3ODY2MDUyNzksImp0aSI6ImFjY2Vzc3Rva2VuLTI5MWJkN2EwLThjNTUtNDQzNC1iZWI2LTFiNDE3NWVlOWIxZSJ9.SLt47nnayETtX6g_FBnGSwulRZp905etacpOhQGquF0sdPOSW8VdvJqU0ZYKAlLGK_nNJr26HQvNR5P9fXemCA - prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyIiwiYXVkIjoiZGlkOndlYjpjb25zdW1lci13YWxsZXQ6dXNlcjpjb25zdW1lciIsIm5iZiI6MTc4NjYwNTI3OSwiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NjYwNTU3OSwiaWF0IjoxNzg2NjA1Mjc5LCJqdGkiOiJkYzMwNzYxNC02YzkyLTQ3ZDgtOTJkMi01YmIxNDBjMjU5MjciLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROall3TlRJM09Td2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnMk5qQTFOVGM1TENKcFlYUWlPakUzT0RZMk1EVXlOemtzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFRJNU1XSmtOMkV3TFRoak5UVXRORFF6TkMxaVpXSTJMVEZpTkRFM05XVmxPV0l4WlNKOS5TTHQ0N25uYXlFVHRYNmdfRkJuR1N3dWxSWnA5MDVldGFjcE9oUUdxdUYwc2RQT1NXOFZkdkpxVTBaWUtBbExHS19uTkpyMjZIUXZOUjVQOWZYZW1DQSJ9.XvOy59jw8vNfcZMxSckSA2Auj8O4bMIyyTLorQJamgAwOHz7MXYBLzFAMiCpBCkHNLggtO7CK6CQfxqF2Wd6Cg + cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NzI5NTI5Nywic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg3Mjk1NTk3LCJpYXQiOjE3ODcyOTUyOTcsImp0aSI6ImFjY2Vzc3Rva2VuLWFhNmI1MzA3LTlhMzctNDBiMS04ODFhLTY0ZGJiODFiMGYyNyJ9.rDdLUlUN6fjH8KMDRgt-NDi41r1x1nNcGq7Wu-YPGWzrnVKhuc4gUBDAks7G-bmiCcxwei87hlKnCp93z3v0Cw + prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyIiwiYXVkIjoiZGlkOndlYjpjb25zdW1lci13YWxsZXQ6dXNlcjpjb25zdW1lciIsIm5iZiI6MTc4NzI5NTI5NywiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NzI5NTU5NywiaWF0IjoxNzg3Mjk1Mjk3LCJqdGkiOiJkMjYyNDVhNi0zMDRmLTQ1NTktODM3Mi0wOGQ0NGIwYWE0NTEiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROekk1TlRJNU55d2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnM01qazFOVGszTENKcFlYUWlPakUzT0RjeU9UVXlPVGNzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFdGaE5tSTFNekEzTFRsaE16Y3ROREJpTVMwNE9ERmhMVFkwWkdKaU9ERmlNR1l5TnlKOS5yRGRMVWxVTjZmakg4S01EUmd0LU5EaTQxcjF4MW5OY0dxN1d1LVlQR1d6cm5WS2h1YzRnVUJEQWtzN0ctYm1pQ2N4d2VpODdobEtuQ3A5M3ozdjBDdyJ9.6mfWbT476xJvP740iNQGdJZ0ifdTuj6u7VEDIfVFRcoHedjoXlDZvkhWYs6CGbaUMzl_APSH1gcLdv33RZSyCw offerId: Y29udHJhY3REZWZUd28=:ZGVtb0Fzc2V0SWRUd28=:ZWJhMjNhNDQtM2ZiYy00MDQwLWE4ZjYtOTk0ZGViZjUwNWYw transferId: 48728072-cf84-4311-b141-e9558c58785e pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg2NjA1NDAwLCJqdGkiOiJkNTQ3OTI3Mi04Mjg2LTQ5NzMtOWVjMS00YTk2Y2VjMTY5ODEifQ.IU-Ki9Z7_RjgFrHWsuDUu4XL25mMJ932emgTg7WVl1VIHvGQduMAN8lWiUoDxiisUS0Cp6m1BO-8TJ3Z52DDrwovQ3WftvS-7eat2-cgFZNDuTOYiDZy4R1hAQdJrYmcLiyX_WbwIwF-CSq4wH5XHtB4sP4OV7vQC09uzMllaEfvbHVTkc2EA0l0abqZ_5qzefb93ZYn2U9Qz4z53vDla3MwsDe1f4b8DsustHCGQtUgb4Hpb1-DJUO44TgqTJdmJbNn_dt1X2UrCEJcEWJdUS_cPLw6XhJmWjf6BkNo_nxvCVDNCZEnYdthgS0Scxeeh4g7S4Zz-DVDM4ib1yoo8Q diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru index 7725a5901a..b037ec5a44 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru @@ -20,7 +20,7 @@ body:json { "id": "dev-def-1", "configuration": { "default": { - "isMember": true + "isMember": "true" }, "blackList": [] } diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java index 0b30ce935b..97d3af4638 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java @@ -42,7 +42,7 @@ public boolean evaluate(Object leftValue, Operator operator, Object rightValue, // case: is rightValue a list? List rightValueList; if (rightValue instanceof List list) { - rightValueList = normalizeNumericToDouble(list); + rightValueList = normalizeNumericToDoubleOrBoolean(list); } else { rightValueList = convertJsonToList(rightValue.toString()); } @@ -59,10 +59,13 @@ public boolean evaluate(Object leftValue, Operator operator, Object rightValue, } // case: rightValue is neither list nor numeric, but could be e.g. a Boolean or non-numeric String value - // or some unexpected type... Object claimValue = extractValueFromCredentialSubject(context, leftValue); if (claimValue == null) return false; + // try to normalize Boolean values + claimValue = normalizeToBoolean(claimValue); + rightValue = normalizeToBoolean(claimValue); + return switch (operator) { case EQ -> rightValue.equals(claimValue); case NEQ -> !rightValue.equals(claimValue); @@ -131,7 +134,7 @@ private boolean handleRightValueNumeric(Object leftValue, Operator operator, dou Object valueFromCredentialClaims = extractValueFromCredentialSubject(context, leftValue); try { if (valueFromCredentialClaims instanceof List claimList && operator.equals(Operator.HAS_PART)) { - return normalizeNumericToDouble(claimList).contains(expectedNumber); + return normalizeNumericToDoubleOrBoolean(claimList).contains(expectedNumber); } double numericFromCredentialClaims = Double.parseDouble(valueFromCredentialClaims.toString()); @@ -152,7 +155,7 @@ private boolean handleRightValueNumeric(Object leftValue, Operator operator, dou private @Nullable Object extractValueFromCredentialSubject(C context, Object leftValue) { var verifiableCredentialList = getVerifiableCredentialList(context); if (verifiableCredentialList == null) return null; - String requiredCredentialType = truncateLastPathSegment(leftValue); + String requiredCredentialType = truncatePrefixAndLastPathSegment(leftValue); for (var credential : verifiableCredentialList) { if (credential.getType() == null || !credential.getType().contains(requiredCredentialType)) { continue; diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java index 2b70b38d6c..a3f78554da 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java @@ -48,7 +48,7 @@ private Set exploreConstraint(Constraint constraint) { } else if (constraint instanceof AtomicConstraint atomicConstraint) { if (atomicConstraint.getLeftExpression() instanceof LiteralExpression literalExpression) { if (isCredentialConstraint(literalExpression.getValue())) { - output.add("org.eclipse.dspace.dcp.vc.type:" + truncateLastPathSegment(literalExpression.getValue()) + ":read"); + output.add("org.eclipse.dspace.dcp.vc.type:" + truncatePrefixAndLastPathSegment(literalExpression.getValue()) + ":read"); } } } diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java index bf30239ad5..a4330089aa 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java @@ -90,7 +90,7 @@ public static Object getPathObject(Object leftValue, Map map) { } } if (current instanceof List foundList) { - return normalizeNumericToDouble(foundList); + return normalizeNumericToDoubleOrBoolean(foundList); } try { @@ -188,18 +188,26 @@ private static List convertArray(ArrayNode arrNode) { * @param list * @return a new list */ - public static List normalizeNumericToDouble(List list) { + public static List normalizeNumericToDoubleOrBoolean(List list) { return list.stream() .map(it -> { if (it instanceof List nestedList) { - return normalizeNumericToDouble(nestedList); + return normalizeNumericToDoubleOrBoolean(nestedList); } // Note: Nested maps unsupported, doubtful if it's needed try { return Double.parseDouble(it.toString()); } catch (NumberFormatException e) { - return it; + return normalizeToBoolean(it); } }).toList(); } + + public static Object normalizeToBoolean(Object value) { + if ("true".equalsIgnoreCase(value.toString()) || "false".equalsIgnoreCase(value.toString())) { + return Boolean.valueOf(value.toString()); + } + return value; + + } } From fd0bcdd5a570634078efc06256b7a48e56cb5d50 Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Fri, 21 Aug 2026 09:27:38 +0200 Subject: [PATCH 04/11] fix: function --- .../basic_abac/dev/BasicAbacCredentialConstraintFunction.java | 2 +- .../extension/basic_abac/dev/BasicAbacPolicyPostValidator.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java index 97d3af4638..99ec4c4a05 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java @@ -155,7 +155,7 @@ private boolean handleRightValueNumeric(Object leftValue, Operator operator, dou private @Nullable Object extractValueFromCredentialSubject(C context, Object leftValue) { var verifiableCredentialList = getVerifiableCredentialList(context); if (verifiableCredentialList == null) return null; - String requiredCredentialType = truncatePrefixAndLastPathSegment(leftValue); + String requiredCredentialType = truncateLastPathSegment(leftValue); for (var credential : verifiableCredentialList) { if (credential.getType() == null || !credential.getType().contains(requiredCredentialType)) { continue; diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java index a3f78554da..2b70b38d6c 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java @@ -48,7 +48,7 @@ private Set exploreConstraint(Constraint constraint) { } else if (constraint instanceof AtomicConstraint atomicConstraint) { if (atomicConstraint.getLeftExpression() instanceof LiteralExpression literalExpression) { if (isCredentialConstraint(literalExpression.getValue())) { - output.add("org.eclipse.dspace.dcp.vc.type:" + truncatePrefixAndLastPathSegment(literalExpression.getValue()) + ":read"); + output.add("org.eclipse.dspace.dcp.vc.type:" + truncateLastPathSegment(literalExpression.getValue()) + ":read"); } } } From 8aa7461be86a1aab4d2460e462aeebbd85b6c716 Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Fri, 21 Aug 2026 13:43:26 +0200 Subject: [PATCH 05/11] feat: new regex, added unit tests --- .../environments/local-con-x-env.bru | 28 +- .../Prepare Issuer/createDevAttestation.bru | 2 +- .../createAssetOne/CreateAccessPolicy.bru | 2 +- .../createAssetOne/CreateContractPolicy.bru | 2 +- .../createAssetTwo/CreateAccessPolicy.bru | 2 +- .../createAssetTwo/CreateContractPolicy.bru | 2 +- .../local/docker-compose.yaml | 7 +- edc-extensions/basic-abac/README.md | 177 +++-- ...BasicAbacCredentialConstraintFunction.java | 1 - .../basic_abac/dev/BasicAbacExtension.java | 13 +- .../dev/BasicAbacPolicyPostValidator.java | 6 +- .../basic_abac/dev/BasicAbacUtils.java | 32 +- .../src/test/java/BasicAbacTest.java | 667 +++++++++++++++++- 13 files changed, 831 insertions(+), 110 deletions(-) diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru index f8b4565723..0fe3fb29a8 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru @@ -6,12 +6,12 @@ vars { CONSUMER_IDHUB_ID_API: http://localhost:20100/api/identity CONSUMER_IDHUB_STS_API: http://localhost:20500/api/sts CONSUMER_IDHUB_CREDS_API: http://localhost:20600/api/credentials - ISSUER_APIKEY: Y29uLXgtaXNzdWVy.yD3eAX6YzmDyg7LM6ISgLtUkhCY8lVPkCU/P4Q3awvCcQ99MRFWkBWZdviXczMNpN4/oquTSDtGK3hgcI4iPBQ== - CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.AyoATgP9P0Y4XYvNcLs+OTdb0bzwvSnLAkDtQbNaO1LwQ8+ahEXIKDwxWOaQOMCAdWM/y6b3YRuvy4mX7/92dg== - PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.+223WGFK96KzlPdu9IfcC6XIheca6M57vQEiKdNklyZYfzTrYq/xufN7vOdBqJ3LqlhPxQ/Zq92PfM5IdHNxFA== + ISSUER_APIKEY: Y29uLXgtaXNzdWVy.p6yHKIO6G+o0TtV6GIdV3kFP0ifmHRoWKiC+MaFnndprwWO065TyDQTfGPvJEO40JxEv8ABS2ADdjwCSNGW23A== + CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.GHu6UMLWF/ZX1XKLUJJEooDEeSuzHGx6ikh1t0zwNYDJAd7PJmKIVyDixgnhD6S417qm3i9ghScv50XWJqTGjg== + PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.RAFSKO0ddYkvzywB6Wql6cfsLgTAiOmdWdoOiU1rlgp5o1lpHuDhePnSSXjK/x+8COk9k6cXXhHjsMA9uOHguA== VAULTURL: http://localhost:8200 - CONSUMER_STS_SECRET: 0Rs3SyOEIbTtzWC5 - PROVIDER_STS_SECRET: GmJCHGaXnryWVzvU + CONSUMER_STS_SECRET: PXZKpcHs8LcCri4T + PROVIDER_STS_SECRET: On4okKfut4rVl9ce PROVIDER_MANAGEMENT: http://localhost:39010/management CONSUMER_MANAGEMENT: http://localhost:29010/management PROVIDER_DATAPLANE_PUBLIC: http://localhost:9500/public @@ -22,19 +22,19 @@ vars { ISS_ID: did:web:local-issuer-wallet:con-x-issuer CONS_ID: did:web:consumer-wallet:user:consumer PROV_ID: did:web:provider-wallet:user:provider - cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NzI5NTI5Nywic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg3Mjk1NTk3LCJpYXQiOjE3ODcyOTUyOTcsImp0aSI6ImFjY2Vzc3Rva2VuLWFhNmI1MzA3LTlhMzctNDBiMS04ODFhLTY0ZGJiODFiMGYyNyJ9.rDdLUlUN6fjH8KMDRgt-NDi41r1x1nNcGq7Wu-YPGWzrnVKhuc4gUBDAks7G-bmiCcxwei87hlKnCp93z3v0Cw - prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyIiwiYXVkIjoiZGlkOndlYjpjb25zdW1lci13YWxsZXQ6dXNlcjpjb25zdW1lciIsIm5iZiI6MTc4NzI5NTI5NywiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NzI5NTU5NywiaWF0IjoxNzg3Mjk1Mjk3LCJqdGkiOiJkMjYyNDVhNi0zMDRmLTQ1NTktODM3Mi0wOGQ0NGIwYWE0NTEiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROekk1TlRJNU55d2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnM01qazFOVGszTENKcFlYUWlPakUzT0RjeU9UVXlPVGNzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFdGaE5tSTFNekEzTFRsaE16Y3ROREJpTVMwNE9ERmhMVFkwWkdKaU9ERmlNR1l5TnlKOS5yRGRMVWxVTjZmakg4S01EUmd0LU5EaTQxcjF4MW5OY0dxN1d1LVlQR1d6cm5WS2h1YzRnVUJEQWtzN0ctYm1pQ2N4d2VpODdobEtuQ3A5M3ozdjBDdyJ9.6mfWbT476xJvP740iNQGdJZ0ifdTuj6u7VEDIfVFRcoHedjoXlDZvkhWYs6CGbaUMzl_APSH1gcLdv33RZSyCw - offerId: Y29udHJhY3REZWZUd28=:ZGVtb0Fzc2V0SWRUd28=:ZWJhMjNhNDQtM2ZiYy00MDQwLWE4ZjYtOTk0ZGViZjUwNWYw - transferId: 48728072-cf84-4311-b141-e9558c58785e - pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg2NjA1NDAwLCJqdGkiOiJkNTQ3OTI3Mi04Mjg2LTQ5NzMtOWVjMS00YTk2Y2VjMTY5ODEifQ.IU-Ki9Z7_RjgFrHWsuDUu4XL25mMJ932emgTg7WVl1VIHvGQduMAN8lWiUoDxiisUS0Cp6m1BO-8TJ3Z52DDrwovQ3WftvS-7eat2-cgFZNDuTOYiDZy4R1hAQdJrYmcLiyX_WbwIwF-CSq4wH5XHtB4sP4OV7vQC09uzMllaEfvbHVTkc2EA0l0abqZ_5qzefb93ZYn2U9Qz4z53vDla3MwsDe1f4b8DsustHCGQtUgb4Hpb1-DJUO44TgqTJdmJbNn_dt1X2UrCEJcEWJdUS_cPLw6XhJmWjf6BkNo_nxvCVDNCZEnYdthgS0Scxeeh4g7S4Zz-DVDM4ib1yoo8Q + cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NzMxMjM0MCwic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg3MzEyNjQwLCJpYXQiOjE3ODczMTIzNDAsImp0aSI6ImFjY2Vzc3Rva2VuLWViYzg3NzkxLTg1MjAtNGFmZC1iMjBiLTQ1YjYwYzdlMjJlNiJ9.WT7uNu-ASoeteYmt-owlQD4xpwuhLcg2dALsZGBFghBHnL-XF1qymgilZmfX1Y3OSYdtV7oOflVUfzrLj_SNAw + prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyIiwiYXVkIjoiZGlkOndlYjpjb25zdW1lci13YWxsZXQ6dXNlcjpjb25zdW1lciIsIm5iZiI6MTc4NzMxMjM0MCwiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NzMxMjY0MCwiaWF0IjoxNzg3MzEyMzQwLCJqdGkiOiI3MjhlYmJiMy02ODcyLTQzMWQtODRjMi01NThhYTJiN2YwMzYiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROek14TWpNME1Dd2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnM016RXlOalF3TENKcFlYUWlPakUzT0Rjek1USXpOREFzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFdWaVl6ZzNOemt4TFRnMU1qQXROR0ZtWkMxaU1qQmlMVFExWWpZd1l6ZGxNakpsTmlKOS5XVDd1TnUtQVNvZXRlWW10LW93bFFENHhwd3VoTGNnMmRBTHNaR0JGZ2hCSG5MLVhGMXF5bWdpbFptZlgxWTNPU1lkdFY3b09mbFZVZnpyTGpfU05BdyJ9.KxCDMdJspJ_1GoY0xU0pnnhHu88lPHjIhOtzi0iorr8Z_xPiMJXwPraLQ5wbPs6JclxPYQKFLNRWIh5ONzNdAg + offerId: Y29udHJhY3REZWZPbmU=:ZGVtb0Fzc2V0SWRPbmU=:MzdiY2YwMDItODMxNS00YzE2LWI0ZGQtZGUwNjI5NzkyMDg0 + transferId: b5c13c58-1c9f-445e-bd08-db55d3f59d5d + pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg3MzA2ODQ4LCJqdGkiOiI4MGE2MWEyMC0yMWZiLTQ3NGEtODgzNS1iNDkyMzVhZmUxMTgifQ.2WEIAOblqYvFKdMLxQ06w3z5aXu0wIhW4y6FyN3gcoDvA2GlTiBgz75r4IYX-eNtzPCaZvsGL4ln4xP692_vVjavSS8tNWQn5IIDdQZPAa0wwS7-dl23FwT57hdQHjetFwuu-HweZf3jlHhjEKmIFgcu0MxC4UFAiBbpi921eK8LEqCkjDcG0lLu-abRpGpTa-5Wf5ir0Seu8OXwOt-_yFqNkrDJZ6F5XnKZW0KaI7SPXip3Lwyfhmt6HbzLsO3Dm4A0R5EtGqpC4DQ-fL6WeGMsfBzsxCHpMWzhq76xO51JGv-0rfPp3djnWOkHcZuYs7OJEZZ3OnS1l6QhCd5dNw ISS_PART_CONT: con-x-issuer CONS_PART_CONT: user-consumer PROV_PART_CONT: user-provider MEM_CRED_ID: https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential CUSTOM_CRED_ID: https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential - catalogPermission: [{"action":"use","constraint":[{"and":{"leftOperand":"https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential.accessLevels.barLevel.role","operator":"isAnyOf","rightOperand":"[\"Sheriff\", \"Marshal\"]"}}]}] - TARGET_ASSET_ID: demoAssetIdTwo + catalogPermission: [{"action":"use","constraint":[{"and":{"leftOperand":"https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential.credentialSubject.accessLevels.fooLevel","operator":"isAnyOf","rightOperand":"[2, 3, 4, 5]"}}]}] + TARGET_ASSET_ID: demoAssetIdOne CRFORMAT: VC1_0_JWT - contractId: 808ae7f3-ddf3-4d3c-bf2f-7acccb073518 - negotiation-id: 199c56ad-1ec7-46b2-934c-1d2060a931bc + contractId: 25076811-0ad7-4c81-b624-dff173ea261b + negotiation-id: f7b964a2-ad7c-4ddf-8d81-6153f6020a2b } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru index b037ec5a44..7725a5901a 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru @@ -20,7 +20,7 @@ body:json { "id": "dev-def-1", "configuration": { "default": { - "isMember": "true" + "isMember": true }, "blackList": [] } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru index bc6b58a83b..287dc59bdf 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateAccessPolicy.bru @@ -26,7 +26,7 @@ body:json { "constraint": { "and": [ { - "leftOperand": "{{MEM_CRED_ID}}.isMember", + "leftOperand": "{{MEM_CRED_ID}}.credentialSubject.isMember", "operator": "isAnyOf", "rightOperand": "[true]" } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru index db1b0d27ae..06a62bf800 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetOne/CreateContractPolicy.bru @@ -26,7 +26,7 @@ body:json { "constraint": { "and": [ { - "leftOperand": "{{CUSTOM_CRED_ID}}.accessLevels.fooLevel", + "leftOperand": "{{CUSTOM_CRED_ID}}.credentialSubject.accessLevels.fooLevel", "operator": "isAnyOf", "rightOperand": "[2, 3, 4, 5]" } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru index de32c21392..65778efd39 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateAccessPolicy.bru @@ -26,7 +26,7 @@ body:json { "constraint": { "and": [ { - "leftOperand": "{{MEM_CRED_ID}}.isMember", + "leftOperand": "{{MEM_CRED_ID}}.credentialSubject.isMember", "operator": "eq", "rightOperand": true } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru index 6236ff41cd..910719efc4 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/provider/createAssetTwo/CreateContractPolicy.bru @@ -26,7 +26,7 @@ body:json { "constraint": { "and": [ { - "leftOperand": "{{CUSTOM_CRED_ID}}.accessLevels.barLevel.role", + "leftOperand": "{{CUSTOM_CRED_ID}}.credentialSubject.accessLevels.barLevel.role", "operator": "isAnyOf", "rightOperand": "[\"Sheriff\", \"Marshal\"]" } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml index a31e8e50d5..caf72aeb96 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml +++ b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml @@ -248,9 +248,7 @@ services: - edc.vault.hashicorp.url=http://shared-vault:8200 - edc.vault.hashicorp.health.check.enabled=true - edc.vault.hashicorp.token=vaultsecret0123456789 - - tx.edc.iam.iatp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type - - tx.edc.iam.iatp.default-scopes.test.type=MembershipCredential - - tx.edc.iam.iatp.default-scopes.test.operation=read + healthcheck: test: ["CMD-SHELL", "wget --spider http://localhost:9000/api/check/readiness || exit 1"] start_period: 10s @@ -358,9 +356,6 @@ services: - edc.vault.hashicorp.url=http://shared-vault:8200 - edc.vault.hashicorp.health.check.enabled=true - edc.vault.hashicorp.token=vaultsecret0123456789 - - tx.edc.iam.iatp.default-scopes.test.alias=org.eclipse.dspace.dcp.vc.type - - tx.edc.iam.iatp.default-scopes.test.type=MembershipCredential - - tx.edc.iam.iatp.default-scopes.test.operation=read healthcheck: test: [ "CMD-SHELL", "wget --spider http://localhost:9000/api/check/readiness || exit 1" ] start_period: 10s diff --git a/edc-extensions/basic-abac/README.md b/edc-extensions/basic-abac/README.md index 7b7dd92cd5..7669684ba6 100644 --- a/edc-extensions/basic-abac/README.md +++ b/edc-extensions/basic-abac/README.md @@ -1,99 +1,148 @@ # Basic Abac Extension (draft version) -This extension allows you to create policy definitions that enable attribute-based access control. +This extension allows you to create policy definitions that enable attribute-based access control. -That means, you have to specify a credential type by setting a leftOperand that designates a fully qualified credential -type identifier, and that needs to be appended by a "pointer" that allows navigating through the structure of the credential -subject that is used in that credential type. +That means you have to specify a credential type by setting a `leftOperand` that designates a fully qualified credential type identifier. The identifier must be followed by `.credentialSubject` and one or more JSONPath segments that allow navigating through the credential subject used in that credential type. Please note that this extension will (at least currently) only work as expected, if you are using constraints within +the `permission` of a policy definition. -Consider the following example: +Consider the following example: -``` - "permission": [ +```json +{ + "permission": [ + { + "action": "use", + "constraint": [ + { + "and": [ + { + "leftOperand": "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.credentialSubject.isMember", + "operator": "eq", + "rightOperand": "true" + }, { - "action": "use", - "constraint": [ - { - "and": [ - { - "leftOperand": "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.isMember", - "operator": "eq", - "rightOperand": "true" - }, - { - "leftOperand": "https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.accessLevel", - "operator": "gteq", - "rightOperand": "4" - } - ] - } - ] + "leftOperand": "https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.credentialSubject.accessLevel", + "operator": "gteq", + "rightOperand": 4 } ] + } + ] + } + ] +} ``` -Here, we have "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.isMember" first. The last -path segment must have a "pointer" suffix (see more on that below). In this case, the suffix would be ".isMember" +Here, the first `leftOperand` is: + +```text +https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.credentialSubject.isMember +``` +The last path segment consists of: + +1. The complete credential type, in this case `ConstructXMembershipCredential` +2. The mandatory `.credentialSubject` segment +3. One or more JSONPath segments, in this case `.isMember` ### Pattern matching -If the leftOperand of a policy definition matches a certain regex pattern (current defined in the [BasicAbacUtils](./src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java) - class). +The `leftOperand` of a policy definition must match the regex pattern currently defined in the [BasicAbacUtils](./src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java) class: ```java - public static final String BASIC_ABAC_REGEX = - "^(?=.*/credentials)https://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?::[0-9]+)?/.*/[A-Z][^\\s/.]*(?:\\.[^\\s/.]+)+$"; +public static final String BASIC_ABAC_REGEX = + "^https://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?::[0-9]+)?(?:/[^\\s/]+)*/[A-Z][^\\s/.]*\\.credentialSubject(?:\\.[^\\s/.]+)+$"; ``` -In essence, this regex matches if the target string -- is a valid https-url and -- that has the "magic word" "/credentials" somewhere in its path segments and -- whose last path segment starts with a capital letter and -- the suffix of the last path segment must provide a "pointer" to a value in the credential subject - of the corresponding verifiable credential. +In essence, this regex matches if the target string: + +- is a valid HTTPS URL, +- has a last path segment that starts with a capital letter, +- contains `.credentialSubject` directly after the complete credential type, and +- contains one or more JSONPath segments after `.credentialSubject`. + +The JSONPath segments following .credentialSubject specify the path to a value within the credential subject of the corresponding verifiable credential. + +#### Examples matching the regex + +- `https://w3id.org/constructx/credentials/v1.0/Foo.credentialSubject.fooLevel` +- `https://w3id.org/constructx/credentials/v1.0/Bar.credentialSubject.nestedObject.barLevel` +- `https://my-domain.com:8080/some/arbitrary/path/segments/Foo.credentialSubject.fooLevel` +- `https://w3id.org/constructx/policies/v1.0/Foo.credentialSubject.fooLevel` + +#### Examples not matching the regex + +- `https://w3id.org/constructx/credentials/v1.0/Foo` + Missing `.credentialSubject` and a JSONPath segment. + +- `https://w3id.org/constructx/credentials/v1.0/Foo.credentialSubject` + Missing a JSONPath segment after `.credentialSubject`. + +- `https://w3id.org/constructx/credentials/v1.0/Foo.fooLevel` + Missing the mandatory `.credentialSubject` segment. -The suffix of the last path segment can and should be used to provide a "pointer" to a value in the credential subject -of the corresponding verifiable credential. +- `http://example.org/credentials/Foo.credentialSubject.fooLevel` + Uses HTTP instead of HTTPS. -#### Examples (matching the regex) +- `https://w3id.org/constructx/credentials/v1.0/moo.credentialSubject.mooLevel` + The last path segment does not start with a capital letter. -- "https://w3id.org/constructx/credentials/v1.0/Foo.fooLevel" -- "https://w3id.org/constructx/credentials/v1.0/Bar.nestedObject.barLevel" -- "https://my-domain.com:8080/credentials/Foo.fooLevel" +#### Understanding the JSONPath suffix +To create sensible ABAC policies, you need to be aware of the structure of the credential subject in the verifiable credential. -#### Examples (NOT matching the regex) +Consider the following `leftOperand`: -- "https://w3id.org/constructx/credentials/v1.0/Foo" (missing "pointer" suffix) -- "http://example.org/credentials/Foo.fooLevel" (http is not https) -- "https://w3id.org/constructx/credentials/v1.0/moo.mooLevel" (no capital letter at start of last path segment) -- "https://w3id.org/constructx/policies/v1.0/Foo.fooLevel" (missing "/credentials" magic word) +```text +https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential.credentialSubject.isMember +``` + +The corresponding credential subject could be structured as follows: + +```json +{ + "id": "did:web:consumer-wallet:user:consumer", + "isMember": "true" +} +``` -#### Understanding the "pointer suffix" +The mandatory `.credentialSubject` segment identifies the credential subject. The following JSONPath segment `.isMember` points to the value of the `isMember` field, which is the string `"true"` in this example. Just b.t.w.: Note that a JSON literal `true` would have also worked here (more on that below). -In order to create sensible ABAC policies, you need to be aware of the given structure of the credential subject in the -verifiable credential. Continuing the above-mentioned example, let's suppose that the credential subject of the "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential" is structured like this: +Since the policy definition expects that boolean value to be equal to `true`, this condition is fulfilled. +Now consider the second ABAC policy condition: + +```text +https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.credentialSubject.accessLevel ``` - { - "id": "did:web:consumer-wallet:user:consumer", - "isMember": "true" - } + +Assume the consumer participant has this credential, but the trusted issuer provided it with the following credential subject: + +```json +{ + "id": "did:web:consumer-wallet:user:consumer", + "accessLevel": "3" +} ``` -Then, the given suffix (".isMember") is pointing at value of the "isMember" field that JSON object, i.e. the string "true". -Since our policy definition (see above again) is expecting the value "true" to be equal to whatever is found in the value -of the credential subject, everything is fine here. +The evaluation determines that an `accessLevel` of `4` is required, while only level `3` was granted. Therefore, the consumer participant is denied access when attempting to negotiate the corresponding contract offer. -Now let's expand this further to the second ABAC policy condition. Here, we see "https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.accessLevel". Assuming that our consumer-participant -does have this credential, but the relevant trusted issuer gave it to him with this credential subject ... +#### Equality of values + +This extension chooses to be rather generous when it comes to comparing different variants of representations of numeric or boolean values. + +That means for example + +- a string containing five, i.e. `"5"` is equal to a numeric `5`. And both are equal to a decimal `5.0`. +- a string whose content sort of looks like a boolean, e.g. `"true"`, `"FaLSE"` or `"TRUE"` will be interpreted as the corresponding boolean values `true` or `false` respectively. I.e. there is no case-sensitivity here. + +#### Default Credential + +There is a technical necessity that the controlplane minimally needs at least one credential type it will expect or show to external partners. This is especially relevant when there is no credential-specific policy context, from which any credential types could possibly get extracted, i.e. when someone is trying to request someone else's EDC catalog. It can be defined with a property: ``` - { - "id": "did:web:consumer-wallet:user:consumer", - "accessLevel": "3" - } +edc.abac.defaultcredential=https://w3id.org/my/very/special/FooMembershipCredential ``` -... then the evaluation would find that an accessLevel of 4 was required, but only level 3 was granted to him. Which means, -that this consumer participant is out of luck here, and he will be denied if he attempts to negotiate for this contract offer. +You are not required to use that property. If you don't, it will default to `https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential`. + diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java index 99ec4c4a05..786522b45e 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java @@ -82,7 +82,6 @@ public boolean canHandle(Object leftValue) { private boolean handleRightValueList(Object leftValue, Operator operator, List rightValueList, Permission rule, C context) { Object claimValue = extractValueFromCredentialSubject(context, leftValue); if (claimValue == null) return false; - return switch (operator) { case IN -> !(claimValue instanceof List) && rightValueList.contains(claimValue); diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java index f69c1511f2..4302cea8d1 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacExtension.java @@ -30,12 +30,13 @@ import org.eclipse.edc.policy.model.Permission; 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.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; import java.util.Set; -import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.BASIC_ABAC_PATTERN; +import static de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils.*; import static org.eclipse.edc.connector.controlplane.catalog.spi.policy.CatalogPolicyContext.CATALOG_SCOPE; import static org.eclipse.edc.connector.controlplane.contract.spi.policy.ContractNegotiationPolicyContext.NEGOTIATION_SCOPE; import static org.eclipse.edc.connector.controlplane.contract.spi.policy.TransferProcessPolicyContext.TRANSFER_SCOPE; @@ -48,6 +49,11 @@ @Extension("Basic Abac Extension") public class BasicAbacExtension implements ServiceExtension { + @Setting(description = "The default credential type to be used", + defaultValue = "https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential", + key = "edc.abac.defaultcredential") + private String defaultCredential; + @Inject private PolicyEngine policyEngine; @@ -63,6 +69,11 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { var monitor = context.getMonitor(); + + BasicAbacUtils.DEFAULT_MEMBERSHIP_SCOPE = DCP_PREFIX + defaultCredential + READ_SUFFIX; + monitor.withPrefix(this.getClass().getSimpleName()) + .info("Using default as default scope: " + BasicAbacUtils.DEFAULT_MEMBERSHIP_SCOPE); + policyEngine.registerPostValidator(RequestCatalogPolicyContext.class, new BasicAbacPolicyPostValidator<>(monitor)); policyEngine.registerPostValidator(RequestContractNegotiationPolicyContext.class, new BasicAbacPolicyPostValidator<>(monitor)); policyEngine.registerPostValidator(RequestTransferProcessPolicyContext.class, new BasicAbacPolicyPostValidator<>(monitor)); diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java index 2b70b38d6c..9485da87c2 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacPolicyPostValidator.java @@ -24,8 +24,8 @@ public Boolean apply(Policy policy, C requestPolicyContext) { requestPolicyContext.requestScopeBuilder().scopes(foundAbacCredentialTypeIdentifiers); monitor.debug("Found credential type identifiers " + foundAbacCredentialTypeIdentifiers); - // Minimally require CON-X Membership Credential - requestPolicyContext.requestScopeBuilder().scope(CONX_MEMBERSHIP_SCOPE); + // Minimally require the default credential + requestPolicyContext.requestScopeBuilder().scope(DEFAULT_MEMBERSHIP_SCOPE); return true; } @@ -48,7 +48,7 @@ private Set exploreConstraint(Constraint constraint) { } else if (constraint instanceof AtomicConstraint atomicConstraint) { if (atomicConstraint.getLeftExpression() instanceof LiteralExpression literalExpression) { if (isCredentialConstraint(literalExpression.getValue())) { - output.add("org.eclipse.dspace.dcp.vc.type:" + truncateLastPathSegment(literalExpression.getValue()) + ":read"); + output.add(DCP_PREFIX + truncateLastPathSegment(literalExpression.getValue()) + READ_SUFFIX); } } } diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java index a4330089aa..1e06b7326b 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacUtils.java @@ -34,10 +34,12 @@ public class BasicAbacUtils { public static final String BASIC_ABAC_REGEX = - "^(?=.*/credentials)https://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?::[0-9]+)?/.*/[A-Z][^\\s/.]*(?:\\.[^\\s/.]+)+$"; + "^https://[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}(?::[0-9]+)?(?:/[^\\s/]+)*/[A-Z][^\\s/.]*\\.credentialSubject(?:\\.[^\\s/.]+)+$"; public static final Pattern BASIC_ABAC_PATTERN = Pattern.compile(BASIC_ABAC_REGEX); - public static final String CONX_MEMBERSHIP_SCOPE = "org.eclipse.dspace.dcp.vc.type:https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential:read"; + public static final String DCP_PREFIX = "org.eclipse.dspace.dcp.vc.type:"; + public static final String READ_SUFFIX = ":read"; + static String DEFAULT_MEMBERSHIP_SCOPE; // get initialized by the extension class private static final ObjectMapper MAPPER = new ObjectMapper(); @@ -48,7 +50,7 @@ public class BasicAbacUtils { *

* - the credential subject of a verifiable credential (as processed by the edc framework, i.e. as a java.util.Map) *

- * The leftValue is expected to have matched the BASIC_ABAC_REGEX, containing a pointer suffix at the end. + * The leftValue is expected to have matched the BASIC_ABAC_REGEX, containing a JSONPath at the end. * That suffix will be interpreted by this method to navigate through the credential subject (also see the readme). * If the found value is numeric, then -for the sake of normalization- it will be converted to a Double value. * @@ -57,7 +59,7 @@ public class BasicAbacUtils { * @return the object (which might be a string, a list, a map or a Double) or null if nothing could be found */ public static Object getPathObject(Object leftValue, Map map) { - String jsonPath = leftValue.toString().replace(truncateLastPathSegment(leftValue) + ".", ""); + String jsonPath = leftValue.toString().replace(truncateLastPathSegment(leftValue) + ".credentialSubject.", ""); if (jsonPath.isBlank()) return null; Object current = map; String[] segments = jsonPath.split("\\."); @@ -71,8 +73,12 @@ public static Object getPathObject(Object leftValue, Map map) { fieldName = fieldName.substring(0, bracketStart); arrayIndex = Integer.parseInt(indexString); } - if (!fieldName.isBlank() && current instanceof Map nestedMap) { - current = nestedMap.get(fieldName); + if (!fieldName.isBlank()) { + if (current instanceof Map nestedMap) { + current = nestedMap.get(fieldName); + } else { + return null; + } } if (current == null) { return null; @@ -96,7 +102,7 @@ public static Object getPathObject(Object leftValue, Map map) { try { return Double.parseDouble(current.toString()); } catch (Exception e) { - return current; + return normalizeToBoolean(current); } } @@ -105,22 +111,22 @@ public static Object getPathObject(Object leftValue, Map map) { * This method expects the leftValue Parameter of the evaluate method from the DynamicAtomicConstraintRuleFunction * interface as input. It is also expected that the left expression was matched by the BASIC_ABAC_REGEX. *

- * It will remove the pointer suffix, effectively returning the fully qualified name of the credential. + * It will remove the JSONPath, effectively returning the fully qualified name of the credential. * * @param leftExpression - * @return the fully qualified name of the credential + * @return the fully qualified name of the credential or null if the leftExpression was invalid */ public static String truncateLastPathSegment(Object leftExpression) { if (leftExpression instanceof String url) { int lastSlashIndex = url.lastIndexOf('/'); if (lastSlashIndex == -1) { - return url; + return null; } String beforeLastSegment = url.substring(0, lastSlashIndex + 1); String lastSegment = url.substring(lastSlashIndex + 1); int firstDotIndex = lastSegment.indexOf('.'); if (firstDotIndex == -1) { - return url; + return null; } return beforeLastSegment + lastSegment.substring(0, firstDotIndex); } @@ -203,8 +209,8 @@ public static List normalizeNumericToDoubleOrBoolean(List list) { }).toList(); } - public static Object normalizeToBoolean(Object value) { - if ("true".equalsIgnoreCase(value.toString()) || "false".equalsIgnoreCase(value.toString())) { + public static @Nullable Object normalizeToBoolean(@Nullable Object value) { + if (value != null && ("true".equalsIgnoreCase(value.toString()) || "false".equalsIgnoreCase(value.toString()))) { return Boolean.valueOf(value.toString()); } return value; diff --git a/edc-extensions/basic-abac/src/test/java/BasicAbacTest.java b/edc-extensions/basic-abac/src/test/java/BasicAbacTest.java index 1ceb34a441..5ee1714218 100644 --- a/edc-extensions/basic-abac/src/test/java/BasicAbacTest.java +++ b/edc-extensions/basic-abac/src/test/java/BasicAbacTest.java @@ -14,10 +14,671 @@ * SPDX-License-Identifier: Apache-2.0 */ -import static org.mockito.Mockito.mock; +import de.fraunhofer.isst.edc.extension.basic_abac.dev.BasicAbacUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.ValueSource; -public class BasicAbacTest { +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; - // TODO: unit tests for util methods and the regex +import static org.junit.jupiter.api.Assertions.*; +class BasicAbacTest { + + // Tests for BasicAbacUtils.BASIC_ABAC_PATTERN + + @ParameterizedTest + @ValueSource(strings = { + "https://w3id.org/constructx/credentials/v1.0/Foo.credentialSubject.fooLevel", + "https://w3id.org/constructx/notcredentials/v1.0/Bar.credentialSubject.nestedObject.barLevel", + "https://my-domain.com:8080/some/fancy/path/segments/Foo.credentialSubject.fooLevel", + + "https://w3id.org/constructx/policies/v1.0/Foo.credentialSubject.fooLevel", + "https://example.org/Foo.credentialSubject.fooLevel", + + // Multiple JSONPath segments + "https://example.org/types/EmployeeCredential.credentialSubject.address.city", + "https://example.org/types/Employee123Credential.credentialSubject.department.name" + }) + void shouldMatchValidBasicAbacOperands(String leftOperand) { + assertTrue( + BasicAbacUtils.BASIC_ABAC_PATTERN.matcher(leftOperand).matches(), + () -> "Expected leftOperand to match regex: " + leftOperand + ); + } + + @ParameterizedTest + @ValueSource(strings = { + // Only HTTPS is allowed + "http://example.org/credentials/Foo.credentialSubject.fooLevel", + + // The credential type must start with an uppercase letter + "https://example.org/credentials/foo.credentialSubject.fooLevel", + + // .credentialSubject is missing + "https://example.org/credentials/Foo.fooLevel", + + // JSONPath segment after .credentialSubject is missing + "https://example.org/credentials/Foo.credentialSubject", + + // Empty JSONPath segment + "https://example.org/credentials/Foo.credentialSubject..fooLevel", + + // Incorrect spelling or capitalization of credentialSubject + "https://example.org/credentials/Foo.credentialsSubject.fooLevel", + "https://example.org/credentials/Foo.CredentialSubject.fooLevel", + + // .credentialSubject is not directly after the credential type + "https://example.org/credentials/Foo.additional.credentialSubject.fooLevel", + + // Trailing dot or slash + "https://example.org/credentials/Foo.credentialSubject.", + "https://example.org/credentials/Foo.credentialSubject.fooLevel/", + + // Missing host or top-level domain + "https://localhost/Foo.credentialSubject.fooLevel", + "https:///Foo.credentialSubject.fooLevel", + + // Not a complete HTTPS URL + "Foo.credentialSubject.fooLevel", + "" + }) + void shouldNotMatchInvalidBasicAbacOperands(String leftOperand) { + assertFalse( + BasicAbacUtils.BASIC_ABAC_PATTERN.matcher(leftOperand).matches(), + () -> "Expected leftOperand not to match regex: " + leftOperand + ); + } + + + // Tests for BasicAbacUtils.truncateLastPathSegment + + @ParameterizedTest + @CsvSource( + delimiter = '|', + value = { + "https://w3id.org/constructx/credentials/v1.0/Foo.credentialSubject.fooLevel" + + "|https://w3id.org/constructx/credentials/v1.0/Foo", + "https://w3id.org/constructx/credentials/v1.0/Bar.credentialSubject.nestedObject.barLevel" + + "|https://w3id.org/constructx/credentials/v1.0/Bar", + "https://my-domain.com:8080/credentials/Foo.credentialSubject.fooLevel" + + "|https://my-domain.com:8080/credentials/Foo", + "https://w3id.org/constructx/policies/v1.0/Employee123Credential.credentialSubject.department.name" + + "|https://w3id.org/constructx/policies/v1.0/Employee123Credential", + "https://example.org/Foo.credentialSubject.fooLevel" + + "|https://example.org/Foo" + } + ) + void shouldRemoveJsonPathFromLeftExpression(String leftExpression, String expectedCredentialType) { + String result = BasicAbacUtils.truncateLastPathSegment(leftExpression); + + assertEquals(expectedCredentialType, result); + } + + @Test + void shouldNotRemoveDotsFromPreviousPathSegments() { + String leftExpression = + "https://example.org/path.with.dots/v1.0/Foo.credentialSubject.value"; + + String result = BasicAbacUtils.truncateLastPathSegment(leftExpression); + + assertEquals( + "https://example.org/path.with.dots/v1.0/Foo", + result + ); + } + + @ParameterizedTest + @ValueSource(strings = { + // The last path segment does not contain a dot + "https://example.org/credentials/Foo", + + // The expression does not contain a slash + "Foo.credentialSubject.fooLevel", + + // The last path segment is empty + "https://example.org/credentials/", + + // Empty expression + "" + }) + void shouldReturnNullForInvalidStringExpression(String leftExpression) { + String result = BasicAbacUtils.truncateLastPathSegment(leftExpression); + + assertNull(result); + } + + @ParameterizedTest + @ValueSource(ints = { 0, 42 }) + void shouldReturnNullNonStringExpression(Object leftExpression) { + String result = BasicAbacUtils.truncateLastPathSegment(leftExpression); + + assertNull(result); + } + + + // Tests for BasicAbacUtils.getPathObject + + @Test + void shouldReturnStringFromCredentialSubject() { + Map credentialSubject = Map.of( + "firstName", "Alice" + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.firstName", + credentialSubject + ); + + assertEquals("Alice", result); + } + + @Test + void shouldNavigateThroughNestedObjects() { + Map credentialSubject = Map.of( + "address", Map.of( + "city", "Berlin", + "country", "Germany" + ) + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.address.city", + credentialSubject + ); + + assertEquals("Berlin", result); + } + + @Test + void shouldNormalizeIntegerToDouble() { + Map credentialSubject = Map.of( + "accessLevel", 4 + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.accessLevel", + credentialSubject + ); + + assertEquals(4.0, result); + } + + @Test + void shouldReturnDoubleValue() { + Map credentialSubject = Map.of( + "score", 12.5 + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.score", + credentialSubject + ); + + assertEquals(12.5, result); + } + + @Test + void shouldNormalizeNumericStringToDouble() { + Map credentialSubject = Map.of( + "accessLevel", "4" + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.accessLevel", + credentialSubject + ); + + assertEquals(4.0, result); + } + + @Test + void shouldReturnBooleanValue() { + Map credentialSubject = Map.of( + "active", true + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.active", + credentialSubject + ); + + assertEquals(true, result); + } + + @Test + void shouldReturnNestedMap() { + Map address = new LinkedHashMap<>(); + address.put("city", "Berlin"); + address.put("postalCode", "10115"); + + Map credentialSubject = Map.of( + "address", address + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.address", + credentialSubject + ); + + assertSame(address, result); + } + + @Test + void shouldReturnAndNormalizeList() { + Map credentialSubject = Map.of( + "values", List.of(1, 2.5, "3", true, "text") + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.values", + credentialSubject + ); + + assertEquals( + List.of(1.0, 2.5, 3.0, true, "text"), + result + ); + } + + @Test + void shouldAccessListElementByIndex() { + Map credentialSubject = Map.of( + "roles", List.of("reader", "editor", "admin") + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.roles[1]", + credentialSubject + ); + + assertEquals("editor", result); + } + + @Test + void shouldNavigateThroughObjectInsideList() { + Map credentialSubject = Map.of( + "addresses", List.of( + Map.of("city", "Berlin"), + Map.of("city", "Munich") + ) + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.addresses[1].city", + credentialSubject + ); + + assertEquals("Munich", result); + } + + @Test + void shouldNavigateThroughMultipleNestedListsAndObjects() { + Map credentialSubject = Map.of( + "departments", List.of( + Map.of( + "name", "Engineering", + "employees", List.of( + Map.of("name", "Alice", "accessLevel", 3), + Map.of("name", "Bob", "accessLevel", 4) + ) + ) + ) + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential" + + ".credentialSubject.departments[0].employees[1].accessLevel", + credentialSubject + ); + + assertEquals(4.0, result); + } + + @Test + void shouldReturnNullWhenFieldDoesNotExist() { + Map credentialSubject = Map.of( + "firstName", "Alice" + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.lastName", + credentialSubject + ); + + assertNull(result); + } + + @Test + void shouldReturnNullWhenNestedFieldDoesNotExist() { + Map credentialSubject = Map.of( + "address", Map.of( + "city", "Berlin" + ) + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.address.country", + credentialSubject + ); + + assertNull(result); + } + + @Test + void shouldReturnNullWhenListIndexIsOutOfBounds() { + Map credentialSubject = Map.of( + "roles", List.of("reader", "editor") + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.roles[5]", + credentialSubject + ); + + assertNull(result); + } + + @Test + void shouldReturnNullWhenListIndexIsNegative() { + Map credentialSubject = Map.of( + "roles", List.of("reader", "editor") + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.roles[-1]", + credentialSubject + ); + + assertNull(result); + } + + @Test + void shouldReturnNullWhenIndexedValueIsNotAList() { + Map credentialSubject = Map.of( + "role", "admin" + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.role[0]", + credentialSubject + ); + + assertNull(result); + } + + @Test + void shouldReturnNullWhenIntermediateValueIsNotAMap() { + Map credentialSubject = Map.of( + "address", "Berlin" + ); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.address.city", + credentialSubject + ); + + assertNull(result); + } + + @Test + void shouldReturnNullForEmptyCredentialSubject() { + Map credentialSubject = Map.of(); + + Object result = BasicAbacUtils.getPathObject( + "https://example.org/EmployeeCredential.credentialSubject.firstName", + credentialSubject + ); + + assertNull(result); + } + + + + + // Tests for BasicAbacUtils.convertJsonToList + + @Test + void shouldConvertEmptyJsonArrayToEmptyList() { + List result = BasicAbacUtils.convertJsonToList("[]"); + + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + void shouldConvertJsonArrayContainingStrings() { + List result = BasicAbacUtils.convertJsonToList( + "[\"reader\", \"editor\", \"admin\"]" + ); + + assertEquals( + List.of("reader", "editor", "admin"), + result + ); + } + + @Test + void shouldConvertJsonNumbersToDoubles() { + List result = BasicAbacUtils.convertJsonToList( + "[1, 2.5, -3, 0]" + ); + + assertEquals( + List.of(1.0, 2.5, -3.0, 0.0), + result + ); + } + + @Test + void shouldConvertJsonBooleans() { + List result = BasicAbacUtils.convertJsonToList( + "[true, false]" + ); + + assertEquals( + List.of(true, false), + result + ); + } + + @Test + void shouldPreserveJsonNullValues() { + List expected = new ArrayList<>(); + expected.add("first"); + expected.add(null); + expected.add("third"); + + List result = BasicAbacUtils.convertJsonToList( + "[\"first\", null, \"third\"]" + ); + + assertEquals(expected, result); + } + + @Test + void shouldConvertArrayContainingDifferentJsonTypes() { + List expected = new ArrayList<>(); + expected.add("text"); + expected.add(42.0); + expected.add(true); + expected.add(null); + + List result = BasicAbacUtils.convertJsonToList( + "[\"text\", 42, true, null]" + ); + + assertEquals(expected, result); + } + + @Test + void shouldConvertNestedJsonArrays() { + List result = BasicAbacUtils.convertJsonToList( + "[[1, 2], [3, 4], []]" + ); + + assertEquals( + List.of( + List.of(1.0, 2.0), + List.of(3.0, 4.0), + List.of() + ), + result + ); + } + + @Test + void shouldConvertJsonObjectsInsideArrayToMaps() { + List result = BasicAbacUtils.convertJsonToList( + """ + [ + { + "name": "Alice", + "accessLevel": 4, + "active": true + }, + { + "name": "Bob", + "accessLevel": 3, + "active": false + } + ] + """ + ); + + assertEquals( + List.of( + Map.of( + "name", "Alice", + "accessLevel", 4.0, + "active", true + ), + Map.of( + "name", "Bob", + "accessLevel", 3.0, + "active", false + ) + ), + result + ); + } + + @Test + void shouldConvertNestedObjectsAndArrays() { + String json = """ + [ + { + "department": { + "name": "Engineering", + "employees": [ + { + "name": "Alice", + "accessLevel": 3 + }, + { + "name": "Bob", + "accessLevel": 4 + } + ] + } + } + ] + """; + + Map alice = new LinkedHashMap<>(); + alice.put("name", "Alice"); + alice.put("accessLevel", 3.0); + + Map bob = new LinkedHashMap<>(); + bob.put("name", "Bob"); + bob.put("accessLevel", 4.0); + + Map department = new LinkedHashMap<>(); + department.put("name", "Engineering"); + department.put("employees", List.of(alice, bob)); + + Map rootObject = new LinkedHashMap<>(); + rootObject.put("department", department); + + List result = BasicAbacUtils.convertJsonToList(json); + + assertEquals(List.of(rootObject), result); + } + + @Test + void shouldPreserveNullValuesInsideJsonObjects() { + String json = """ + [ + { + "name": "Alice", + "optionalValue": null + } + ] + """; + + Map expectedObject = new LinkedHashMap<>(); + expectedObject.put("name", "Alice"); + expectedObject.put("optionalValue", null); + + List result = BasicAbacUtils.convertJsonToList(json); + + assertEquals(List.of(expectedObject), result); + } + + @Test + void shouldConvertEscapedAndUnicodeStrings() { + List result = BasicAbacUtils.convertJsonToList( + "[\"Hello\\\\World\", \"Line 1\\nLine 2\", \"München\"]" + ); + + assertEquals( + List.of( + "Hello\\World", + "Line 1\nLine 2", + "München" + ), + result + ); + } + + @ParameterizedTest + @ValueSource(strings = { + // A JSON object is not a list + "{}", + "{\"name\":\"Alice\"}", + + // Primitive JSON values are not lists + "\"text\"", + "42", + "true", + "null", + + // Empty or malformed JSON + "", + "not-json", + "[1, 2", + "[1,,2]", + "{invalid}" + }) + void shouldReturnNullWhenInputIsNotAValidJsonArray(String jsonString) { + List result = BasicAbacUtils.convertJsonToList(jsonString); + + assertNull(result); + } + + @ParameterizedTest + @ValueSource(strings = { + " ", + "\t", + "\n" + }) + void shouldReturnNullForNullOrBlankInput(String jsonString) { + List result = BasicAbacUtils.convertJsonToList(jsonString); + + assertNull(result); + } } From 70325bb3f0d34a5f92da1934229df4709fc67a8e Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Thu, 27 Aug 2026 09:06:52 +0200 Subject: [PATCH 06/11] fix: minor logic issues --- .../BasicAbacCredentialConstraintFunction.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java index 786522b45e..e9901adcf8 100644 --- a/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java +++ b/edc-extensions/basic-abac/src/main/java/de/fraunhofer/isst/edc/extension/basic_abac/dev/BasicAbacCredentialConstraintFunction.java @@ -54,6 +54,10 @@ public boolean evaluate(Object leftValue, Operator operator, Object rightValue, // case: is rightValue a (single) numeric? try { double numericValue = Double.parseDouble(rightValue.toString()); + if (!Double.isFinite(numericValue)) { + // always reject "NaN" or "Infinity" + return false; + } return handleRightValueNumeric(leftValue, operator, numericValue, rule, context); } catch (NumberFormatException e) { } @@ -64,7 +68,7 @@ public boolean evaluate(Object leftValue, Operator operator, Object rightValue, // try to normalize Boolean values claimValue = normalizeToBoolean(claimValue); - rightValue = normalizeToBoolean(claimValue); + rightValue = normalizeToBoolean(rightValue); return switch (operator) { case EQ -> rightValue.equals(claimValue); @@ -83,8 +87,12 @@ private boolean handleRightValueList(Object leftValue, Operator operator, List !(claimValue instanceof List) && rightValueList.contains(claimValue); - + case IN -> { + if (claimValue instanceof List claimList) { + yield !claimList.isEmpty() && rightValueList.containsAll(claimList); + } + yield rightValueList.contains(claimValue); + } case IS_ANY_OF -> { if (claimValue instanceof List claimList) { yield !Collections.disjoint(claimList, rightValueList); @@ -114,7 +122,8 @@ private boolean handleRightValueList(Object leftValue, Operator operator, List claimList) { yield claimList.containsAll(rightValueList); } - yield false; + yield rightValueList.size() == 1 + && rightValueList.contains(claimValue); } case EQ -> claimValue instanceof List claimList && listsEqualAsSets(claimList, rightValueList); From db1e0ad128a9f059e35b4785afeff05d136d7fd9 Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Thu, 27 Aug 2026 09:07:20 +0200 Subject: [PATCH 07/11] chore: some readme additions --- edc-extensions/basic-abac/README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/edc-extensions/basic-abac/README.md b/edc-extensions/basic-abac/README.md index 7669684ba6..e913da832f 100644 --- a/edc-extensions/basic-abac/README.md +++ b/edc-extensions/basic-abac/README.md @@ -136,6 +136,32 @@ That means for example - a string containing five, i.e. `"5"` is equal to a numeric `5`. And both are equal to a decimal `5.0`. - a string whose content sort of looks like a boolean, e.g. `"true"`, `"FaLSE"` or `"TRUE"` will be interpreted as the corresponding boolean values `true` or `false` respectively. I.e. there is no case-sensitivity here. +#### Constraints with set-operators + +This extension can also handle odrl set operators, like for instance `isPartOf`, `isNoneOf`, etc. In these cases, the rightOperand is expected to be structured as a JSON list. However, due to some technical reasons, it is not possible to write such a list like a normal JSON list in the context of a "create policy-definition call" at the EDC-management API. I.e. something like this + +```json + { + "leftOperand": "https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.credentialSubject.accessLevels.barLevel.role", + "operator": "isAnyOf", + "rightOperand": ["Sheriff", "Marshal"] + } +``` + +should always be avoided. Note that in such cases, the EDC-management API will NOT react with a status code 400. But any policy that was created in such a way, will almost certainly not work as intended. + +Instead, you need to use the "stringified" version of such a JSON list like this: + +```json + { + "leftOperand": "https://w3id.org/constructx/credentials/v1.0/Baustelle123Credential.credentialSubject.accessLevels.barLevel.role", + "operator": "isAnyOf", + "rightOperand": "[\"Sheriff\", \"Marshal\"]" + } +``` + +I.e. the rightOperand formally is always a JSON string, whose content can be parsed as a JSON list. Note that "internal" quotation marks need to be escaped like in the example above. + #### Default Credential There is a technical necessity that the controlplane minimally needs at least one credential type it will expect or show to external partners. This is especially relevant when there is no credential-specific policy context, from which any credential types could possibly get extracted, i.e. when someone is trying to request someone else's EDC catalog. It can be defined with a property: From e5c01db79d39a7080966c0b602f8bc23f58a5528 Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Thu, 3 Sep 2026 14:24:29 +0200 Subject: [PATCH 08/11] chore: version bump & cleanup --- .../environments/local-con-x-env.bru | 13 ------------- .../Inspect Outcome/ShowConsumerCredentials.bru | 2 +- .../Inspect Outcome/ShowProviderCredentials.bru | 2 +- .../CreateConsumerParticipant.bru | 8 ++++---- .../RequestConsumerDevMemCredential.bru | 4 ++-- .../Prepare Consumer ID/RequestCustomCredential.bru | 4 ++-- .../Prepare Issuer/CreateIssuerParticipant.bru | 4 ++-- .../identities/Prepare Issuer/addConsumerHolder.bru | 4 ++-- .../identities/Prepare Issuer/addProviderHolder.bru | 4 ++-- .../Prepare Issuer/createCustomAttestation.bru | 4 ++-- .../Prepare Issuer/createCustomCredentialDef.bru | 4 ++-- .../Prepare Issuer/createDevAttestation.bru | 4 ++-- .../Prepare Issuer/createDevMemCredentialDef.bru | 4 ++-- .../CreateProviderParticipant.bru | 8 ++++---- .../Prepare Provider ID/RequestCustomCredential.bru | 4 ++-- .../RequestProviderDevMemCredential.bru | 4 ++-- .../Simulated DCP Flow/Consumer Token.bru | 2 +- .../Simulated DCP Flow/Provider Token.bru | 2 +- .../consumer/CheckNegotiationResult.bru | 2 +- .../transactions/consumer/Get EDR.bru | 2 +- .../transactions/consumer/InitPullTransfer.bru | 2 +- .../transactions/consumer/InitiateNegotiation.bru | 2 +- .../transactions/consumer/PullAssetData.bru | 2 +- .../consumer/RequestProviderCatalog.bru | 4 ++-- .../local/docker-compose.yaml | 6 +++--- 25 files changed, 44 insertions(+), 57 deletions(-) diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru index 0fe3fb29a8..3ba5c16c63 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/environments/local-con-x-env.bru @@ -6,12 +6,7 @@ vars { CONSUMER_IDHUB_ID_API: http://localhost:20100/api/identity CONSUMER_IDHUB_STS_API: http://localhost:20500/api/sts CONSUMER_IDHUB_CREDS_API: http://localhost:20600/api/credentials - ISSUER_APIKEY: Y29uLXgtaXNzdWVy.p6yHKIO6G+o0TtV6GIdV3kFP0ifmHRoWKiC+MaFnndprwWO065TyDQTfGPvJEO40JxEv8ABS2ADdjwCSNGW23A== - CONSUMER_IH_APIKEY: dXNlci1jb25zdW1lcg==.GHu6UMLWF/ZX1XKLUJJEooDEeSuzHGx6ikh1t0zwNYDJAd7PJmKIVyDixgnhD6S417qm3i9ghScv50XWJqTGjg== - PROVIDER_IH_APIKEY: dXNlci1wcm92aWRlcg==.RAFSKO0ddYkvzywB6Wql6cfsLgTAiOmdWdoOiU1rlgp5o1lpHuDhePnSSXjK/x+8COk9k6cXXhHjsMA9uOHguA== VAULTURL: http://localhost:8200 - CONSUMER_STS_SECRET: PXZKpcHs8LcCri4T - PROVIDER_STS_SECRET: On4okKfut4rVl9ce PROVIDER_MANAGEMENT: http://localhost:39010/management CONSUMER_MANAGEMENT: http://localhost:29010/management PROVIDER_DATAPLANE_PUBLIC: http://localhost:9500/public @@ -22,19 +17,11 @@ vars { ISS_ID: did:web:local-issuer-wallet:con-x-issuer CONS_ID: did:web:consumer-wallet:user:consumer PROV_ID: did:web:provider-wallet:user:provider - cons_access_token: eyJraWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsIm5iZiI6MTc4NzMxMjM0MCwic2NvcGUiOiJvcmcuZWNsaXBzZS5kc3BhY2UuZGNwLnZjLnR5cGU6aHR0cHM6Ly93M2lkLm9yZy9jb25zdHJ1Y3R4L2NyZWRlbnRpYWxzL3YxLjAvQ29uc3RydWN0WE1lbWJlcnNoaXBDcmVkZW50aWFsOnJlYWQiLCJpc3MiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwiZXhwIjoxNzg3MzEyNjQwLCJpYXQiOjE3ODczMTIzNDAsImp0aSI6ImFjY2Vzc3Rva2VuLWViYzg3NzkxLTg1MjAtNGFmZC1iMjBiLTQ1YjYwYzdlMjJlNiJ9.WT7uNu-ASoeteYmt-owlQD4xpwuhLcg2dALsZGBFghBHnL-XF1qymgilZmfX1Y3OSYdtV7oOflVUfzrLj_SNAw - prov_access_token: eyJraWQiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyI2tleS0xIiwiYWxnIjoiRWQyNTUxOSJ9.eyJzdWIiOiJkaWQ6d2ViOnByb3ZpZGVyLXdhbGxldDp1c2VyOnByb3ZpZGVyIiwiYXVkIjoiZGlkOndlYjpjb25zdW1lci13YWxsZXQ6dXNlcjpjb25zdW1lciIsIm5iZiI6MTc4NzMxMjM0MCwiaXNzIjoiZGlkOndlYjpwcm92aWRlci13YWxsZXQ6dXNlcjpwcm92aWRlciIsImV4cCI6MTc4NzMxMjY0MCwiaWF0IjoxNzg3MzEyMzQwLCJqdGkiOiI3MjhlYmJiMy02ODcyLTQzMWQtODRjMi01NThhYTJiN2YwMzYiLCJ0b2tlbiI6ImV5SnJhV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUkydGxlUzB4SWl3aVlXeG5Jam9pUldReU5UVXhPU0o5LmV5SmhkV1FpT2lKa2FXUTZkMlZpT21OdmJuTjFiV1Z5TFhkaGJHeGxkRHAxYzJWeU9tTnZibk4xYldWeUlpd2ljM1ZpSWpvaVpHbGtPbmRsWWpwd2NtOTJhV1JsY2kxM1lXeHNaWFE2ZFhObGNqcHdjbTkyYVdSbGNpSXNJbTVpWmlJNk1UYzROek14TWpNME1Dd2ljMk52Y0dVaU9pSnZjbWN1WldOc2FYQnpaUzVrYzNCaFkyVXVaR053TG5aakxuUjVjR1U2YUhSMGNITTZMeTkzTTJsa0xtOXlaeTlqYjI1emRISjFZM1I0TDJOeVpXUmxiblJwWVd4ekwzWXhMakF2UTI5dWMzUnlkV04wV0UxbGJXSmxjbk5vYVhCRGNtVmtaVzUwYVdGc09uSmxZV1FpTENKcGMzTWlPaUprYVdRNmQyVmlPbU52Ym5OMWJXVnlMWGRoYkd4bGREcDFjMlZ5T21OdmJuTjFiV1Z5SWl3aVpYaHdJam94TnpnM016RXlOalF3TENKcFlYUWlPakUzT0Rjek1USXpOREFzSW1wMGFTSTZJbUZqWTJWemMzUnZhMlZ1TFdWaVl6ZzNOemt4TFRnMU1qQXROR0ZtWkMxaU1qQmlMVFExWWpZd1l6ZGxNakpsTmlKOS5XVDd1TnUtQVNvZXRlWW10LW93bFFENHhwd3VoTGNnMmRBTHNaR0JGZ2hCSG5MLVhGMXF5bWdpbFptZlgxWTNPU1lkdFY3b09mbFZVZnpyTGpfU05BdyJ9.KxCDMdJspJ_1GoY0xU0pnnhHu88lPHjIhOtzi0iorr8Z_xPiMJXwPraLQ5wbPs6JclxPYQKFLNRWIh5ONzNdAg - offerId: Y29udHJhY3REZWZPbmU=:ZGVtb0Fzc2V0SWRPbmU=:MzdiY2YwMDItODMxNS00YzE2LWI0ZGQtZGUwNjI5NzkyMDg0 - transferId: b5c13c58-1c9f-445e-bd08-db55d3f59d5d - pullSecret: eyJraWQiOiJwcm92X3B1YiIsImFsZyI6IlJTMjU2In0.eyJpc3MiOiJhbm9ueW1vdXMiLCJhdWQiOiJkaWQ6d2ViOmNvbnN1bWVyLXdhbGxldDp1c2VyOmNvbnN1bWVyIiwic3ViIjoiYW5vbnltb3VzIiwiaWF0IjoxNzg3MzA2ODQ4LCJqdGkiOiI4MGE2MWEyMC0yMWZiLTQ3NGEtODgzNS1iNDkyMzVhZmUxMTgifQ.2WEIAOblqYvFKdMLxQ06w3z5aXu0wIhW4y6FyN3gcoDvA2GlTiBgz75r4IYX-eNtzPCaZvsGL4ln4xP692_vVjavSS8tNWQn5IIDdQZPAa0wwS7-dl23FwT57hdQHjetFwuu-HweZf3jlHhjEKmIFgcu0MxC4UFAiBbpi921eK8LEqCkjDcG0lLu-abRpGpTa-5Wf5ir0Seu8OXwOt-_yFqNkrDJZ6F5XnKZW0KaI7SPXip3Lwyfhmt6HbzLsO3Dm4A0R5EtGqpC4DQ-fL6WeGMsfBzsxCHpMWzhq76xO51JGv-0rfPp3djnWOkHcZuYs7OJEZZ3OnS1l6QhCd5dNw ISS_PART_CONT: con-x-issuer CONS_PART_CONT: user-consumer PROV_PART_CONT: user-provider MEM_CRED_ID: https://w3id.org/constructx/credentials/v1.0/ConstructXMembershipCredential CUSTOM_CRED_ID: https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential - catalogPermission: [{"action":"use","constraint":[{"and":{"leftOperand":"https://foo.org/constructx/bar/credentials/v1/Baustelle123Credential.credentialSubject.accessLevels.fooLevel","operator":"isAnyOf","rightOperand":"[2, 3, 4, 5]"}}]}] TARGET_ASSET_ID: demoAssetIdOne CRFORMAT: VC1_0_JWT - contractId: 25076811-0ad7-4c81-b624-dff173ea261b - negotiation-id: f7b964a2-ad7c-4ddf-8d81-6153f6020a2b } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowConsumerCredentials.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowConsumerCredentials.bru index 5886d0f70b..af7d10a50d 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowConsumerCredentials.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowConsumerCredentials.bru @@ -5,7 +5,7 @@ meta { } get { - url: {{CONSUMER_IDHUB_ID_API}}/v1alpha/credentials + url: {{CONSUMER_IDHUB_ID_API}}/v1beta/credentials body: none auth: none } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowProviderCredentials.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowProviderCredentials.bru index 072de2cdd0..0d78ee9618 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowProviderCredentials.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Inspect Outcome/ShowProviderCredentials.bru @@ -5,7 +5,7 @@ meta { } get { - url: {{PROVIDER_IDHUB_ID_API}}/v1alpha/credentials + url: {{PROVIDER_IDHUB_ID_API}}/v1beta/credentials body: none auth: none } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/CreateConsumerParticipant.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/CreateConsumerParticipant.bru index 6c2d065aa4..5e4731db28 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/CreateConsumerParticipant.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/CreateConsumerParticipant.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{CONSUMER_IDHUB_ID_API}}/v1alpha/participants + url: {{CONSUMER_IDHUB_ID_API}}/v1beta/participants body: json auth: inherit } @@ -26,7 +26,7 @@ body:json { { "id": "ConsumerIssuerService-ID", "type": "IssuerService", - "serviceEndpoint": "http://consumer-wallet:13132/api/issuance/v1alpha/participants/{{CONS_PART_CONT}}" + "serviceEndpoint": "http://consumer-wallet:13132/api/issuance/v1beta/participants/{{CONS_PART_CONT}}" } ], "active": true, @@ -52,11 +52,11 @@ script:pre-request { script:post-response { const apiKey = res.getBody().apiKey.trim(); if (apiKey) { - bru.setEnvVar("CONSUMER_IH_APIKEY", apiKey); + bru.setVar("CONSUMER_IH_APIKEY", apiKey); } const stsSecret = res.getBody().clientSecret.trim(); if (stsSecret) { - bru.setEnvVar("CONSUMER_STS_SECRET", stsSecret) + bru.setVar("CONSUMER_STS_SECRET", stsSecret) } } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru index 34ea31ccc1..d65c70ae00 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestConsumerDevMemCredential.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{CONSUMER_IDHUB_ID_API}}/v1alpha/participants/{{CONS_PART_CONT}}/credentials/request + url: {{CONSUMER_IDHUB_ID_API}}/v1beta/participants/{{CONS_PART_CONT}}/credentials/request body: json auth: none } @@ -25,7 +25,7 @@ script:pre-request { const cons_id = bru.getEnvVar("CONS_ID"); const participantContext = cons_id.split(":").slice(3).join(":").replace(/:/g, "-"); bru.setEnvVar("CONS_PART_CONT", participantContext); - req.setHeader("x-api-key", bru.getEnvVar("CONSUMER_IH_APIKEY")); + req.setHeader("x-api-key", bru.getVar("CONSUMER_IH_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru index 035cd8b845..cb905f720e 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Consumer ID/RequestCustomCredential.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{CONSUMER_IDHUB_ID_API}}/v1alpha/participants/{{CONS_PART_CONT}}/credentials/request + url: {{CONSUMER_IDHUB_ID_API}}/v1beta/participants/{{CONS_PART_CONT}}/credentials/request body: json auth: none } @@ -25,7 +25,7 @@ script:pre-request { const cons_id = bru.getEnvVar("CONS_ID"); const participantContext = cons_id.split(":").slice(3).join(":").replace(/:/g, "-"); bru.setEnvVar("CONS_PART_CONT", participantContext); - req.setHeader("x-api-key", bru.getEnvVar("CONSUMER_IH_APIKEY")); + req.setHeader("x-api-key", bru.getVar("CONSUMER_IH_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru index 380ab5c193..68e41b75f6 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{ISSUER_ID_API}}/v1alpha/participants + url: {{ISSUER_ID_API}}/v1beta/participants body: json auth: inherit } @@ -21,7 +21,7 @@ body:json { { "id": "IssuerIssuerService-ID", "type": "IssuerService", - "serviceEndpoint": "http://local-issuer-wallet:13132/api/issuance/v1alpha/participants/{{ISS_PART_CONT}}" + "serviceEndpoint": "http://local-issuer-wallet:13132/api/issuance/v1beta/participants/{{ISS_PART_CONT}}" }, { "id": "IssuerCredentialService-ID", diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addConsumerHolder.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addConsumerHolder.bru index 11f36cebcf..414e56ad2e 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addConsumerHolder.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addConsumerHolder.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/holders + url: {{ISSUER_ISS_API}}/v1beta/participants/{{ISS_PART_CONT}}/holders body: json auth: inherit } @@ -23,7 +23,7 @@ script:pre-request { const participantContext = cons_id.split(":").slice(3).join(":").replace(/:/g, "-"); bru.setEnvVar("CONS_PART_CONT", participantContext); - req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); + req.setHeader("x-api-key", bru.getVar("ISSUER_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addProviderHolder.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addProviderHolder.bru index e10bfc6ac6..8c886e8048 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addProviderHolder.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/addProviderHolder.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/holders + url: {{ISSUER_ISS_API}}/v1beta/participants/{{ISS_PART_CONT}}/holders body: json auth: inherit } @@ -23,7 +23,7 @@ script:pre-request { const participantContext = prov_id.split(":").slice(3).join(":").replace(/:/g, "-"); bru.setEnvVar("PROV_PART_CONT", participantContext); - req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); + req.setHeader("x-api-key", bru.getVar("ISSUER_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru index 33021772a4..435198d6a5 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomAttestation.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/attestations + url: {{ISSUER_ISS_API}}/v1beta/participants/{{ISS_PART_CONT}}/attestations body: json auth: none } @@ -33,7 +33,7 @@ body:json { } script:pre-request { - req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); + req.setHeader("x-api-key", bru.getVar("ISSUER_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru index 44c52954f7..890744f183 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createCustomCredentialDef.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/credentialdefinitions + url: {{ISSUER_ISS_API}}/v1beta/participants/{{ISS_PART_CONT}}/credentialdefinitions body: json auth: inherit } @@ -33,7 +33,7 @@ body:json { } script:pre-request { - req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); + req.setHeader("x-api-key", bru.getVar("ISSUER_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru index 7725a5901a..a6dfeda201 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevAttestation.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/attestations + url: {{ISSUER_ISS_API}}/v1beta/participants/{{ISS_PART_CONT}}/attestations body: json auth: none } @@ -28,7 +28,7 @@ body:json { } script:pre-request { - req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); + req.setHeader("x-api-key", bru.getVar("ISSUER_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru index f1f0b17da6..43752c2a08 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/createDevMemCredentialDef.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{ISSUER_ISS_API}}/v1alpha/participants/{{ISS_PART_CONT}}/credentialdefinitions + url: {{ISSUER_ISS_API}}/v1beta/participants/{{ISS_PART_CONT}}/credentialdefinitions body: json auth: inherit } @@ -33,7 +33,7 @@ body:json { } script:pre-request { - req.setHeader("x-api-key", bru.getEnvVar("ISSUER_APIKEY")); + req.setHeader("x-api-key", bru.getVar("ISSUER_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/CreateProviderParticipant.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/CreateProviderParticipant.bru index d37af1d728..066b5ca69f 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/CreateProviderParticipant.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/CreateProviderParticipant.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{PROVIDER_IDHUB_ID_API}}/v1alpha/participants + url: {{PROVIDER_IDHUB_ID_API}}/v1beta/participants body: json auth: inherit } @@ -26,7 +26,7 @@ body:json { { "id": "ProviderIssuerService-ID", "type": "IssuerService", - "serviceEndpoint": "http://provider-wallet:13132/api/issuance/v1alpha/participants/{{PROV_PART_CONT}}" + "serviceEndpoint": "http://provider-wallet:13132/api/issuance/v1beta/participants/{{PROV_PART_CONT}}" } ], "active": true, @@ -52,11 +52,11 @@ script:pre-request { script:post-response { const apiKey = res.getBody().apiKey.trim(); if (apiKey) { - bru.setEnvVar("PROVIDER_IH_APIKEY", apiKey); + bru.setVar("PROVIDER_IH_APIKEY", apiKey); } const stsSecret = res.getBody().clientSecret.trim(); if (stsSecret) { - bru.setEnvVar("PROVIDER_STS_SECRET", stsSecret) + bru.setVar("PROVIDER_STS_SECRET", stsSecret) } } diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru index dc360942ce..d1f5103241 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestCustomCredential.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{PROVIDER_IDHUB_ID_API}}/v1alpha/participants/{{PROV_PART_CONT}}/credentials/request + url: {{PROVIDER_IDHUB_ID_API}}/v1beta/participants/{{PROV_PART_CONT}}/credentials/request body: json auth: none } @@ -26,7 +26,7 @@ script:pre-request { const participantContext = prov_id.split(":").slice(3).join(":").replace(/:/g, "-"); bru.setEnvVar("PROV_PART_CONT", participantContext); - req.setHeader("x-api-key", bru.getEnvVar("PROVIDER_IH_APIKEY")); + req.setHeader("x-api-key", bru.getVar("PROVIDER_IH_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru index 9ddde8ec3e..47a2f15fcb 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Provider ID/RequestProviderDevMemCredential.bru @@ -5,7 +5,7 @@ meta { } post { - url: {{PROVIDER_IDHUB_ID_API}}/v1alpha/participants/{{PROV_PART_CONT}}/credentials/request + url: {{PROVIDER_IDHUB_ID_API}}/v1beta/participants/{{PROV_PART_CONT}}/credentials/request body: json auth: none } @@ -26,7 +26,7 @@ script:pre-request { const participantContext = prov_id.split(":").slice(3).join(":").replace(/:/g, "-"); bru.setEnvVar("PROV_PART_CONT", participantContext); - req.setHeader("x-api-key", bru.getEnvVar("PROVIDER_IH_APIKEY")); + req.setHeader("x-api-key", bru.getVar("PROVIDER_IH_APIKEY")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru index a4dbd74c7b..f0dbf38065 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Consumer Token.bru @@ -26,7 +26,7 @@ script:post-response { const payloadObject = JSON.parse(payload); const internalToken = payloadObject.token.trim(); - bru.setEnvVar("cons_access_token", internalToken); + bru.setVar("cons_access_token", internalToken); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Provider Token.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Provider Token.bru index 3ec92f6e2c..7edb4bc020 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Provider Token.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Simulated DCP Flow/Provider Token.bru @@ -21,7 +21,7 @@ body:form-urlencoded { script:post-response { const accessToken = res.getBody().access_token.trim(); - bru.setEnvVar("prov_access_token", accessToken); + bru.setVar("prov_access_token", accessToken); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/CheckNegotiationResult.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/CheckNegotiationResult.bru index d475666650..6fa3278388 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/CheckNegotiationResult.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/CheckNegotiationResult.bru @@ -16,7 +16,7 @@ headers { script:post-response { const contractId = res.getBody()['contractAgreementId']; - bru.setEnvVar("contractId", contractId); + bru.setVar("contractId", contractId); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/Get EDR.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/Get EDR.bru index cc00b204b0..7a50665e7a 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/Get EDR.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/Get EDR.bru @@ -16,7 +16,7 @@ headers { script:post-response { const authToken = res.getBody().authorization; - bru.setEnvVar("pullSecret", authToken); + bru.setVar("pullSecret", authToken); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitPullTransfer.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitPullTransfer.bru index b7635d665c..f394160806 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitPullTransfer.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitPullTransfer.bru @@ -26,7 +26,7 @@ body:json { script:post-response { const transferId = res.getBody()['@id']; - bru.setEnvVar("transferId", transferId); + bru.setVar("transferId", transferId); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru index 1b5b920f0a..11cf82a57e 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/InitiateNegotiation.bru @@ -39,7 +39,7 @@ body:json { script:post-response { var x = res.getBody()['@id']; console.log("id " + x); - bru.setEnvVar("negotiation-id", res.getBody()['@id']); + bru.setVar("negotiation-id", res.getBody()['@id']); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/PullAssetData.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/PullAssetData.bru index df33451a12..11b51d2670 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/PullAssetData.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/PullAssetData.bru @@ -11,7 +11,7 @@ get { } script:pre-request { - req.setHeader("Authorization", bru.getEnvVar("pullSecret")); + req.setHeader("Authorization", bru.getVar("pullSecret")); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru index eb6d6a38f0..e95eb423be 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/transactions/consumer/RequestProviderCatalog.bru @@ -46,8 +46,8 @@ script:post-response { offerId = ""; permissionArray = []; } - bru.setEnvVar("offerId", offerId); - bru.setEnvVar("catalogPermission", JSON.stringify(permissionArray)); + bru.setVar("offerId", offerId); + bru.setVar("catalogPermission", JSON.stringify(permissionArray)); } settings { diff --git a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml index 6e07f958be..a74861cb83 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml +++ b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml @@ -21,7 +21,7 @@ services: local-issuer-wallet: container_name: local-issuer-wallet - image: ghcr.io/project-construct-x/wallet:0.17.0-1 + image: ghcr.io/project-construct-x/wallet-sql-vault:0.18.0-1 pull_policy: missing depends_on: shared-postgres: @@ -121,7 +121,7 @@ services: consumer-wallet: container_name: consumer-wallet - image: ghcr.io/project-construct-x/wallet:0.17.0-1 + image: ghcr.io/project-construct-x/wallet-sql-vault:0.18.0-1 pull_policy: missing depends_on: shared-postgres: @@ -162,7 +162,7 @@ services: provider-wallet: container_name: provider-wallet - image: ghcr.io/project-construct-x/wallet:0.17.0-1 + image: ghcr.io/project-construct-x/wallet-sql-vault:0.18.0-1 pull_policy: missing depends_on: shared-postgres: From c84668b07c8382b8c249ada274087dbc6875a29c Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Thu, 3 Sep 2026 14:53:50 +0200 Subject: [PATCH 09/11] fix: missing build.gradle --- edc-extensions/basic-abac/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edc-extensions/basic-abac/build.gradle.kts b/edc-extensions/basic-abac/build.gradle.kts index b78ccc964f..c411caadcf 100644 --- a/edc-extensions/basic-abac/build.gradle.kts +++ b/edc-extensions/basic-abac/build.gradle.kts @@ -21,7 +21,7 @@ plugins { repositories { mavenCentral() } -val edcVersion = "0.15.1" +val edcVersion = project.property("con-x-edcVersion") as String dependencies { implementation("org.eclipse.edc:core-spi:${edcVersion}") From 884478b94445f8ad9b1a545407e9e477c5b00ccc Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Thu, 3 Sep 2026 15:37:07 +0200 Subject: [PATCH 10/11] fix: missing edit --- .../identities/Prepare Issuer/CreateIssuerParticipant.bru | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru index 68e41b75f6..ff1d5055c2 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru +++ b/edc-controlplane/edc-controlplane-construct-x/local/bruno/con-x-local-test/identities/Prepare Issuer/CreateIssuerParticipant.bru @@ -52,7 +52,7 @@ script:pre-request { script:post-response { const apiKey = res.getBody().apiKey.trim(); if (apiKey) { - bru.setEnvVar("ISSUER_APIKEY", apiKey); + bru.setVar("ISSUER_APIKEY", apiKey); } } From a26ba4d200bbc80c8f2c34546a4e1e1a0432050b Mon Sep 17 00:00:00 2001 From: Ernst-Christoph Schrewe Date: Tue, 8 Sep 2026 13:12:15 +0200 Subject: [PATCH 11/11] fix: deprecated property --- .../edc-controlplane-construct-x/local/docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml index a74861cb83..6403310355 100644 --- a/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml +++ b/edc-controlplane/edc-controlplane-construct-x/local/docker-compose.yaml @@ -207,7 +207,7 @@ services: pull_policy: never environment: - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 - - edc.iam.trusted-issuer.example.id=did:web:local-issuer-wallet:con-x-issuer + - edc.iam.trustedissuer.example.id=did:web:local-issuer-wallet:con-x-issuer - edc.iam.did.web.use.https=false - edc.iam.sts.oauth.client.secret.alias=consumersecret - edc.iam.credential.revocation.mimetype=application/json @@ -303,7 +303,7 @@ services: pull_policy: never environment: - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0.0.0.0:5005 - - edc.iam.trusted-issuer.example.id=did:web:local-issuer-wallet:con-x-issuer + - edc.iam.trustedissuer.example.id=did:web:local-issuer-wallet:con-x-issuer - edc.iam.did.web.use.https=false - edc.iam.sts.oauth.client.secret.alias=providersecret - edc.iam.credential.revocation.mimetype=application/json