From ed060727508d039132f4b457c4c41f86368f00a0 Mon Sep 17 00:00:00 2001 From: Christophe Loiseau Date: Mon, 7 Oct 2024 13:38:06 +0200 Subject: [PATCH] chore: cherry-pick elements for a hotfix release (#1057) * fix: Trivy TOOMANYREQUESTS GitHub API response by authenticating (#1053) * Trivy hotfix * fix: Abide to the DataOfferCreationRequest.PolicyEnum rule (#1055) * Enable CI on hotfix branches * select hotfix branch explicitly * change to target branch --------- Co-authored-by: Sebastian Opriel <22075788+SebastianOpriel@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- .github/workflows/license_scan.yml | 14 +- .github/workflows/secret_scan.yml | 5 +- .github/workflows/security_scan.yml | 10 +- .github/workflows/trivy.yml | 7 +- CHANGELOG.md | 10 +- docs/api/sovity-edc-api-wrapper.yaml | 2 +- .../ui/model/DataOfferCreationRequest.java | 2 +- .../ui/model/PolicyDefinitionChoiceEnum.java | 7 + extensions/wrapper/wrapper/build.gradle.kts | 1 + .../edc/ext/wrapper/WrapperExtension.java | 4 + .../WrapperExtensionContextBuilder.java | 9 +- .../data_offer/DataOfferPageApiService.java | 102 +++++++-- .../de/sovity/edc/e2e/UiApiWrapperTest.java | 193 +++++++++++++++++- 14 files changed, 324 insertions(+), 44 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 06a600619..f7e660f10 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: release: types: [ published ] pull_request: - branches: [ main ] + branches: [ main, hotfix/v10.4.1.x ] env: REGISTRY_URL: ghcr.io diff --git a/.github/workflows/license_scan.yml b/.github/workflows/license_scan.yml index fff50cc76..729fa50d6 100644 --- a/.github/workflows/license_scan.yml +++ b/.github/workflows/license_scan.yml @@ -10,32 +10,38 @@ jobs: timeout-minutes: 30 steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run license scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.24.0 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 with: scan-type: "rootfs" scan-ref: "." scanners: "license" severity: "CRITICAL,HIGH" exit-code: 1 + github-pat: ${{ secrets.GITHUB_TOKEN }} license_scan2: name: License scan (repo) runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: npm install (typescript-client) run: cd extensions/wrapper/clients/typescript-client && npm clean-install - name: npm install (typescript-client-example) run: cd extensions/wrapper/clients/typescript-client-example && npm clean-install - name: Run license scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.24.0 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 with: scan-type: "repo" scan-ref: "." scanners: "license" severity: "CRITICAL,HIGH" exit-code: 1 + github-pat: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/secret_scan.yml b/.github/workflows/secret_scan.yml index 613fc5682..0f1d746fb 100644 --- a/.github/workflows/secret_scan.yml +++ b/.github/workflows/secret_scan.yml @@ -17,9 +17,12 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Run vulnerability scanner - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.24.0 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 with: scan-type: "fs" exit-code: "1" ignore-unfixed: true scanners: secret + github-pat: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/security_scan.yml b/.github/workflows/security_scan.yml index 6a5076180..7442741ad 100644 --- a/.github/workflows/security_scan.yml +++ b/.github/workflows/security_scan.yml @@ -12,7 +12,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Run static analysis (rootfs) - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.24.0 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 with: scan-type: "rootfs" scanners: "vuln,misconfig" @@ -20,6 +22,7 @@ jobs: format: "sarif" output: "trivy-results-rootfs.sarif" severity: "CRITICAL,HIGH" + github-pat: ${{ secrets.GITHUB_TOKEN }} security_scan_repo: name: security_scan_repo runs-on: ubuntu-latest @@ -27,7 +30,9 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - name: Run static analysis (repo) - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.24.0 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 with: scan-type: "repo" scanners: "vuln,misconfig" @@ -35,6 +40,7 @@ jobs: format: "sarif" output: "trivy-results-repo.sarif" severity: "CRITICAL,HIGH" + github-pat: ${{ secrets.GITHUB_TOKEN }} - name: Upload Trivy scan results to GitHub Security tab (repo) uses: github/codeql-action/upload-sarif@v2 continue-on-error: true diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 1a415400c..4c75748ff 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -12,10 +12,12 @@ jobs: timeout-minutes: 30 steps: - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Run static analysis - uses: aquasecurity/trivy-action@master + uses: aquasecurity/trivy-action@0.24.0 + env: + TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2 with: scan-type: 'fs' security-checks: 'vuln,secret,config' @@ -23,6 +25,7 @@ jobs: format: 'sarif' output: 'trivy-results.sarif' severity: 'CRITICAL' + github-pat: ${{ secrets.GITHUB_TOKEN }} - name: Upload Trivy scan results to GitHub Security tab diff --git a/CHANGELOG.md b/CHANGELOG.md index f0ad876ea..f7514fa9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md). #### Patch Changes +- Fix issues with the Create Data Offer Endpoint ([PR#1055](https://github.com/sovity/edc-ce/pull/1055)) + ### Deployment Migration Notes _No special deployment migration steps required_ @@ -26,8 +28,6 @@ _No special deployment migration steps required_ - Dev EDC: `ghcr.io/sovity/edc-dev:{{ VERSION }}` - sovity EDC CE: `ghcr.io/sovity/edc-ce:{{ VERSION }}` - MDS EDC CE: `ghcr.io/sovity/edc-ce-mds:{{ VERSION }}` - - Dev Catalog Crawler: `ghcr.io/sovity/catalog-crawler-dev:{{ VERSION }}` - - Catalog Crawler CE: `ghcr.io/sovity/catalog-crawler-ce:{{ VERSION }}` - Connector UI Docker Image: `ghcr.io/sovity/edc-ui:{{ UI VERSION }}` @@ -226,7 +226,7 @@ MDS 2.2 release #### Major Changes - Complex policies using AND, OR and XONE: - - Complex policy support in the Connector UI. + - Complex policy support in the Connector UI. - The `UiPolicy` model has been adjusted to support complex expressions including `AND`, `OR` and `XONE`. - The `createPolicyDefinition` has been marked as deprecated in favor of the new `createPolicyDefinitionV2` endpoint that supports complex policies. - Removed the recently rushed `createPolicyDefinitionUseCase` endpoint in favor of the new `createPolicyDefinitionV2` endpoint. @@ -271,7 +271,7 @@ MDS 2.2 intermediate release - API Wrapper UI API: Data sources are now well-typed. - The Broker has been removed in favor of the Authority Portal: - - A new Deployment Unit, the ["Data Catalog Crawler"](extensions/catalog-crawler/README.md), has been added. + - A new Deployment Unit, the ["Data Catalog Crawler"](https://github.com/sovity/edc-ce/tree/v9.0.0/extensions/catalog-crawler/README.md), has been added. - Each "Data Catalog Crawler" connects to an existing Authority Portal Deployment's DB. - Each "Data Catalog Crawler" is responsible for crawling exactly one environment. - The Data Catalog functionality of the Broker has been integrated into the Authority Portal. @@ -294,7 +294,7 @@ MDS 2.2 intermediate release - The database migration system has been moved from multiple migration history tables to a single one. - Broker: - The broker has been removed. For Authority Portal users, please check out the new - [Data Catalog Crawler Productive Deployment Guide](docs/deployment-guide/goals/catalog-crawler-production/README.md). + [Data Catalog Crawler Productive Deployment Guide](https://github.com/sovity/edc-ce/tree/v9.0.0/docs/deployment-guide/goals/catalog-crawler-production/README.md). - Any previous broker deployment's database is not required anymore. - Please care that only some environment variables look similar. It is recommended to create fresh deployments. diff --git a/docs/api/sovity-edc-api-wrapper.yaml b/docs/api/sovity-edc-api-wrapper.yaml index 8cda83f2b..a6400de63 100644 --- a/docs/api/sovity-edc-api-wrapper.yaml +++ b/docs/api/sovity-edc-api-wrapper.yaml @@ -948,7 +948,7 @@ components: $ref: '#/components/schemas/UiAssetCreateRequest' policy: type: string - description: Which policy to apply to this asset. + description: Which policy to apply to this asset creation. enum: - DONT_PUBLISH - PUBLISH_UNRESTRICTED diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/DataOfferCreationRequest.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/DataOfferCreationRequest.java index 705aff710..74aa91d59 100644 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/DataOfferCreationRequest.java +++ b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/DataOfferCreationRequest.java @@ -35,7 +35,7 @@ public class DataOfferCreationRequest { @Schema(description = "The asset to create", requiredMode = REQUIRED) private UiAssetCreateRequest uiAssetCreateRequest; - @Schema(description = "Which policy to apply to this asset.", requiredMode = REQUIRED) + @Schema(description = "Which policy to apply to this asset creation.", requiredMode = REQUIRED) private PolicyDefinitionChoiceEnum policy; @Schema(description = "Policy Expression.", requiredMode = NOT_REQUIRED) diff --git a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/PolicyDefinitionChoiceEnum.java b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/PolicyDefinitionChoiceEnum.java index 3f494f240..a8ed13c7a 100644 --- a/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/PolicyDefinitionChoiceEnum.java +++ b/extensions/wrapper/wrapper-api/src/main/java/de/sovity/edc/ext/wrapper/api/ui/model/PolicyDefinitionChoiceEnum.java @@ -14,8 +14,15 @@ package de.sovity.edc.ext.wrapper.api.ui.model; +import io.swagger.v3.oas.annotations.media.Schema; + +import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED; + public enum PolicyDefinitionChoiceEnum { + @Schema(description = "Only create the asset", requiredMode = REQUIRED) DONT_PUBLISH, + @Schema(description = "Create the asset and assigns the always-true policy in the contract definition", requiredMode = REQUIRED) PUBLISH_UNRESTRICTED, + @Schema(description = "Create the asset, a policy and a contract definition", requiredMode = REQUIRED) PUBLISH_RESTRICTED } diff --git a/extensions/wrapper/wrapper/build.gradle.kts b/extensions/wrapper/wrapper/build.gradle.kts index 027f3c567..5461d9981 100644 --- a/extensions/wrapper/wrapper/build.gradle.kts +++ b/extensions/wrapper/wrapper/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(project(":extensions:contract-termination")) implementation(project(":extensions:database-direct-access")) + implementation(project(":extensions:policy-always-true")) implementation(project(":extensions:sovity-messenger")) implementation(project(":utils:jooq-database-access")) diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java index 3c0f81079..68e097e99 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtension.java @@ -34,6 +34,7 @@ import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; import org.eclipse.edc.protocol.dsp.api.configuration.DspApiConfiguration; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.runtime.metamodel.annotation.Setting; @@ -78,6 +79,8 @@ public class WrapperExtension implements ServiceExtension { @Inject private PolicyEngine policyEngine; @Inject + private RuleBindingRegistry ruleBindingRegistry; + @Inject private TransferProcessService transferProcessService; @Inject private TransferProcessStore transferProcessStore; @@ -122,6 +125,7 @@ public void initialize(ServiceExtensionContext context) { policyDefinitionService, policyDefinitionStore, policyEngine, + ruleBindingRegistry, transferProcessService, transferProcessStore, typeTransformerRegistry diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java index 2e8a40fbc..d3ebbc0e6 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/WrapperExtensionContextBuilder.java @@ -79,6 +79,7 @@ import de.sovity.edc.ext.wrapper.controller.PlaceholderEndpointController; import de.sovity.edc.extension.contacttermination.ContractAgreementTerminationService; import de.sovity.edc.extension.db.directaccess.DslContextFactory; +import de.sovity.edc.extension.policy.services.AlwaysTruePolicyDefinitionService; import de.sovity.edc.utils.catalog.DspCatalogService; import de.sovity.edc.utils.catalog.mapper.DspDataOfferBuilder; import lombok.NoArgsConstructor; @@ -96,6 +97,7 @@ import org.eclipse.edc.connector.transfer.spi.store.TransferProcessStore; import org.eclipse.edc.jsonld.spi.JsonLd; import org.eclipse.edc.policy.engine.spi.PolicyEngine; +import org.eclipse.edc.policy.engine.spi.RuleBindingRegistry; import org.eclipse.edc.runtime.metamodel.annotation.Inject; import org.eclipse.edc.spi.asset.AssetIndex; import org.eclipse.edc.spi.monitor.Monitor; @@ -135,6 +137,7 @@ public static WrapperExtensionContext buildContext( PolicyDefinitionService policyDefinitionService, PolicyDefinitionStore policyDefinitionStore, PolicyEngine policyEngine, + RuleBindingRegistry ruleBindingRegistry, TransferProcessService transferProcessService, TransferProcessStore transferProcessStore, TypeTransformerRegistry typeTransformerRegistry @@ -250,10 +253,14 @@ public static WrapperExtensionContext buildContext( miwConfigBuilder, selfDescriptionService ); + var alwaysTruePolicyService = new AlwaysTruePolicyDefinitionService( + policyDefinitionService + ); var dataOfferPageApiService = new DataOfferPageApiService( assetApiService, contractDefinitionApiService, - policyDefinitionApiService + policyDefinitionApiService, + alwaysTruePolicyService ); var uiResource = new UiResourceImpl( contractAgreementApiService, diff --git a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/data_offer/DataOfferPageApiService.java b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/data_offer/DataOfferPageApiService.java index 4bdfd949a..81fd36c2e 100644 --- a/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/data_offer/DataOfferPageApiService.java +++ b/extensions/wrapper/wrapper/src/main/java/de/sovity/edc/ext/wrapper/api/ui/pages/data_offer/DataOfferPageApiService.java @@ -13,6 +13,8 @@ import de.sovity.edc.ext.wrapper.api.ui.pages.asset.AssetApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.contract_definitions.ContractDefinitionApiService; import de.sovity.edc.ext.wrapper.api.ui.pages.policy.PolicyDefinitionApiService; +import de.sovity.edc.extension.policy.AlwaysTruePolicyConstants; +import de.sovity.edc.extension.policy.services.AlwaysTruePolicyDefinitionService; import lombok.RequiredArgsConstructor; import lombok.val; import org.eclipse.edc.spi.types.domain.asset.Asset; @@ -32,6 +34,7 @@ public class DataOfferPageApiService { private final AssetApiService assetApiService; private final ContractDefinitionApiService contractDefinitionApiService; private final PolicyDefinitionApiService policyDefinitionApiService; + private final AlwaysTruePolicyDefinitionService alwaysTruePolicyDefinitionService; @NotNull public IdAvailabilityResponse checkIfPolicyIdAvailable(DSLContext dsl, String id) { @@ -66,45 +69,106 @@ private boolean isIdAvailable(DSLContext dsl, Table table, TableField createButDontPublish(dsl, dataOfferCreationRequest, commonId); + case PUBLISH_UNRESTRICTED -> createAndPublishUnrestricted(dsl, dataOfferCreationRequest, commonId); + case PUBLISH_RESTRICTED -> createAndPublishRestricted(dsl, dataOfferCreationRequest, commonId); + }; + } - val policyIdExists = checkIfPolicyIdAvailable(dsl, commonId).isAvailable(); - if (!policyIdExists) { - throw new InvalidRequestException("Policy with id %s already exists".formatted(commonId)); + private @NotNull IdResponseDto createAndPublishUnrestricted( + DSLContext dsl, + DataOfferCreationRequest dataOfferCreationRequest, + String commonId + ) { + val assetId = commonId; + val contractDefinitionId = commonId; + val policyId = AlwaysTruePolicyConstants.POLICY_DEFINITION_ID; + + checkAssetIdAvailable(dsl, assetId); + checkContractDefinitionIdAvailable(dsl, contractDefinitionId); + + if (!alwaysTruePolicyDefinitionService.exists()) { + // the default always-true policy has been deleted, recreate it. + alwaysTruePolicyDefinitionService.create(); } + assetApiService.createAsset(dataOfferCreationRequest.getUiAssetCreateRequest()); + + return createContractDefinition(assetId, policyId, contractDefinitionId); + } + + private @NotNull IdResponseDto createAndPublishRestricted( + DSLContext dsl, + DataOfferCreationRequest dataOfferCreationRequest, + String commonId + ) { + val assetId = commonId; + val policyId = commonId; + val contractDefinitionId = commonId; + + checkAssetIdAvailable(dsl, assetId); + checkPolicyIdAvailable(dsl, policyId); + checkContractDefinitionIdAvailable(dsl, contractDefinitionId); + + assetApiService.createAsset(dataOfferCreationRequest.getUiAssetCreateRequest()); + + val policyExpression = Optional.ofNullable(dataOfferCreationRequest.getUiPolicyExpression()) + .orElseThrow(() -> new InvalidRequestException("Missing policy expression")); + policyDefinitionApiService.createPolicyDefinitionV2(new PolicyDefinitionCreateDto(policyId, policyExpression)); + + createContractDefinition(assetId, policyId, contractDefinitionId); + + return new IdResponseDto(commonId, OffsetDateTime.now()); + } + + private @NotNull IdResponseDto createButDontPublish( + DSLContext dsl, + DataOfferCreationRequest dataOfferCreationRequest, + String commonId + ) { + checkAssetIdAvailable(dsl, commonId); + return assetApiService.createAsset(dataOfferCreationRequest.getUiAssetCreateRequest()); + } + + private void checkContractDefinitionIdAvailable(DSLContext dsl, String commonId) { val contractDefinitionIdExists = checkIfContractDefinitionIdAvailable(dsl, commonId).isAvailable(); if (!contractDefinitionIdExists) { throw new InvalidRequestException("Contract definition with id %s already exists".formatted(commonId)); } + } - assetApiService.createAsset(dataOfferCreationRequest.getUiAssetCreateRequest()); - - val maybeNewPolicy = Optional.ofNullable(dataOfferCreationRequest.getUiPolicyExpression()); + private void checkPolicyIdAvailable(DSLContext dsl, String commonId) { + val policyIdExists = checkIfPolicyIdAvailable(dsl, commonId).isAvailable(); + if (!policyIdExists) { + throw new InvalidRequestException("Policy with id %s already exists".formatted(commonId)); + } + } - maybeNewPolicy.ifPresent( - policy -> policyDefinitionApiService.createPolicyDefinitionV2(new PolicyDefinitionCreateDto(commonId, policy))); + private void checkAssetIdAvailable(DSLContext dsl, String commonId) { + val assetIdExists = checkIfAssetIdAvailable(dsl, commonId).isAvailable(); + if (!assetIdExists) { + throw new InvalidRequestException("Asset with id %s already exists".formatted(commonId)); + } + } + private @NotNull IdResponseDto createContractDefinition(String assetId, String policyId, String contractDefinitionId) { val cd = new ContractDefinitionRequest(); cd.setAssetSelector(List.of(UiCriterion.builder() .operandLeft(Asset.PROPERTY_ID) .operator(UiCriterionOperator.EQ) .operandRight(UiCriterionLiteral.builder() .type(UiCriterionLiteralType.VALUE) - .value(commonId) + .value(assetId) .build()) .build())); - cd.setAccessPolicyId(commonId); - cd.setContractPolicyId(commonId); - cd.setContractDefinitionId(commonId); + cd.setAccessPolicyId(policyId); + cd.setContractPolicyId(policyId); + cd.setContractDefinitionId(contractDefinitionId); - contractDefinitionApiService.createContractDefinition(cd); - - return new IdResponseDto(commonId, OffsetDateTime.now()); + return contractDefinitionApiService.createContractDefinition(cd); } } diff --git a/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java b/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java index 0b9875b62..6000045b7 100644 --- a/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java +++ b/tests/src/test/java/de/sovity/edc/e2e/UiApiWrapperTest.java @@ -582,8 +582,6 @@ void checkIdAvailability(E2eScenario scenario, @Provider EdcClient providerClien scenario.createPolicy(policyId, OffsetDateTime.MIN, OffsetDateTime.MAX); var contractDefinitionId = scenario.createContractDefinition(policyId, assetId); - val asset = providerClient.uiApi().getAssetPage(); - // act val negAssetResponse = providerClient.uiApi().isAssetIdAvailable(assetId); val negPolicyResponse = providerClient.uiApi().isPolicyIdAvailable(policyId); @@ -687,7 +685,7 @@ void canMakeAnOnDemandDataSourceAvailable( } @Test - void canCreateDataOfferWithoutAnyNewPolicy( + void canCreateDataOfferWithoutAnyNewPolicyNotContractDefinition( @Provider EdcClient providerClient ) { // arrange @@ -726,9 +724,7 @@ void canCreateDataOfferWithoutAnyNewPolicy( assertThat(getAllPoliciesExceptTheAlwaysTruePolicy(providerClient)).hasSize(0); assertThat(providerClient.uiApi().getContractDefinitionPage().getContractDefinitions()) - .extracting(ContractDefinitionEntry::getContractDefinitionId) - .first() - .isEqualTo(assetId); + .hasSize(0); } @Test @@ -753,7 +749,7 @@ void canCreateDataOfferWithNewPolicy( val dataOfferCreateRequest = new DataOfferCreationRequest( asset, - DataOfferCreationRequest.PolicyEnum.DONT_PUBLISH, + DataOfferCreationRequest.PolicyEnum.PUBLISH_RESTRICTED, UiPolicyExpression.builder() .constraint(UiPolicyConstraint.builder() .left("foo") @@ -788,6 +784,55 @@ void canCreateDataOfferWithNewPolicy( .isEqualTo(assetId); } + @Test + void canCreateDataOfferWithNewEmptyPolicyAndRestrictedPublishing( + @Provider EdcClient providerClient + ) { + // arrange + val dataSource = UiDataSource.builder() + .httpData(UiDataSourceHttpData.builder() + .baseUrl("http://example.com") + .method(UiDataSourceHttpDataMethod.GET) + .build()) + .type(DataSourceType.HTTP_DATA) + .build(); + + val assetId = "asset"; + val asset = UiAssetCreateRequest.builder() + .dataSource(dataSource) + .id(assetId) + .title("My asset") + .build(); + + val dataOfferCreateRequest = new DataOfferCreationRequest( + asset, + DataOfferCreationRequest.PolicyEnum.PUBLISH_RESTRICTED, + UiPolicyExpression.builder().build() + ); + + // act + val returnedId = providerClient.uiApi().createDataOffer(dataOfferCreateRequest).getId(); + + // assert + assertThat(returnedId).isEqualTo(assetId); + + assertThat(providerClient.uiApi().getAssetPage().getAssets()) + .extracting(UiAsset::getAssetId) + .first() + .isEqualTo(assetId); + + assertThat(getAllPoliciesExceptTheAlwaysTruePolicy(providerClient)) + .hasSize(1) + .extracting(PolicyDefinitionDto::getPolicyDefinitionId) + .first() + .isEqualTo(assetId); + + assertThat(providerClient.uiApi().getContractDefinitionPage().getContractDefinitions()) + .extracting(ContractDefinitionEntry::getContractDefinitionId) + .first() + .isEqualTo(assetId); + } + @Test void dontCreateAnythingIfTheAssetAlreadyExists( E2eScenario scenario, @@ -919,6 +964,140 @@ void dontCreateAnythingIfTheContractDefinitionAlreadyExists( .isEqualTo(assetId); } + @Test + void reuseTheAlwaysTruePolicyWhenPublishingUnrestricted( + @Provider EdcClient providerClient + ) { + // arrange + val assetId = "assetId"; + + // act + providerClient.uiApi() + .createDataOffer(DataOfferCreationRequest.builder() + .uiAssetCreateRequest(UiAssetCreateRequest.builder() + .id(assetId) + .dataSource(UiDataSource.builder() + .type(DataSourceType.ON_REQUEST) + .onRequest(UiDataSourceOnRequest.builder() + .contactEmail("foo@example.com") + .contactPreferredEmailSubject("Subject") + .build()) + .build()) + .build()) + .policy(DataOfferCreationRequest.PolicyEnum.PUBLISH_UNRESTRICTED) + .build()); + + // assert + assertThat(providerClient.uiApi().getAssetPage().getAssets()) + // the asset used for the placeholder contract definition + .hasSize(1) + .extracting(UiAsset::getAssetId) + .first() + .isEqualTo(assetId); + + assertThat(getAllPoliciesExceptTheAlwaysTruePolicy(providerClient)).hasSize(0); + + assertThat(providerClient.uiApi().getContractDefinitionPage().getContractDefinitions()) + .hasSize(1) + .filteredOn(it -> it.getContractDefinitionId().equals(assetId)) + .extracting(ContractDefinitionEntry::getContractDefinitionId) + .first() + // the already existing one, before the data offer creation attempt + .isEqualTo(assetId); + } + + @Test + void onlyCreateTheAssetWhenDontPublish( + @Provider EdcClient providerClient + ) { + // arrange + val assetId = "assetId"; + + // act + providerClient.uiApi() + .createDataOffer(DataOfferCreationRequest.builder() + .uiAssetCreateRequest(UiAssetCreateRequest.builder() + .id(assetId) + .dataSource(UiDataSource.builder() + .type(DataSourceType.ON_REQUEST) + .onRequest(UiDataSourceOnRequest.builder() + .contactEmail("foo@example.com") + .contactPreferredEmailSubject("Subject") + .build()) + .build()) + .build()) + .policy(DataOfferCreationRequest.PolicyEnum.DONT_PUBLISH) + .build()); + + // assert + assertThat(providerClient.uiApi().getAssetPage().getAssets()) + // the asset used for the placeholder contract definition + .hasSize(1) + .extracting(UiAsset::getAssetId) + .first() + .isEqualTo(assetId); + + assertThat(getAllPoliciesExceptTheAlwaysTruePolicy(providerClient)).hasSize(0); + + assertThat(providerClient.uiApi().getContractDefinitionPage().getContractDefinitions()) + .hasSize(0); + } + + @Test + void recreateTheAlwaysTruePolicyIfDeleted( + @Provider EdcClient providerClient + ) { + // arrange + val assetId = "assetId"; + providerClient.uiApi().deletePolicyDefinition(AlwaysTruePolicyConstants.POLICY_DEFINITION_ID); + + List withoutDefaultAlwaysTrue = + providerClient.uiApi() + .getPolicyDefinitionPage() + .getPolicies() + .stream() + .filter(it -> !it.getPolicyDefinitionId().equals(AlwaysTruePolicyConstants.POLICY_DEFINITION_ID)) + .toList(); + + assertThat(withoutDefaultAlwaysTrue).hasSize(0); + + // act + providerClient.uiApi() + .createDataOffer(DataOfferCreationRequest.builder() + .uiAssetCreateRequest(UiAssetCreateRequest.builder() + .id(assetId) + .dataSource(UiDataSource.builder() + .type(DataSourceType.ON_REQUEST) + .onRequest(UiDataSourceOnRequest.builder() + .contactEmail("foo@example.com") + .contactPreferredEmailSubject("Subject") + .build()) + .build()) + .build()) + .policy(DataOfferCreationRequest.PolicyEnum.PUBLISH_UNRESTRICTED) + .build()); + + // assert + assertThat(providerClient.uiApi().getAssetPage().getAssets()) + // the asset used for the placeholder contract definition + .hasSize(1) + .extracting(UiAsset::getAssetId) + .first() + .isEqualTo(assetId); + + List policies = + providerClient.uiApi() + .getPolicyDefinitionPage() + .getPolicies() + .stream() + .toList(); + + assertThat(policies).hasSize(1); + + assertThat(providerClient.uiApi().getContractDefinitionPage().getContractDefinitions()) + .hasSize(1); + } + private static @NotNull List getAllPoliciesExceptTheAlwaysTruePolicy(EdcClient edcClient) { return edcClient.uiApi().getPolicyDefinitionPage().getPolicies().stream().filter(it -> !it.getPolicyDefinitionId().equals( AlwaysTruePolicyConstants.POLICY_DEFINITION_ID)).toList();