diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataRequest.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataRequest.java index 9e5fb1f..5e3b423 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataRequest.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/api/data/DataRequest.java @@ -14,8 +14,11 @@ package org.eclipse.edc.virtualized.api.data; +import org.jetbrains.annotations.Nullable; + public record DataRequest( String providerId, - String policyId + String policyId, + @Nullable String policyType ) { } diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/Data.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/Data.java index 3469d27..190ba24 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/Data.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/Data.java @@ -21,6 +21,8 @@ import org.eclipse.edc.policy.model.Permission; import org.eclipse.edc.policy.model.Policy; +import java.util.Map; + public class Data { public static final Policy MEMBERSHIP_POLICY = Policy.Builder.newInstance() .permission(Permission.Builder.newInstance() @@ -34,4 +36,19 @@ public class Data { .build()) .build()) .build(); + + public static final Policy MANUFACTURER_POLICY = Policy.Builder.newInstance() + .permission(Permission.Builder.newInstance() + .action(Action.Builder.newInstance() + .type("http://www.w3.org/ns/odrl/2/use") + .build()) + .constraint(AtomicConstraint.Builder.newInstance() + .leftExpression(new LiteralExpression("ManufacturerCredential")) + .operator(Operator.EQ) + .rightExpression(new LiteralExpression("active")) + .build()) + .build()) + .build(); + + public static final Map POLICY_MAP = Map.of("membership", MEMBERSHIP_POLICY, "manufacturer", MANUFACTURER_POLICY); } diff --git a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java index 0fa8e80..bfaad07 100644 --- a/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java +++ b/extensions/api/mgmt/src/main/java/org/eclipse/edc/virtualized/service/DataRequestService.java @@ -43,6 +43,8 @@ import static java.net.http.HttpClient.newHttpClient; import static java.util.Optional.ofNullable; +import static org.eclipse.edc.virtualized.service.Data.MEMBERSHIP_POLICY; +import static org.eclipse.edc.virtualized.service.Data.POLICY_MAP; /** * this is a wrapper service that initiates the contract negotiation and the transfer process, waits for its completion, and then downloads the data. @@ -65,7 +67,7 @@ public DataRequestService(ContractNegotiationService contractNegotiationService, public CompletableFuture> getData(ParticipantContext participantContext, DataRequest dataRequest) { return initiateContractNegotiation(participantContext, dataRequest) .thenCompose(this::waitForContractNegotiation) - .thenCompose(contractNegotiation -> startTransferProcess(participantContext, contractNegotiation)) + .thenCompose(agreement -> startTransferProcess(participantContext, agreement)) .thenCompose(this::waitForTransferProcess) .thenCompose(transferProcess -> getEdr(transferProcess.getId())) .thenCompose(this::downloadData) @@ -87,6 +89,9 @@ private CompletableFuture initiateContractNegotiation(ParticipantContext if (addressForDid.failed()) { return CompletableFuture.failedFuture(new RuntimeException("Could not resolve address for did: %s".formatted(addressForDid.getFailureDetail()))); } + + var policy = ofNullable(dataRequest.policyType()).map(POLICY_MAP::get).orElse(MEMBERSHIP_POLICY); + var offerId = ContractOfferId.parseId(dataRequest.policyId()); var rq = ContractRequest.Builder.newInstance() .protocol("dataspace-protocol-http:2025-1") @@ -94,7 +99,7 @@ private CompletableFuture initiateContractNegotiation(ParticipantContext .contractOffer(ContractOffer.Builder.newInstance() .id(dataRequest.policyId()) .assetId(offerId.getContent().assetIdPart()) - .policy(Data.MEMBERSHIP_POLICY.toBuilder() + .policy(policy.toBuilder() .target(offerId.getContent().assetIdPart()) .assigner(dataRequest.providerId()) .type(PolicyType.OFFER) diff --git a/k8s/apps/controlplane-config.yaml b/k8s/apps/controlplane-config.yaml index 2d36984..4afff0f 100644 --- a/k8s/apps/controlplane-config.yaml +++ b/k8s/apps/controlplane-config.yaml @@ -69,6 +69,11 @@ data: edc.iam.dcp.scopes.membership.type: "DEFAULT" edc.iam.dcp.scopes.membership.value: "org.eclipse.edc.vc.type:MembershipCredential:read" + edc.iam.dcp.scopes.manufacturer.id: "manufacturer-scope" + edc.iam.dcp.scopes.manufacturer.type: "POLICY" + edc.iam.dcp.scopes.manufacturer.value: "org.eclipse.edc.vc.type:ManufacturerCredential:read" + edc.iam.dcp.scopes.manufacturer.prefix-mapping: "ManufacturerCredential" + # Trusted Issuers edc.iam.trusted-issuer.issuer.id: "did:web:issuerservice.edc-v.svc.cluster.local%3A10016:issuer" diff --git a/k8s/apps/issuerservice-seed-job.yaml b/k8s/apps/issuerservice-seed-job.yaml index c22e386..c80723f 100644 --- a/k8s/apps/issuerservice-seed-job.yaml +++ b/k8s/apps/issuerservice-seed-job.yaml @@ -82,7 +82,7 @@ spec: # Create Keycloak client for Vault access echo "Creating Vault Access Client" - curl -sf -X POST "${KC_HOST}/admin/realms/edcv/clients" \ + if curl -sf -X POST "${KC_HOST}/admin/realms/edcv/clients" \ -H "Authorization: Bearer ${KC_TOKEN}" \ -H "Content-Type: application/json" \ -d '{ @@ -127,9 +127,11 @@ spec: } } ] - }' - - echo "✓ Vault Access Token created" + }'; then + echo "✓ Vault Access Token created" + else + echo "⚠ Vault Access Client creation failed (may already exist)" + fi echo "" echo "================================================" @@ -192,7 +194,7 @@ spec: echo "" echo "================================================" - echo "Step 3: Create AttestationDefinition" + echo "Step 3: Create AttestationDefinitions" echo "================================================" # Get issuer token @@ -208,7 +210,8 @@ spec: exit 1 fi - # Create attestation definition + # Create attestation definitions + echo "Creating Membership AttestationDefinition" curl -sf -X POST "http://issuerservice.edc-v.svc.cluster.local:10013/api/admin/v1alpha/participants/aXNzdWVy/attestations" \ -H "Authorization: Bearer ${ISSUER_TOKEN}" \ -H "Content-Type: application/json" \ @@ -217,15 +220,26 @@ spec: "configuration": {}, "id": "membership-attestation-def-1" }' + + echo "Creating Manufacturer AttestationDefinition" + curl -sf -X POST "http://issuerservice.edc-v.svc.cluster.local:10013/api/admin/v1alpha/participants/aXNzdWVy/attestations" \ + -H "Authorization: Bearer ${ISSUER_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "attestationType": "manufacturer", + "configuration": {}, + "id": "manufacturer-attestation-def-1" + }' - echo "✓ AttestationDefinition created" + echo "✓ AttestationDefinitions created" echo "" echo "================================================" - echo "Step 4: Create CredentialDefinition" + echo "Step 4: Create CredentialDefinitions" echo "================================================" - # Create credential definition + # Create credential definitions + echo "Creating Membership CredentialDefinition" curl -sf -X POST "http://issuerservice.edc-v.svc.cluster.local:10013/api/admin/v1alpha/participants/aXNzdWVy/credentialdefinitions" \ -H "Authorization: Bearer ${ISSUER_TOKEN}" \ -H "Content-Type: application/json" \ @@ -256,6 +270,38 @@ spec: "format": "VC1_0_JWT", "validity": "604800" }' + + echo "Creating Manufacturer CredentialDefinition" + curl -sf -X POST "http://issuerservice.edc-v.svc.cluster.local:10013/api/admin/v1alpha/participants/aXNzdWVy/credentialdefinitions" \ + -H "Authorization: Bearer ${ISSUER_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{ + "attestations": ["manufacturer-attestation-def-1"], + "credentialType": "ManufacturerCredential", + "id": "manufacturer-credential-def", + "jsonSchema": "{}", + "jsonSchemaUrl": "https://example.com/schema/manufacturer-credential.json", + "mappings": [ + { + "input": "contractVersion", + "output": "credentialSubject.contractVersion", + "required": true + }, + { + "input": "component_types", + "output": "credentialSubject.part_types", + "required": "true" + }, + { + "input": "since", + "output": "credentialSubject.since", + "required": true + } + ], + "rules": [], + "format": "VC1_0_JWT", + "validity": "604800" + }' echo "✓ CredentialDefinition created" echo "" diff --git a/k8s/apps/tenant-manager-seed-job.yaml b/k8s/apps/tenant-manager-seed-job.yaml index b22227a..7a46b4e 100644 --- a/k8s/apps/tenant-manager-seed-job.yaml +++ b/k8s/apps/tenant-manager-seed-job.yaml @@ -88,7 +88,15 @@ spec: "issuer": "did:web:issuerservice.edc-v.svc.cluster.local%3A10016:issuer", "format": "VC1_0_JWT", "id": "membership-credential-def" - }] + }, + { + "type": "ManufacturerCredential", + "issuer": "did:web:issuerservice.edc-v.svc.cluster.local%3A10016:issuer", + "format": "VC1_0_JWT", + "id": "manufacturer-credential-def", + "role": "manufacturer" + } + ] } }') diff --git a/k8s/apps/ui.yaml b/k8s/apps/ui.yaml index bff040d..a621a41 100644 --- a/k8s/apps/ui.yaml +++ b/k8s/apps/ui.yaml @@ -36,7 +36,6 @@ spec: containers: - name: jad-web-ui image: -# ghcr.io/aruba-it-s-p-a/edc-client-participant-fe:3b202e0bb6c88ddef712686e635e25357852377a ghcr.io/aruba-it-s-p-a/edc-public-participant-portal:demo-madrid ports: - containerPort: 80 diff --git a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationExtension.java b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationExtension.java similarity index 67% rename from launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationExtension.java rename to launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationExtension.java index e2c35e1..439cb13 100644 --- a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationExtension.java +++ b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationExtension.java @@ -12,7 +12,7 @@ * */ -package org.eclipse.edc.issuerservice.seed.attestation.dataprocessor; +package org.eclipse.edc.issuerservice.seed.attestation.manufacturer; import org.eclipse.edc.issuerservice.spi.issuance.attestation.AttestationDefinitionValidatorRegistry; import org.eclipse.edc.issuerservice.spi.issuance.attestation.AttestationSourceFactoryRegistry; @@ -21,11 +21,11 @@ import org.eclipse.edc.spi.system.ServiceExtension; import org.eclipse.edc.spi.system.ServiceExtensionContext; -import static org.eclipse.edc.issuerservice.seed.attestation.dataprocessor.DataProcessorAttestationExtension.NAME; +import static org.eclipse.edc.issuerservice.seed.attestation.manufacturer.ManufacturerAttestationExtension.NAME; @Extension(NAME) -public class DataProcessorAttestationExtension implements ServiceExtension { - public static final String NAME = "DataProcessor Attestations Extension"; +public class ManufacturerAttestationExtension implements ServiceExtension { + public static final String NAME = "Manufacturer Attestations Extension"; @Inject private AttestationSourceFactoryRegistry registry; @@ -40,7 +40,7 @@ public String name() { @Override public void initialize(ServiceExtensionContext context) { - registry.registerFactory("dataprocessor", new DataProcessorAttestationSourceFactory()); - validatorRegistry.registerValidator("dataprocessor", new DataProcessorAttestationSourceValidator()); + registry.registerFactory("manufacturer", new ManufacturerAttestationSourceFactory()); + validatorRegistry.registerValidator("manufacturer", new ManufacturerAttestationSourceValidator()); } } diff --git a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSource.java b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSource.java similarity index 77% rename from launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSource.java rename to launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSource.java index 67465f4..b659fad 100644 --- a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSource.java +++ b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSource.java @@ -12,24 +12,26 @@ * */ -package org.eclipse.edc.issuerservice.seed.attestation.dataprocessor; +package org.eclipse.edc.issuerservice.seed.attestation.manufacturer; import org.eclipse.edc.issuerservice.spi.issuance.attestation.AttestationContext; import org.eclipse.edc.issuerservice.spi.issuance.attestation.AttestationSource; import org.eclipse.edc.spi.result.Result; +import java.time.Instant; import java.util.Map; -public record DataProcessorAttestationSource(Map config) implements AttestationSource { +public record ManufacturerAttestationSource(Map config) implements AttestationSource { private static final String DEFAULT_CONTRACT_VERSION = "1.0.0"; - private static final String LEVEL = "processing"; @Override public Result> execute(AttestationContext context) { var contractVersion = config.getOrDefault("contractVersion", DEFAULT_CONTRACT_VERSION); + return Result.success(Map.of( "contractVersion", contractVersion, - "level", LEVEL, + "component_types", "all", + "since", Instant.now().toString(), "id", context.participantContextId() )); } diff --git a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSourceFactory.java b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSourceFactory.java similarity index 78% rename from launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSourceFactory.java rename to launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSourceFactory.java index b24a6ed..29047ce 100644 --- a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSourceFactory.java +++ b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSourceFactory.java @@ -12,16 +12,16 @@ * */ -package org.eclipse.edc.issuerservice.seed.attestation.dataprocessor; +package org.eclipse.edc.issuerservice.seed.attestation.manufacturer; import org.eclipse.edc.issuerservice.spi.issuance.attestation.AttestationSource; import org.eclipse.edc.issuerservice.spi.issuance.attestation.AttestationSourceFactory; import org.eclipse.edc.issuerservice.spi.issuance.model.AttestationDefinition; -public class DataProcessorAttestationSourceFactory implements AttestationSourceFactory { +public class ManufacturerAttestationSourceFactory implements AttestationSourceFactory { @Override public AttestationSource createSource(AttestationDefinition definition) { var config = definition.getConfiguration(); - return new DataProcessorAttestationSource(config); + return new ManufacturerAttestationSource(config); } } diff --git a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSourceValidator.java b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSourceValidator.java similarity index 80% rename from launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSourceValidator.java rename to launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSourceValidator.java index 54146a6..13518c8 100644 --- a/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/dataprocessor/DataProcessorAttestationSourceValidator.java +++ b/launchers/issuerservice/src/main/java/org/eclipse/edc/issuerservice/seed/attestation/manufacturer/ManufacturerAttestationSourceValidator.java @@ -12,13 +12,13 @@ * */ -package org.eclipse.edc.issuerservice.seed.attestation.dataprocessor; +package org.eclipse.edc.issuerservice.seed.attestation.manufacturer; import org.eclipse.edc.issuerservice.spi.issuance.model.AttestationDefinition; import org.eclipse.edc.validator.spi.ValidationResult; import org.eclipse.edc.validator.spi.Validator; -public class DataProcessorAttestationSourceValidator implements Validator { +public class ManufacturerAttestationSourceValidator implements Validator { @Override public ValidationResult validate(AttestationDefinition input) { return ValidationResult.success(); diff --git a/launchers/issuerservice/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension b/launchers/issuerservice/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension index b144ca3..5a13bf3 100644 --- a/launchers/issuerservice/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension +++ b/launchers/issuerservice/src/main/resources/META-INF/services/org.eclipse.edc.spi.system.ServiceExtension @@ -12,4 +12,4 @@ # # org.eclipse.edc.issuerservice.seed.attestation.membership.MembershipAttestationsExtension -org.eclipse.edc.issuerservice.seed.attestation.dataprocessor.DataProcessorAttestationExtension \ No newline at end of file +org.eclipse.edc.issuerservice.seed.attestation.manufacturer.ManufacturerAttestationExtension \ No newline at end of file diff --git a/requests/EDC-V Onboarding/CFM - Provision Consumer/Create a new Tenant.bru b/requests/EDC-V Onboarding/CFM - Provision Consumer/Create a new Tenant.bru index 5a3475e..e707ad2 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Consumer/Create a new Tenant.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Consumer/Create a new Tenant.bru @@ -1,7 +1,7 @@ meta { name: Create a new Tenant type: http - seq: 2 + seq: 3 } post { diff --git a/requests/EDC-V Onboarding/CFM - Provision Consumer/Deploy Participant Profile.bru b/requests/EDC-V Onboarding/CFM - Provision Consumer/Deploy Participant Profile.bru index 95f5ab0..a1bdd14 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Consumer/Deploy Participant Profile.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Consumer/Deploy Participant Profile.bru @@ -1,7 +1,7 @@ meta { name: Deploy Participant Profile type: http - seq: 3 + seq: 4 } post { @@ -13,12 +13,16 @@ post { body:json { { "id": "{{$randomAlphaNumeric}}", - "identifier": "{{participant_did}}", - "properties": { - }, + "identifier": "{{participant_did}}-{{$randomNamePrefix}}", + "properties": {}, "cellId": "{{cell_id}}", "version": 0, - "vpaProperties": {} + "vpaProperties": {}, + "participantRoles": { + "{{dataspace_id}}": [ + "manufacturer" + ] + } } } diff --git a/requests/EDC-V Onboarding/CFM - Provision Consumer/Get Participant Profile.bru b/requests/EDC-V Onboarding/CFM - Provision Consumer/Get Participant Profile.bru index 1a46f87..5ef2b9b 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Consumer/Get Participant Profile.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Consumer/Get Participant Profile.bru @@ -1,7 +1,7 @@ meta { name: Get Participant Profile type: http - seq: 4 + seq: 5 } get { diff --git a/requests/EDC-V Onboarding/CFM - Provision Consumer/Obtain Secret from Vault.bru b/requests/EDC-V Onboarding/CFM - Provision Consumer/Obtain Secret from Vault.bru index a4fcb0b..b99c06f 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Consumer/Obtain Secret from Vault.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Consumer/Obtain Secret from Vault.bru @@ -1,7 +1,7 @@ meta { name: Obtain Secret from Vault type: http - seq: 5 + seq: 6 } get { diff --git a/requests/EDC-V Onboarding/CFM - Provision Consumer/TM- Get Dataspace Profiles.bru b/requests/EDC-V Onboarding/CFM - Provision Consumer/TM- Get Dataspace Profiles.bru new file mode 100644 index 0000000..d5f5672 --- /dev/null +++ b/requests/EDC-V Onboarding/CFM - Provision Consumer/TM- Get Dataspace Profiles.bru @@ -0,0 +1,22 @@ +meta { + name: TM: Get Dataspace Profiles + type: http + seq: 2 +} + +get { + url: {{tmBaseUrl}}/api/v1alpha1/dataspace-profiles + body: json + auth: inherit +} + +script:post-response { + let body = res.getBody() + test("Response contains id", function () { + expect(body[0]).to.have.property("id"); + }); + + if (body && body[0].id) { + bru.setVar("dataspace_id", body[0].id); + } +} diff --git a/requests/EDC-V Onboarding/CFM - Provision Consumer/folder.bru b/requests/EDC-V Onboarding/CFM - Provision Consumer/folder.bru index 160578d..9105dc3 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Consumer/folder.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Consumer/folder.bru @@ -1,6 +1,6 @@ meta { name: CFM - Provision Consumer - seq: 1 + seq: 2 } auth { diff --git a/requests/EDC-V Onboarding/CFM - Provision Provider/Create a new Tenant.bru b/requests/EDC-V Onboarding/CFM - Provision Provider/Create a new Tenant.bru index 5a3475e..e707ad2 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Provider/Create a new Tenant.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Provider/Create a new Tenant.bru @@ -1,7 +1,7 @@ meta { name: Create a new Tenant type: http - seq: 2 + seq: 3 } post { diff --git a/requests/EDC-V Onboarding/CFM - Provision Provider/Deploy Participant Profile.bru b/requests/EDC-V Onboarding/CFM - Provision Provider/Deploy Participant Profile.bru index 6544e72..63a6d5b 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Provider/Deploy Participant Profile.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Provider/Deploy Participant Profile.bru @@ -1,7 +1,7 @@ meta { name: Deploy Participant Profile type: http - seq: 3 + seq: 4 } post { @@ -14,11 +14,15 @@ body:json { { "id": "{{$randomAlphaNumeric}}", "identifier": "{{participant_did}}", - "properties": { - }, + "properties": {}, "cellId": "{{cell_id}}", "version": 0, - "vpaProperties": {} + "vpaProperties": {}, + "participantRoles": { + "{{dataspace_id}}": [ + "manufacturer" + ] + } } } diff --git a/requests/EDC-V Onboarding/CFM - Provision Provider/Get Participant Profile.bru b/requests/EDC-V Onboarding/CFM - Provision Provider/Get Participant Profile.bru index 81742d2..2b088d0 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Provider/Get Participant Profile.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Provider/Get Participant Profile.bru @@ -1,7 +1,7 @@ meta { name: Get Participant Profile type: http - seq: 4 + seq: 5 } get { diff --git a/requests/EDC-V Onboarding/CFM - Provision Provider/Obtain Secret from Vault.bru b/requests/EDC-V Onboarding/CFM - Provision Provider/Obtain Secret from Vault.bru index 5072052..aad5bbc 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Provider/Obtain Secret from Vault.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Provider/Obtain Secret from Vault.bru @@ -1,7 +1,7 @@ meta { name: Obtain Secret from Vault type: http - seq: 5 + seq: 6 } get { diff --git a/requests/EDC-V Onboarding/CFM - Provision Provider/TM- Get Dataspace Profiles.bru b/requests/EDC-V Onboarding/CFM - Provision Provider/TM- Get Dataspace Profiles.bru new file mode 100644 index 0000000..d5f5672 --- /dev/null +++ b/requests/EDC-V Onboarding/CFM - Provision Provider/TM- Get Dataspace Profiles.bru @@ -0,0 +1,22 @@ +meta { + name: TM: Get Dataspace Profiles + type: http + seq: 2 +} + +get { + url: {{tmBaseUrl}}/api/v1alpha1/dataspace-profiles + body: json + auth: inherit +} + +script:post-response { + let body = res.getBody() + test("Response contains id", function () { + expect(body[0]).to.have.property("id"); + }); + + if (body && body[0].id) { + bru.setVar("dataspace_id", body[0].id); + } +} diff --git a/requests/EDC-V Onboarding/CFM - Provision Provider/folder.bru b/requests/EDC-V Onboarding/CFM - Provision Provider/folder.bru index 1afb117..f757417 100644 --- a/requests/EDC-V Onboarding/CFM - Provision Provider/folder.bru +++ b/requests/EDC-V Onboarding/CFM - Provision Provider/folder.bru @@ -1,6 +1,6 @@ meta { name: CFM - Provision Provider - seq: 2 + seq: 3 } auth { diff --git a/requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression (Manufacturing).bru b/requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression (Manufacturing).bru new file mode 100644 index 0000000..9b1a50f --- /dev/null +++ b/requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression (Manufacturing).bru @@ -0,0 +1,35 @@ +meta { + name: Create CEL expression (Manufacturing) + type: http + seq: 4 +} + +post { + url: {{cpBaseUrl}}/api/mgmt/v4alpha/celexpressions + body: json + auth: inherit +} + +body:json { + { + "@context": [ + "https://w3id.org/edc/connector/management/v2", + "https://w3id.org/edc/virtual-connector/management/v2" + ], + "@type": "CelExpression", + "@id": "manufacturer_expr", + "leftOperand": "ManufacturerCredential", + "description": "Expression for evaluating manufacturer credentials", + "scopes": [ + "catalog", + "contract.negotiation", + "transfer.process" + ], + "expression": "ctx.agent.claims.vc.filter(c, c.type.exists(t, t == 'ManufacturerCredential')).exists(c, c.credentialSubject.exists(cs, timestamp(cs.since) < now))" + } +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression.bru b/requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression (Membership).bru similarity index 94% rename from requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression.bru rename to requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression (Membership).bru index b60fa9e..958d3b4 100644 --- a/requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression.bru +++ b/requests/EDC-V Onboarding/EDC-V Management/Prepare Consumer Participant/Create CEL expression (Membership).bru @@ -1,5 +1,5 @@ meta { - name: Create CEL expression + name: Create CEL expression (Membership) type: http seq: 2 } diff --git a/requests/EDC-V Onboarding/EDC-V Management/folder.bru b/requests/EDC-V Onboarding/EDC-V Management/folder.bru index a9a34a8..26faa72 100644 --- a/requests/EDC-V Onboarding/EDC-V Management/folder.bru +++ b/requests/EDC-V Onboarding/EDC-V Management/folder.bru @@ -1,5 +1,6 @@ meta { name: EDC-V Management + seq: 4 } auth { diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/DataTransferEndToEndTest.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/DataTransferEndToEndTest.java index f996e5d..f4a1f58 100644 --- a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/DataTransferEndToEndTest.java +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/DataTransferEndToEndTest.java @@ -56,8 +56,8 @@ public class DataTransferEndToEndTest { private static final ConsoleMonitor MONITOR = new ConsoleMonitor(ConsoleMonitor.Level.DEBUG, true); private static ClientCredentials providerCredentials; private static ClientCredentials consumerCredentials; - private static String consumerContextId; private static String providerContextId; + private static ClientCredentials manufacturerCredentials; static String loadResourceFile(String resourceName) { @@ -85,15 +85,16 @@ static void prepare() { var slug = Instant.now().getEpochSecond(); var adminToken = createKeycloakToken("admin", "edc-v-admin-secret", "issuer-admin-api:write", "identity-api:write", "management-api:write", "identity-api:read"); - createCelExpression(adminToken); + createCelExpression(adminToken, "membership_cel_expression.json"); + createCelExpression(adminToken, "manufacturer_cel_expression.json"); MONITOR.info("Create cell and dataspace profile"); var cellId = getCellId(); // onboard consumer - MONITOR.info("Onboarding consumer"); + MONITOR.info("Onboarding (standard) consumer"); var consumerName = "consumer-" + slug; - consumerContextId = "did:web:identityhub.edc-v.svc.cluster.local%3A7083:" + consumerName; + var consumerContextId = "did:web:identityhub.edc-v.svc.cluster.local%3A7083:" + consumerName; var po = new ParticipantOnboarding(consumerName, consumerContextId, VAULT_TOKEN, MONITOR.withPrefix("Consumer " + slug)); consumerCredentials = po.execute(cellId); @@ -102,16 +103,24 @@ static void prepare() { var providerName = "provider-" + slug; providerContextId = "did:web:identityhub.edc-v.svc.cluster.local%3A7083:" + providerName; var providerPo = new ParticipantOnboarding(providerName, providerContextId, VAULT_TOKEN, MONITOR.withPrefix("Provider " + slug)); - providerCredentials = providerPo.execute(cellId); + providerCredentials = providerPo.execute(cellId, "manufacturer"); + + // onboard manufacturer consumer - only this one will see some assets + MONITOR.info("Onboarding manufacturer consumer"); + var name = "manufacturer-" + slug; + var manufacturerContextId = "did:web:identityhub.edc-v.svc.cluster.local%3A7083:" + name; + var manufacturerPo = new ParticipantOnboarding(name, manufacturerContextId, VAULT_TOKEN, MONITOR.withPrefix("Manufacturer " + slug)); + manufacturerCredentials = manufacturerPo.execute(cellId, "manufacturer"); } /** * Creates a Common Expression Language (CEL) entry in the control plane * - * @param accessToken OAuth2 token + * @param accessToken OAuth2 token + * @param resourceName name of the resource file that contains the CEL expression. */ - private static void createCelExpression(String accessToken) { - var template = loadResourceFile("create_cel_expression.json"); + private static void createCelExpression(String accessToken, String resourceName) { + var template = loadResourceFile(resourceName); given() .baseUri(CONTROLPLANE_BASE_URL) @@ -144,10 +153,10 @@ void testTodoDataTransfer() { MONITOR.info("Seeding provider"); var providerAccessToken = getAccessToken(providerCredentials.clientId(), providerCredentials.clientSecret(), "management-api:write").accessToken(); - var assetId = createAsset(providerCredentials.clientId(), providerAccessToken); - var policyDefId = createPolicyDef(providerCredentials.clientId(), providerAccessToken); - createContractDef(providerCredentials.clientId(), providerAccessToken, policyDefId, assetId); - registerDataplane(providerCredentials.clientId(), providerAccessToken); + var assetId = createAsset(providerCredentials.clientId(), providerAccessToken, "asset.json"); + var policyDefId = createPolicyDef(providerCredentials.clientId(), providerAccessToken, "policy-def.json"); + createContractDef(providerCredentials.clientId(), providerAccessToken, policyDefId, policyDefId, assetId); + registerDataPlane(providerCredentials.clientId(), providerAccessToken); // perform data transfer MONITOR.info("Starting data transfer"); @@ -183,9 +192,9 @@ void testCertDataTransfer() { var providerAccessToken = getAccessToken(providerCredentials.clientId(), providerCredentials.clientSecret(), "management-api:write").accessToken(); var assetId = createCertAsset(providerCredentials.clientId(), providerAccessToken); - var policyDefId = createPolicyDef(providerCredentials.clientId(), providerAccessToken); - createContractDef(providerCredentials.clientId(), providerAccessToken, policyDefId, assetId); - registerDataplane(providerCredentials.clientId(), providerAccessToken); + var policyDefId = createPolicyDef(providerCredentials.clientId(), providerAccessToken, "policy-def.json"); + createContractDef(providerCredentials.clientId(), providerAccessToken, policyDefId, policyDefId, assetId); + registerDataPlane(providerCredentials.clientId(), providerAccessToken); // perform data transfer MONITOR.info("Starting data transfer"); @@ -226,6 +235,61 @@ void testCertDataTransfer() { assertThat(list).isEmpty(); } + @Test + void testTransferLimitedAccess() { + // seed provider + MONITOR.info("Seeding provider"); + var providerAccessToken = getAccessToken(providerCredentials.clientId(), providerCredentials.clientSecret(), "management-api:write").accessToken(); + + var assetId = createAsset(providerCredentials.clientId(), providerAccessToken, "asset-restricted.json"); + var accessPolicyId = createPolicyDef(providerCredentials.clientId(), providerAccessToken, "policy-def.json"); + var contractPolicyId = createPolicyDef(providerCredentials.clientId(), providerAccessToken, "policy-def-manufacturer.json"); + createContractDef(providerCredentials.clientId(), providerAccessToken, accessPolicyId, contractPolicyId, assetId); + registerDataPlane(providerCredentials.clientId(), providerAccessToken); + + // perform data transfer + MONITOR.info("Starting data transfer"); + var catalog = fetchCatalog(consumerCredentials); + + MONITOR.info("Catalog received, starting data transfer"); + var offerId = catalog.datasets().stream().filter(dataSet -> dataSet.id().equals(assetId)).findFirst().get().offers().get(0).id(); + assertThat(offerId).isNotNull(); + + + // attempt download as a normal consumer - should fail due to missing credentials + given() + .baseUri(CONTROLPLANE_BASE_URL) + .auth().oauth2(getAccessToken(consumerCredentials.clientId(), consumerCredentials.clientSecret(), "management-api:write").accessToken()) + .body(""" + { + "providerId":"%s", + "policyId": "%s", + "policyType": "manufacturer" + } + """.formatted(providerContextId, offerId)) + .contentType("application/json") + .post("/api/mgmt/v1alpha/participants/%s/data".formatted(consumerCredentials.clientId())) + .then() + .statusCode(500); + + // download the asset as manufacturer - should work because the manufacturer has the necessary credentials + given() + .baseUri(CONTROLPLANE_BASE_URL) + .auth().oauth2(getAccessToken(manufacturerCredentials.clientId(), manufacturerCredentials.clientSecret(), "management-api:write").accessToken()) + .body(""" + { + "providerId":"%s", + "policyId": "%s", + "policyType": "manufacturer" + } + """.formatted(providerContextId, offerId)) + .contentType("application/json") + .post("/api/mgmt/v1alpha/participants/%s/data".formatted(manufacturerCredentials.clientId())) + .then() + .statusCode(200); + + } + private CatalogResponse fetchCatalog(ClientCredentials consumerCredentials) { var accessToken = getAccessToken(consumerCredentials.clientId(), consumerCredentials.clientSecret(), "management-api:read"); @@ -252,7 +316,7 @@ private CatalogResponse fetchCatalog(ClientCredentials consumerCredentials) { * @param participantContextId Participant context for which the data plane should be registered. * @param accessToken OAuth2 token */ - private void registerDataplane(String participantContextId, String accessToken) { + private void registerDataPlane(String participantContextId, String accessToken) { given() .baseUri(CONTROLPLANE_BASE_URL) .contentType(APPLICATION_JSON) @@ -270,8 +334,8 @@ private void registerDataplane(String participantContextId, String accessToken) .statusCode(204); } - private String createAsset(String participantContextId, String accessToken) { - var template = loadResourceFile("asset.json"); + private String createAsset(String participantContextId, String accessToken, String resourceName) { + var template = loadResourceFile(resourceName); return given() .baseUri(CONTROLPLANE_BASE_URL) .auth().oauth2(accessToken) @@ -284,20 +348,11 @@ private String createAsset(String participantContextId, String accessToken) { } private String createCertAsset(String participantContextId, String accessToken) { - var template = loadResourceFile("asset-cert.json"); - return given() - .baseUri(CONTROLPLANE_BASE_URL) - .auth().oauth2(accessToken) - .contentType("application/json") - .body(template) - .post("/api/mgmt/v4alpha/participants/%s/assets".formatted(participantContextId)) - .then() - .statusCode(200) - .extract().jsonPath().getString(ID); + return createAsset(participantContextId, accessToken, "asset-cert.json"); } - private String createPolicyDef(String participantContextId, String accessToken) { - var template = loadResourceFile("policy-def.json"); + private String createPolicyDef(String participantContextId, String accessToken, String resourceName) { + var template = loadResourceFile(resourceName); return given() .baseUri(CONTROLPLANE_BASE_URL) .auth().oauth2(accessToken) @@ -309,10 +364,11 @@ private String createPolicyDef(String participantContextId, String accessToken) .extract().jsonPath().getString(ID); } - private String createContractDef(String participantContextId, String accessToken, String policyDefId, String assetId) { + private String createContractDef(String participantContextId, String accessToken, String accessPolicyId, String contractPolicyId, String assetId) { var template = loadResourceFile("contract-def.json"); - template = template.replace("{{policy_def_id}}", policyDefId); + template = template.replace("{{access_policy_id}}", accessPolicyId); + template = template.replace("{{contract_policy_id}}", contractPolicyId); template = template.replace("{{asset_id}}", assetId); return given() diff --git a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/ParticipantOnboarding.java b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/ParticipantOnboarding.java index ce03d23..42f42a3 100644 --- a/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/ParticipantOnboarding.java +++ b/tests/end2end/src/test/java/org/eclipse/edc/jad/tests/ParticipantOnboarding.java @@ -18,7 +18,9 @@ import org.eclipse.edc.jad.tests.model.ParticipantProfile; import org.eclipse.edc.spi.monitor.Monitor; +import java.util.Arrays; import java.util.Base64; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicReference; @@ -43,13 +45,14 @@ public record ParticipantOnboarding(String participantName, String participantCo String vaultToken, Monitor monitor) { @SuppressWarnings("unchecked") - public ClientCredentials execute(String cellId) { + public ClientCredentials execute(String cellId, String... roles) { monitor.info("Creating tenant for %s".formatted(participantName)); var tenantId = createTenant(participantName); - monitor.info("Deploy dataspace profile"); + monitor.info("Get dataspace profile ID"); + var dataspaceId = getDataspaceProfileId(); monitor.info("Deploy participant profile"); - var profileId = deployParticipantProfile(tenantId, cellId, participantContextDid); + var profileId = deployParticipantProfile(tenantId, cellId, participantContextDid, dataspaceId, roles); monitor.info("Waiting for dataspace profile to become active"); await().atMost(20, SECONDS) @@ -83,6 +86,17 @@ public ClientCredentials execute(String cellId) { return new ClientCredentials(participantContextId, secret); } + private String getDataspaceProfileId() { + return given() + .baseUri(Constants.TM_BASE_URL) + .contentType(Constants.APPLICATION_JSON) + .get("/api/v1alpha1/dataspace-profiles") + .then() + .log().ifValidationFails() + .statusCode(200) + .extract().body().jsonPath().getString("[0].id"); + } + //we could the full HashicorpVault for this, but a REST request is simpler here private String getVaultSecret(String participantContextId) { return given() @@ -148,19 +162,24 @@ private String queryOrchestrationByProfileId(String participantProfileId) { * @param tenantId the tenant ID. * @param cellId the cell ID. * @param participantContextDid the Web:DID of the participant. + * @param dataspaceId the ID of the dataspaces * @return the participant profile ID. */ - private String deployParticipantProfile(String tenantId, String cellId, String participantContextDid) { + private String deployParticipantProfile(String tenantId, String cellId, String participantContextDid, Object dataspaceId, String... roles) { + + var body = new HashMap<>(Map.of("identifier", participantContextDid, + "properties", Map.of(), + "cellId", cellId)); + + if (roles.length > 0) { + var rolesString = Arrays.asList(roles); + body.put("participantRoles", Map.of(dataspaceId, rolesString)); + } + return given() .baseUri(Constants.TM_BASE_URL) .contentType(Constants.APPLICATION_JSON) - .body(""" - { - "identifier": "%s", - "properties": {}, - "cellId": "%s" - } - """.formatted(participantContextDid, cellId)) + .body(body) .post("/api/v1alpha1/tenants/%s/participant-profiles".formatted(tenantId)) .then() .log().ifValidationFails() diff --git a/tests/end2end/src/test/resources/asset-restricted.json b/tests/end2end/src/test/resources/asset-restricted.json new file mode 100644 index 0000000..6b47c9c --- /dev/null +++ b/tests/end2end/src/test/resources/asset-restricted.json @@ -0,0 +1,16 @@ +{ + "@context": [ + "https://w3id.org/edc/connector/management/v2" + ], + "@type": "Asset", + "properties": { + "description": "This asset requires the Manufacturer credential to access" + }, + "dataAddress": { + "@type": "DataAddress", + "type": "HttpData", + "baseUrl": "https://jsonplaceholder.typicode.com/todos", + "proxyPath": "true", + "proxyQueryParams": "true" + } +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/contract-def.json b/tests/end2end/src/test/resources/contract-def.json index 89ef757..f552b79 100644 --- a/tests/end2end/src/test/resources/contract-def.json +++ b/tests/end2end/src/test/resources/contract-def.json @@ -3,8 +3,8 @@ "https://w3id.org/edc/connector/management/v2" ], "@type": "ContractDefinition", - "accessPolicyId": "{{policy_def_id}}", - "contractPolicyId": "{{policy_def_id}}", + "accessPolicyId": "{{access_policy_id}}", + "contractPolicyId": "{{contract_policy_id}}", "assetsSelector": [ { "@type": "Criterion", diff --git a/tests/end2end/src/test/resources/contract-request.json b/tests/end2end/src/test/resources/contract-request.json new file mode 100644 index 0000000..b511af5 --- /dev/null +++ b/tests/end2end/src/test/resources/contract-request.json @@ -0,0 +1,26 @@ +{ + "@context": [ + "https://w3id.org/edc/connector/management/v0.0.1" + ], + "@type": "ContractRequest", + "counterPartyAddress": "{{PROVIDER_DSP_URL}}/api/dsp", + "counterPartyId": "{{PROVIDER_ID}}", + "protocol": "dataspace-protocol-http", + "policy": { + "@type": "Offer", + "@id": "{{POLICY_ID_ASSET_1}}", + "assigner": "{{PROVIDER_ID}}", + "permission": [], + "prohibition": [], + "obligation": { + "action": "use", + "constraint": { + "leftOperand": "DataAccess.level", + "operator": "eq", + "rightOperand": "processing" + } + }, + "target": "asset-1" + }, + "callbackAddresses": [] +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/manufacturer_cel_expression.json b/tests/end2end/src/test/resources/manufacturer_cel_expression.json new file mode 100644 index 0000000..2518c06 --- /dev/null +++ b/tests/end2end/src/test/resources/manufacturer_cel_expression.json @@ -0,0 +1,15 @@ +{ + "@context": [ + "https://w3id.org/edc/connector/management/v2", + "https://w3id.org/edc/virtual-connector/management/v2" + ], + "@type": "CelExpression", + "leftOperand": "ManufacturerCredential", + "description": "Expression for evaluating manufacturer credentials", + "scopes": [ + "catalog", + "contract.negotiation", + "transfer.process" + ], + "expression": "ctx.agent.claims.vc.filter(c, c.type.exists(t, t == 'ManufacturerCredential')).exists(c, c.credentialSubject.exists(cs, timestamp(cs.since) < now))" +} \ No newline at end of file diff --git a/tests/end2end/src/test/resources/create_cel_expression.json b/tests/end2end/src/test/resources/membership_cel_expression.json similarity index 100% rename from tests/end2end/src/test/resources/create_cel_expression.json rename to tests/end2end/src/test/resources/membership_cel_expression.json diff --git a/tests/end2end/src/test/resources/policy-def-manufacturer.json b/tests/end2end/src/test/resources/policy-def-manufacturer.json new file mode 100644 index 0000000..fd68935 --- /dev/null +++ b/tests/end2end/src/test/resources/policy-def-manufacturer.json @@ -0,0 +1,21 @@ +{ + "@context": [ + "https://w3id.org/edc/connector/management/v2" + ], + "@type": "PolicyDefinition", + "policy": { + "@type": "Set", + "permission": [ + { + "action": "use", + "constraint": [ + { + "leftOperand": "ManufacturerCredential", + "operator": "eq", + "rightOperand": "active" + } + ] + } + ] + } +} \ No newline at end of file