diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..097f9f98 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..7086d553 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ + + + + + +#### List of Changes + + + +#### Motivation and Context + + + +#### How Has This Been Tested? + + + + + +#### Screenshots (if appropriate): + +#### Types of changes + + + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as + expected) + +#### Checklist: + + + + +- [ ] My change requires a change to the documentation. +- [ ] I have updated the documentation accordingly. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..7f283b1c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" \ No newline at end of file diff --git a/.github/workflows/pr_scan.yml b/.github/workflows/pr_scan.yml new file mode 100644 index 00000000..2d4a51b2 --- /dev/null +++ b/.github/workflows/pr_scan.yml @@ -0,0 +1,101 @@ +name: Check Build and Anchore on PR + +on: + pull_request: + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +env: + DOCKERFILE: Dockerfile.test-only + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + PROJECT_KEY: 'pagopa_eng-lollipop-consumer-java-sdk' + +jobs: + pr_scan: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout project sources + uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + with: + fetch-depth: 0 + - name: Setup Gradle + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~/.sonar-project.properties/cache + key: ${{ runner.os }}-sonar-project.properties + restore-keys: ${{ runner.os }}-sonar-project.properties + - name: Make gradlew executable + run: chmod +x ./gradlew + - name: Run build with Gradle Wrapper + run: ./gradlew build testCodeCoverageReport + - name: Add coverage to PR + id: jacoco + uses: madrapps/jacoco-report@7a334255fbce42f385d7567c25d986a9c62e2971 + with: + paths: ${{ github.workspace }}/test-coverage/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml + token: ${{ secrets.GITHUB_TOKEN }} + min-coverage-overall: 40 + min-coverage-changed-files: 60 + - name: Build the Docker image + run: docker build . --file ${{ env.DOCKERFILE }} --tag localbuild/testimage:latest + - name: Run the Anchore Grype scan action + uses: anchore/scan-action@d5aa5b6cb9414b0c7771438046ff5bcfa2854ed7 + id: scan + with: + image: "localbuild/testimage:latest" + fail-build: true + severity-cutoff: "high" + - name: Upload Anchore Scan Report + uses: github/codeql-action/upload-sarif@9885f86fab4879632b7e44514f19148225dfbdcd + if: always() + with: + sarif_file: ${{ steps.scan.outputs.sarif }} + - name: Run Sonar Scanner on Pull Request + if: ${{ github.event_name == 'pull_request' }} + run: ./gradlew sonar --info + -Dsonar.organization=pagopa + -Dsonar.projectKey=${{ env.PROJECT_KEY }} + -Dsonar.coverage.jacoco.xmlReportPaths=**/test-coverage/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml + -Dsonar.coverage.exclusions="**/config/*","**/*Mock*","**/model/**","**/entity/*","**/*Stub*","**/*Config*,**/*Exception*" + -Dsonar.cpd.exclusions="**/model/**,**/entity/**,**/simple/internal/**" + -Dsonar.host.url=https://sonarcloud.io + -Dsonar.java.libraries="**/*.jar" + -Dsonar.login=${{ env.SONAR_TOKEN }} + -Dsonar.pullrequest.key=${{ github.event.pull_request.number }} + -Dsonar.pullrequest.branch=${{ github.head_ref }} + -Dsonar.pullrequest.base=${{ github.base_ref }} + - name: Run Sonar Scanner + if: ${{ github.event_name != 'pull_request' }} + run: ./gradlew sonar --info + -Dsonar.organization=pagopa + -Dsonar.projectKey=${{ env.PROJECT_KEY }} + -Dsonar.coverage.jacoco.xmlReportPaths=**/test-coverage/build/reports/jacoco/testCodeCoverageReport/testCodeCoverageReport.xml + -Dsonar.coverage.exclusions="**/config/*","**/*Mock*","**/model/**","**/entity/*","**/*Stub*","**/*Config*,**/*Exception*" + -Dsonar.cpd.exclusions="**/model/**,**/entity/**,**/simple/internal/**" + -Dsonar.host.url=https://sonarcloud.io + -Dsonar.java.libraries="**/*.jar" + -Dsonar.login=${{ env.SONAR_TOKEN }} + -Dsonar.branch.name=${{ github.head_ref }} + - name: Publish to Maven Local + run: ./gradlew publishToMavenLocal + - name: Build Spring Sample + working-directory: ./samples/spring + run: chmod +x ./gradlew && ./gradlew bootJar + - name: Run Docker compose with .env.dev + run: docker compose --env-file e2e/.env.dev up -d --build --wait + - name: Sleep for 30 seconds + run: sleep 30s + shell: bash + - name: Install node modules and execute e2e tests + working-directory: ./e2e + run: npm install && npm run execute-test + - name: Shutdown docker compose + run: docker compose down diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..94887c65 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,75 @@ +name: Publish package to GitHub Packages +on: + release: + types: [created] +env: + DOCKERFILE: Dockerfile.test-only + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_ACTOR: ${{ secrets.GITHUB_ACTOR }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} +jobs: + publish: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 + id: cache + with: + key: OpenJDK11U-jdk_x64_linux_hotspot_11.0.18_10.tar.gz + path: | + - ${{ runner.temp }}/jdkfile.tar.gz + - ${{ runner.temp }}/jdkfile.sha256 + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 + # jdkfile version hash was locally computed and checked against https://github.com/paketo-buildpacks/adoptium/releases + - if: steps.cache.outputs.cache-hit != 'true' + run: | + echo "4a29efda1d702b8ff38e554cf932051f40ec70006caed5c4857a8cbc7a0b7db7 ${{ runner.temp }}/jdkfile.tar.gz" >> ${{ runner.temp }}/jdkfile.sha256 + echo {{ runner.temp }}/jdkfile.sha256 + curl -L "https://github.com/adoptium/temurin11-binaries/releases/download/jdk-11.0.18%2B10/OpenJDK11U-jdk_x64_linux_hotspot_11.0.18_10.tar.gz" -o "${{ runner.temp }}/jdkfile.tar.gz" + sha256sum --check --status "${{ runner.temp }}/jdkfile.sha256" + - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 + with: + distribution: "jdkfile" + jdkFile: "${{ runner.temp }}/jdkfile.tar.gz" + java-version: "11" + - name: Validate Gradle wrapper + uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b + - name: Make gradlew executable + run: chmod +x ./gradlew + - name: Publish package + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Make spring sample gradlew executable + run: chmod +x ./gradlew + working-directory: ./samples/spring + - name: Run build with Gradle Wrapper on Spring Sample + run: ./gradlew bootJar + working-directory: ./samples/spring + - name: Log in to the Container registry + uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + - name: Build and push Docker image + uses: docker/build-push-action@f2a1d5e99d037542a71f64918e516c093c6f3fc4 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + secrets: | + "GITHUB_ACTOR=${{ secrets.GITHUB_ACTOR }}" + "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..396fd4ac --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Ignore Gradle project-specific cache directory +.gradle + +# Ignore IntelliJ +.idea + +# Ignore Gradle build output directory +build + +# Ignore newman node modules and test reports +e2e/newman +e2e/node_modules \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..d35b1c2c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,24 @@ +# 1. `pip install pre-commit` +# 2. `pre-commit install` +repos: + - repo: https://github.com/jguttman94/pre-commit-gradle + rev: v0.2.1 + hooks: + - id: gradle-task + name: update-verification-metadata-sha256 + args: [ '-w', '--write-verification-metadata sha256 help'] + - id: gradle-spotless + args: [ '-w'] + - id: gradle-check + args: [ '-w' ] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - repo: https://github.com/gitleaks/gitleaks + rev: v8.16.1 + hooks: + - id: gitleaks \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..e514c2f9 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,3 @@ +# see https://help.github.com/en/articles/about-code-owners#example-of-a-codeowners-file + +* @pagopa/pagopa-tech diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..0ec93a18 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM eclipse-temurin:11-jdk-alpine as build + +WORKDIR /build +COPY ./samples/spring . + +FROM eclipse-temurin:11-jdk-alpine as runtime + +WORKDIR /app +COPY --from=build /build/build/libs/*.jar /app/app.jar +COPY --from=build /build/build/resources/main/application.properties /app/application.properties + +RUN apk --update --no-cache add curl + +RUN addgroup -S appuser && adduser -S appuser -G appuser +USER appuser + +EXPOSE 8080 +ENTRYPOINT [ "java","-jar","/app/app.jar", "/app/application.properties" ] \ No newline at end of file diff --git a/Dockerfile.test-only b/Dockerfile.test-only new file mode 100644 index 00000000..8da464d0 --- /dev/null +++ b/Dockerfile.test-only @@ -0,0 +1,10 @@ +FROM amazoncorretto:11 + +RUN yum update -y --security +RUN mkdir /app + +COPY core/build/libs/*.jar /app/ +COPY http-verifier/build/libs/*.jar /app/ +COPY redis-storage/build/libs/*.jar /app/ +COPY identity-service-rest-client-native/build/libs/*.jar /app/ +COPY assertion-rest-client-native/build/libs/*.jar /app/ \ No newline at end of file diff --git a/README.md b/README.md index bad273a2..ce2e5ef3 100644 Binary files a/README.md and b/README.md differ diff --git a/assertion-rest-client-native/README.md b/assertion-rest-client-native/README.md new file mode 100644 index 00000000..8a11118f --- /dev/null +++ b/assertion-rest-client-native/README.md @@ -0,0 +1,25 @@ +# Assertion rest client +This module is used to obtain the user's SAML assertion. + +The parameters needed to get the assertion are the jwt and the assertion's ref, +both retrieved from the http request's headers. + +At this moment only SAML assertion are supported, OIDC claims are not. + +## Configuration +The client uri, endpoints and the entity id of the CIE identity provider are configurable and are configured by default as follows: + +| VARIABLE | DEFAULT VALUE | USAGE | +|--------------------------|-----------------------|---------------------------------------------------| +| baseUri | http://localhost:3000 | base uri of the api for retrieving the assertions | +| assertionRequestEndpoint | /assertions | endpoint of the request | + +## Example + +In order to create a new instance of the client using the provider and an instance of the configuration class: + +``` +AssertionClientConfig config = AssertionSimpleClientConfig.builder().build(); +AssertionClientProvider assertionClientProvider = + new AssertionSimpleClientProvider(config); +``` diff --git a/assertion-rest-client-native/build.gradle b/assertion-rest-client-native/build.gradle new file mode 100644 index 00000000..7bb20a06 --- /dev/null +++ b/assertion-rest-client-native/build.gradle @@ -0,0 +1,85 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'java-library' + id("io.freefair.lombok") version "8.0.0" +} + +group 'it.pagopa.tech' + +repositories { + mavenLocal() + mavenCentral() +} + +configurations { + implementation { + attributes { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-core-jar')) + } + } +} + +abstract class InstrumentedJarsRule implements AttributeCompatibilityRule { + + @Override + void execute(CompatibilityCheckDetails details) { + if (details.consumerValue.name == 'instrumented-core-jar' && details.producerValue.name == 'jar') { + details.compatible() + } + } +} + +dependencies { + attributesSchema { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) { + compatibilityRules.add(InstrumentedJarsRule) + } + } + implementation project(path: ':core') + + implementation 'javax.annotation:javax.annotation-api:1.3.2' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2' + implementation 'org.openapitools:jackson-databind-nullable:0.2.6' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2' + implementation 'com.google.code.findbugs:jsr305:3.0.2' + + implementation 'javax.inject:javax.inject:1' + testImplementation 'org.mock-server:mockserver-client-java:5.15.0' + testImplementation 'junit:junit:4.13.2' + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.assertj:assertj-core:3.24.2' + + testImplementation 'org.mockito:mockito-core:5.2.0' + testImplementation 'org.mockito:mockito-junit-jupiter:5.2.0' + //Mockserver for testing api + testImplementation 'org.mock-server:mockserver-netty:5.15.0' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} +/* +compileJava.dependsOn tasks.openApiGenerate + +openApiGenerate { + generatorName = "java" + inputSpec = "$projectDir/openapi/openapi-spec.yml" + outputDir = "$projectDir/generated" + configOptions = [ + dataLibrary: "java8", + library: "native", + useRuntimeException: "true", + sourceFolder: "build/generated/sources/" + ] +}*/ diff --git a/assertion-rest-client-native/openapi/openapi-spec.yml b/assertion-rest-client-native/openapi/openapi-spec.yml new file mode 100644 index 00000000..c8baeba4 --- /dev/null +++ b/assertion-rest-client-native/openapi/openapi-spec.yml @@ -0,0 +1,166 @@ +openapi: "3.0.1" +info: + title: Assertion Client + version: $npm_package_version + x-logo: + url: https://io.italia.it/assets/img/io-logo-blue.svg + description: |- + Client used to get the assertion from the identity provider +servers: + - url: http://localhost:3000 +security: + - ApiKeyAuth: [] +paths: + /assertions/{assertion_ref}: + get: + operationId: getAssertion + summary: Get Assertion related to a given assertion ref + parameters: + - name: assertion_ref + required: true + in: path + schema: + $ref: '#/components/schemas/AssertionRef' + - name: x-pagopa-lollipop-auth + required: true + in: header + schema: + $ref: '#/components/schemas/LollipopAuthBearer' + responses: + '200': + description: The assertion related to a valid assertion_ref + content: + application/json: + schema: + $ref: '#/components/schemas/LCUserInfo' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '410': + description: Assertion gone + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: Ocp-Apim-Subscription-Key + schemas: + ProblemJson: + type: object + properties: + type: + type: string + format: uri + description: |- + An absolute URI that identifies the problem type. When dereferenced, + it SHOULD provide human-readable documentation for the problem type + (e.g., using HTML). + default: about:blank + example: https://example.com/problem/constraint-violation + title: + type: string + description: >- + A short, summary of the problem type. Written in english and + readable + + for engineers (usually not suited for non technical stakeholders and + + not localized); example: Service Unavailable + status: + type: integer + format: int32 + description: >- + The HTTP status code generated by the origin server for this + occurrence + + of the problem. + minimum: 100 + maximum: 600 + exclusiveMaximum: true + example: 200 + detail: + type: string + description: |- + A human readable explanation specific to this occurrence of the + problem. + example: There was an error processing the request + instance: + type: string + format: uri + description: >- + An absolute URI that identifies the specific occurrence of the + problem. + + It may or may not yield further information if dereferenced. + AssertionType: + type: string + enum: + - SAML + - OIDC + AssertionRefSha256: + type: string + pattern: ^(sha256-[A-Za-z0-9-_=]{1,44})$ + AssertionRefSha384: + type: string + pattern: ^(sha384-[A-Za-z0-9-_=]{1,66})$ + AssertionRefSha512: + type: string + pattern: ^(sha512-[A-Za-z0-9-_=]{1,88})$ + AssertionRef: + oneOf: + - $ref: '#/components/schemas/AssertionRefSha256' + - $ref: '#/components/schemas/AssertionRefSha384' + - $ref: '#/components/schemas/AssertionRefSha512' + LollipopAuthBearer: + type: string + pattern: ^Bearer [a-zA-Z0-9-_].+ + description: A lollipop's JWT auth custom header as `Bearer ` + example: >- + Bearer + eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c + SamlUserInfo: + type: object + properties: + response_xml: + type: string + description: A string representation of a signed SPID/CIE response + minLength: 1 + required: + - response_xml + OidcSignedJwt: + type: string + description: A JWT representation of a signed SPID/CIE OIDC Idp + minLength: 1 + OidcUserInfo: + type: object + properties: + id_token: + $ref: '#/components/schemas/OidcSignedJwt' + claims_token: + $ref: '#/components/schemas/OidcSignedJwt' + required: + - id_token + - claims_token + LCUserInfo: + oneOf: + - $ref: '#/components/schemas/SamlUserInfo' + - $ref: '#/components/schemas/OidcUserInfo' \ No newline at end of file diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClient.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClient.java new file mode 100644 index 00000000..46cbfe29 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClient.java @@ -0,0 +1,65 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple; + +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClient; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.ApiClient; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.ApiException; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.api.DefaultApi; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model.AssertionRef; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model.LCUserInfo; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model.OidcUserInfo; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model.SamlUserInfo; +import it.pagopa.tech.lollipop.consumer.exception.LollipopAssertionNotFoundException; +import it.pagopa.tech.lollipop.consumer.exception.OidcAssertionNotSupported; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import javax.inject.Inject; + +/** Implementation of the @AssertionClient using generated openAPI REST Client */ +public class AssertionSimpleClient implements AssertionClient { + + private final DefaultApi defaultApi; + + @Inject + public AssertionSimpleClient(ApiClient client) { + this.defaultApi = new DefaultApi(client); + } + + /** + * Retrieve assertion from IdentityProvider using REST Client The retrieved assertion can be of + * two types: SAML or OIDC Only SAML assertions are supported at this moment + * + * @param jwt Auth token for header param x-pagopa-lollipop-auth + * @param assertionRef Assertion unique identification + * @return the retrieved assertion or null if the assertion is not supported (not SAML) + * @throws LollipopAssertionNotFoundException if some error occurred in the request + * @throws OidcAssertionNotSupported if the assertion retrieved is a OIDC token + */ + @Override + public SamlAssertion getAssertion(String jwt, String assertionRef) + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + AssertionRef ref = new AssertionRef(assertionRef); + + LCUserInfo responseAssertion; + + try { + responseAssertion = this.defaultApi.getAssertion(ref, jwt); + } catch (ApiException e) { + throw new LollipopAssertionNotFoundException( + "Error retrieving assertion: " + e.getMessage(), e); + } + + if (responseAssertion.getActualInstance().getClass().equals(SamlUserInfo.class)) { + SamlAssertion response = new SamlAssertion(); + SamlUserInfo data = (SamlUserInfo) responseAssertion.getActualInstance(); + String assertionData = data.getResponseXml(); + response.setAssertionRef(assertionRef); + response.setAssertionData(assertionData); + return response; + } + if (responseAssertion.getActualInstance().getClass().equals(OidcUserInfo.class)) { + throw new OidcAssertionNotSupported("OIDC Claims not supported yet."); + } + + return null; + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientConfig.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientConfig.java new file mode 100644 index 00000000..a0b40182 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientConfig.java @@ -0,0 +1,18 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class AssertionSimpleClientConfig { + + @Builder.Default private String baseUri = "http://localhost:3000"; + + @Builder.Default private String assertionRequestEndpoint = "/assertions"; +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientProvider.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientProvider.java new file mode 100644 index 00000000..b43e27b5 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientProvider.java @@ -0,0 +1,28 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple; + +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClient; +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.ApiClient; +import javax.inject.Inject; + +/** Provider class for retrieving an instance of {@link AssertionSimpleClient} */ +public class AssertionSimpleClientProvider implements AssertionClientProvider { + + private final AssertionSimpleClientConfig assertionClientConfig; + + @Inject + public AssertionSimpleClientProvider(AssertionSimpleClientConfig config) { + this.assertionClientConfig = config; + } + + /** + * Provide an instance of {@link AssertionSimpleClient} + * + * @return {@link AssertionSimpleClient} + */ + @Override + public AssertionClient provideClient() { + return new AssertionSimpleClient(new ApiClient(assertionClientConfig)); + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiClient.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiClient.java new file mode 100644 index 00000000..64d1e2f1 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiClient.java @@ -0,0 +1,170 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientConfig; +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.function.Consumer; +import lombok.Getter; +import org.openapitools.jackson.nullable.JsonNullableModule; + +/** + * Configuration and utility class for API clients. + * + *

This class can be constructed and modified, then used to instantiate the various API classes. + * The API classes use the settings in this class to configure themselves, but otherwise do not + * store a link to this class. + * + *

This class is mutable and not synchronized, so it is not thread-safe. The API classes + * generated from this are immutable and thread-safe. + * + *

The setter methods of this class return the current object to facilitate a fluent style of + * configuration. + */ +@Getter +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public class ApiClient { + + private HttpClient.Builder builder; + private ObjectMapper mapper; + private String scheme; + private String host; + private int port; + private String basePath; + private Consumer interceptor; + private Consumer> responseInterceptor; + private Consumer> asyncResponseInterceptor; + private Duration readTimeout; + private Duration connectTimeout; + private String assertionRequestEndpoint; + + /** + * URL encode a string in the UTF-8 encoding. + * + * @param s String to encode. + * @return URL-encoded representation of the input string. + */ + public static String urlEncode(String s) { + return URLEncoder.encode(s, UTF_8).replaceAll("\\+", "%20"); + } + + /** Create an instance of ApiClient. */ + public ApiClient(AssertionSimpleClientConfig config) { + this.builder = createDefaultHttpClientBuilder(); + this.mapper = createDefaultObjectMapper(); + updateBaseUri(config.getBaseUri()); + interceptor = null; + readTimeout = null; + connectTimeout = null; + responseInterceptor = null; + asyncResponseInterceptor = null; + this.assertionRequestEndpoint = config.getAssertionRequestEndpoint(); + } + + protected ObjectMapper createDefaultObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + mapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + mapper.registerModule(new JavaTimeModule()); + mapper.registerModule(new JsonNullableModule()); + return mapper; + } + + protected HttpClient.Builder createDefaultHttpClientBuilder() { + return HttpClient.newBuilder(); + } + + public void updateBaseUri(String baseUri) { + URI uri = URI.create(baseUri); + scheme = uri.getScheme(); + host = uri.getHost(); + port = uri.getPort(); + basePath = uri.getRawPath(); + } + + /** + * Get an {@link HttpClient} based on the current {@link HttpClient.Builder}. + * + *

The returned object is immutable and thread-safe. + * + * @return The HTTP client. + */ + public HttpClient getHttpClient() { + return builder.build(); + } + + /** + * Get a copy of the current {@link ObjectMapper}. + * + * @return A copy of the current object mapper. + */ + public ObjectMapper getObjectMapper() { + return mapper.copy(); + } + + /** + * Get the base URI to resolve the endpoint paths against. + * + * @return The complete base URI that the rest of the API parameters are resolved against. + */ + public String getBaseUri() { + return scheme + "://" + host + (port == -1 ? "" : ":" + port) + basePath; + } + + /** + * Get the custom interceptor. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer getRequestInterceptor() { + return interceptor; + } + + /** + * Get the custom response interceptor. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer> getResponseInterceptor() { + return responseInterceptor; + } + + /** + * Get the custom async response interceptor. Use this interceptor when asyncNative is set to + * 'true'. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer> getAsyncResponseInterceptor() { + return asyncResponseInterceptor; + } + + /** + * Get the read timeout that was set. + * + * @return The read timeout, or null if no timeout was set. Null represents an infinite wait + * time. + */ + public Duration getReadTimeout() { + return readTimeout; + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiException.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiException.java new file mode 100644 index 00000000..e2e55152 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiException.java @@ -0,0 +1,88 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal; + +import java.net.http.HttpHeaders; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public class ApiException extends RuntimeException { + private int code = 0; + private HttpHeaders responseHeaders = null; + private String responseBody = null; + + public ApiException() {} + + public ApiException(Throwable throwable) { + super(throwable); + } + + public ApiException(String message) { + super(message); + } + + public ApiException( + String message, + Throwable throwable, + int code, + HttpHeaders responseHeaders, + String responseBody) { + super(message, throwable); + this.code = code; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + public ApiException( + String message, int code, HttpHeaders responseHeaders, String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException( + String message, Throwable throwable, int code, HttpHeaders responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + public ApiException(int code, HttpHeaders responseHeaders, String responseBody) { + this((String) null, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public ApiException( + int code, String message, HttpHeaders responseHeaders, String responseBody) { + this(code, message); + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return Headers as an HttpHeaders object + */ + public HttpHeaders getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiResponse.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiResponse.java new file mode 100644 index 00000000..dbe9cb01 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/ApiResponse.java @@ -0,0 +1,47 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal; + +import java.util.List; +import java.util.Map; + +/** + * API response returned by API call. + * + * @param The type of data that is deserialized from response body + */ +public class ApiResponse { + private final int statusCode; + private final Map> headers; + private final T data; + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + */ + public ApiResponse(int statusCode, Map> headers) { + this(statusCode, headers, null); + } + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + * @param data The object deserialized from response bod + */ + public ApiResponse(int statusCode, Map> headers, T data) { + this.statusCode = statusCode; + this.headers = headers; + this.data = data; + } + + public int getStatusCode() { + return statusCode; + } + + public Map> getHeaders() { + return headers; + } + + public T getData() { + return data; + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/JSON.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/JSON.java new file mode 100644 index 00000000..4eee60a5 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/JSON.java @@ -0,0 +1,178 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.*; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import org.openapitools.jackson.nullable.JsonNullableModule; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public class JSON { + private ObjectMapper mapper; + + public JSON() { + mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(MapperFeature.ALLOW_COERCION_OF_SCALARS, false); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); + mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, true); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + mapper.setDateFormat(new RFC3339DateFormat()); + mapper.registerModule(new JavaTimeModule()); + JsonNullableModule jnm = new JsonNullableModule(); + mapper.registerModule(jnm); + } + + /** Helper class to register the discriminator mappings. */ + private static class ClassDiscriminatorMapping { + // The model class name. + Class modelClass; + // The name of the discriminator property. + String discriminatorName; + // The discriminator mappings for a model class. + Map> discriminatorMappings; + + // Constructs a new class discriminator. + ClassDiscriminatorMapping( + Class cls, String propertyName, Map> mappings) { + modelClass = cls; + discriminatorName = propertyName; + discriminatorMappings = new HashMap>(); + if (mappings != null) { + discriminatorMappings.putAll(mappings); + } + } + + // Return the name of the discriminator property for this model class. + String getDiscriminatorPropertyName() { + return discriminatorName; + } + + // Return the discriminator value or null if the discriminator is not + // present in the payload. + String getDiscriminatorValue(JsonNode node) { + // Determine the value of the discriminator property in the input data. + if (discriminatorName != null) { + // Get the value of the discriminator property, if present in the input payload. + node = node.get(discriminatorName); + if (node != null && node.isValueNode()) { + String discrValue = node.asText(); + if (discrValue != null) { + return discrValue; + } + } + } + return null; + } + + /** + * Returns the target model class that should be used to deserialize the input data. This + * function can be invoked for anyOf/oneOf composed models with discriminator mappings. The + * discriminator mappings are used to determine the target model class. + * + * @param node The input data. + * @param visitedClasses The set of classes that have already been visited. + * @return the target model class. + */ + Class getClassForElement(JsonNode node, Set> visitedClasses) { + if (visitedClasses.contains(modelClass)) { + // Class has already been visited. + return null; + } + // Determine the value of the discriminator property in the input data. + String discrValue = getDiscriminatorValue(node); + if (discrValue == null) { + return null; + } + Class cls = discriminatorMappings.get(discrValue); + // It may not be sufficient to return this cls directly because that target class + // may itself be a composed schema, possibly with its own discriminator. + visitedClasses.add(modelClass); + for (Class childClass : discriminatorMappings.values()) { + ClassDiscriminatorMapping childCdm = modelDiscriminators.get(childClass); + if (childCdm == null) { + continue; + } + if (!discriminatorName.equals(childCdm.discriminatorName)) { + discrValue = getDiscriminatorValue(node); + if (discrValue == null) { + continue; + } + } + if (childCdm != null) { + // Recursively traverse the discriminator mappings. + Class childDiscr = childCdm.getClassForElement(node, visitedClasses); + if (childDiscr != null) { + return childDiscr; + } + } + } + return cls; + } + } + + /** + * Returns true if inst is an instance of modelClass in the OpenAPI model hierarchy. + * + *

The Java class hierarchy is not implemented the same way as the OpenAPI model hierarchy, + * so it's not possible to use the instanceof keyword. + * + * @param modelClass A OpenAPI model class. + * @param inst The instance object. + * @param visitedClasses The set of classes that have already been visited. + * @return true if inst is an instance of modelClass in the OpenAPI model hierarchy. + */ + public static boolean isInstanceOf( + Class modelClass, Object inst, Set> visitedClasses) { + if (modelClass.isInstance(inst)) { + // This handles the 'allOf' use case with single parent inheritance. + return true; + } + if (visitedClasses.contains(modelClass)) { + // This is to prevent infinite recursion when the composed schemas have + // a circular dependency. + return false; + } + visitedClasses.add(modelClass); + + // Traverse the oneOf/anyOf composed schemas. + Map> descendants = modelDescendants.get(modelClass); + if (descendants != null) { + for (Class childType : descendants.values()) { + if (isInstanceOf(childType, inst, visitedClasses)) { + return true; + } + } + } + return false; + } + + /** A map of discriminators for all model classes. */ + private static Map, ClassDiscriminatorMapping> modelDiscriminators = new HashMap<>(); + + /** A map of oneOf/anyOf descendants for each model class. */ + private static Map, Map>> modelDescendants = new HashMap<>(); + + /** + * Register the oneOf/anyOf descendants of the modelClass. + * + * @param modelClass the model class + * @param descendants a map of oneOf/anyOf descendants. + */ + public static void registerDescendants(Class modelClass, Map> descendants) { + modelDescendants.put(modelClass, descendants); + } + + private static JSON json; + + static { + json = new JSON(); + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/RFC3339DateFormat.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/RFC3339DateFormat.java new file mode 100644 index 00000000..cd978bd9 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/RFC3339DateFormat.java @@ -0,0 +1,41 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal; + +import com.fasterxml.jackson.databind.util.StdDateFormat; +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.FieldPosition; +import java.text.ParsePosition; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.TimeZone; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode +public class RFC3339DateFormat extends DateFormat { + private static final long serialVersionUID = 1L; + private static final TimeZone TIMEZONE_Z = TimeZone.getTimeZone("UTC"); + + private final StdDateFormat fmt = + new StdDateFormat().withTimeZone(TIMEZONE_Z).withColonInTimeZone(true); + + public RFC3339DateFormat() { + this.calendar = new GregorianCalendar(); + this.numberFormat = new DecimalFormat(); + } + + @Override + public Date parse(String source) { + return parse(source, new ParsePosition(0)); + } + + @Override + public Date parse(String source, ParsePosition pos) { + return fmt.parse(source, pos); + } + + @Override + public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) { + return fmt.format(date, toAppendTo, fieldPosition); + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/api/DefaultApi.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/api/DefaultApi.java new file mode 100644 index 00000000..e56c1cf6 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/api/DefaultApi.java @@ -0,0 +1,158 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.api; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.ApiClient; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.ApiException; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.ApiResponse; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model.AssertionRef; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model.LCUserInfo; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.function.Consumer; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public class DefaultApi { + private final HttpClient memberVarHttpClient; + private final ObjectMapper memberVarObjectMapper; + private final String memberVarBaseUri; + private final Consumer memberVarInterceptor; + private final Duration memberVarReadTimeout; + private final Consumer> memberVarResponseInterceptor; + private final Consumer> memberVarAsyncResponseInterceptor; + private final String memberAssertionRequestEndpoint; + + public DefaultApi(ApiClient apiClient) { + memberVarHttpClient = apiClient.getHttpClient(); + memberVarObjectMapper = apiClient.getObjectMapper(); + memberVarBaseUri = apiClient.getBaseUri(); + memberVarInterceptor = apiClient.getRequestInterceptor(); + memberVarReadTimeout = apiClient.getReadTimeout(); + memberVarResponseInterceptor = apiClient.getResponseInterceptor(); + memberVarAsyncResponseInterceptor = apiClient.getAsyncResponseInterceptor(); + memberAssertionRequestEndpoint = apiClient.getAssertionRequestEndpoint(); + } + + protected ApiException getApiException(String operationId, HttpResponse response) + throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } + + private String formatExceptionMessage(String operationId, int statusCode, String body) { + if (body == null || body.isEmpty()) { + body = "[no body]"; + } + return operationId + " call failed with: " + statusCode + " - " + body; + } + + /** + * Get Assertion related to a given assertion ref + * + * @param assertionRef (required) + * @param xPagopaLollipopAuth (required) + * @return LCUserInfo + * @throws ApiException if fails to make API call + */ + public LCUserInfo getAssertion(AssertionRef assertionRef, String xPagopaLollipopAuth) + throws ApiException { + ApiResponse localVarResponse = + getAssertionWithHttpInfo(assertionRef, xPagopaLollipopAuth); + return localVarResponse.getData(); + } + + /** + * Get Assertion related to a given assertion ref + * + * @param assertionRef (required) + * @param xPagopaLollipopAuth (required) + * @return ApiResponse<LCUserInfo> + * @throws ApiException if fails to make API call + */ + public ApiResponse getAssertionWithHttpInfo( + AssertionRef assertionRef, String xPagopaLollipopAuth) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = + getAssertionRequestBuilder(assertionRef, xPagopaLollipopAuth); + try { + HttpResponse localVarResponse = + memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("getAssertion", localVarResponse); + } + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null + ? null + : memberVarObjectMapper.readValue( + localVarResponse.body(), + new TypeReference< + LCUserInfo>() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder getAssertionRequestBuilder( + AssertionRef assertionRef, String xPagopaLollipopAuth) throws ApiException { + // verify the required parameter 'assertionRef' is set + if (assertionRef == null) { + throw new ApiException( + 400, "Missing the required parameter 'assertionRef' when calling getAssertion"); + } + // verify the required parameter 'xPagopaLollipopAuth' is set + if (xPagopaLollipopAuth == null) { + throw new ApiException( + 400, + "Missing the required parameter 'xPagopaLollipopAuth' when calling" + + " getAssertion"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = + memberAssertionRequestEndpoint + + "/{assertion_ref}" + .replace( + "{assertion_ref}", + ApiClient.urlEncode( + assertionRef.getActualInstance().toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + if (xPagopaLollipopAuth != null) { + localVarRequestBuilder.header("x-pagopa-lollipop-auth", xPagopaLollipopAuth.toString()); + } + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/AbstractOpenApiSchema.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/AbstractOpenApiSchema.java new file mode 100644 index 00000000..f4b070e4 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/AbstractOpenApiSchema.java @@ -0,0 +1,135 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Map; +import java.util.Objects; + +/** Abstract class for oneOf,anyOf schemas defined in OpenAPI spec */ +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public abstract class AbstractOpenApiSchema { + + // store the actual instance of the schema/object + private Object instance; + + // is nullable + private Boolean isNullable; + + // schema type (e.g. oneOf, anyOf) + private final String schemaType; + + public AbstractOpenApiSchema(String schemaType, Boolean isNullable) { + this.schemaType = schemaType; + this.isNullable = isNullable; + } + + /** + * Get the list of oneOf/anyOf composed schemas allowed to be stored in this object + * + * @return an instance of the actual schema/object + */ + public abstract Map> getSchemas(); + + /** + * Get the actual instance + * + * @return an instance of the actual schema/object + */ + @JsonValue + public Object getActualInstance() { + return instance; + } + + /** + * Set the actual instance + * + * @param instance the actual instance of the schema/object + */ + public void setActualInstance(Object instance) { + this.instance = instance; + } + + /** + * Get the instant recursively when the schemas defined in oneOf/anyof happen to be oneOf/anyOf + * schema as well + * + * @return an instance of the actual schema/object + */ + public Object getActualInstanceRecursively() { + return getActualInstanceRecursively(this); + } + + private Object getActualInstanceRecursively(AbstractOpenApiSchema object) { + if (object.getActualInstance() == null) { + return null; + } else if (object.getActualInstance() instanceof AbstractOpenApiSchema) { + return getActualInstanceRecursively((AbstractOpenApiSchema) object.getActualInstance()); + } else { + return object.getActualInstance(); + } + } + + /** + * Get the schema type (e.g. anyOf, oneOf) + * + * @return the schema type + */ + public String getSchemaType() { + return schemaType; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ").append(getClass()).append(" {\n"); + sb.append(" instance: ").append(toIndentedString(instance)).append("\n"); + sb.append(" isNullable: ").append(toIndentedString(isNullable)).append("\n"); + sb.append(" schemaType: ").append(toIndentedString(schemaType)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first + * line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractOpenApiSchema a = (AbstractOpenApiSchema) o; + return Objects.equals(this.instance, a.instance) + && Objects.equals(this.isNullable, a.isNullable) + && Objects.equals(this.schemaType, a.schemaType); + } + + @Override + public int hashCode() { + return Objects.hash(instance, isNullable, schemaType); + } + + /** + * Is nullable + * + * @return true if it's nullable + */ + public Boolean isNullable() { + if (Boolean.TRUE.equals(isNullable)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/AssertionRef.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/AssertionRef.java new file mode 100644 index 00000000..005daa74 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/AssertionRef.java @@ -0,0 +1,267 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.JSON; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.StringJoiner; +import java.util.logging.Level; +import java.util.logging.Logger; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +@JsonDeserialize(using = AssertionRef.AssertionRefDeserializer.class) +@JsonSerialize(using = AssertionRef.AssertionRefSerializer.class) +public class AssertionRef extends AbstractOpenApiSchema { + private static final Logger log = Logger.getLogger(AssertionRef.class.getName()); + + public static class AssertionRefSerializer extends StdSerializer { + public AssertionRefSerializer(Class t) { + super(t); + } + + public AssertionRefSerializer() { + this(null); + } + + @Override + public void serialize(AssertionRef value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static class AssertionRefDeserializer extends StdDeserializer { + public AssertionRefDeserializer() { + this(AssertionRef.class); + } + + public AssertionRefDeserializer(Class vc) { + super(vc); + } + + @Override + public AssertionRef deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + Object deserialized = null; + boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); + int match = 0; + JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + // deserialize String + try { + boolean attemptParsing = true; + // ensure that we respect type coercion as set on the client ObjectMapper + if (String.class.equals(Integer.class) + || String.class.equals(Long.class) + || String.class.equals(Float.class) + || String.class.equals(Double.class) + || String.class.equals(Boolean.class) + || String.class.equals(String.class)) { + attemptParsing = typeCoercion; + if (!attemptParsing) { + attemptParsing |= + ((String.class.equals(Integer.class) + || String.class.equals(Long.class)) + && token == JsonToken.VALUE_NUMBER_INT); + attemptParsing |= + ((String.class.equals(Float.class) + || String.class.equals(Double.class)) + && token == JsonToken.VALUE_NUMBER_FLOAT); + attemptParsing |= + (String.class.equals(Boolean.class) + && (token == JsonToken.VALUE_FALSE + || token == JsonToken.VALUE_TRUE)); + attemptParsing |= + (String.class.equals(String.class) + && token == JsonToken.VALUE_STRING); + } + } + if (attemptParsing) { + deserialized = tree.traverse(jp.getCodec()).readValueAs(String.class); + // TODO: there is no validation against JSON schema constraints + // (min, max, enum, pattern...), this does not perform a strict JSON + // validation, which means the 'match' count may be higher than it should be. + match++; + log.log(Level.FINER, "Input data matches schema 'String'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'String'", e); + } + + if (match == 1) { + AssertionRef ret = new AssertionRef(); + ret.setActualInstance(deserialized); + return ret; + } + throw new IOException( + String.format( + "Failed deserialization for AssertionRef: %d classes match result," + + " expected 1", + match)); + } + + /** Handle deserialization of the 'null' value. */ + @Override + public AssertionRef getNullValue(DeserializationContext ctxt) throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "AssertionRef cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + public AssertionRef() { + super("oneOf", Boolean.FALSE); + } + + public AssertionRef(String o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + static { + schemas.put("String", String.class); + JSON.registerDescendants(AssertionRef.class, Collections.unmodifiableMap(schemas)); + } + + @Override + public Map> getSchemas() { + return AssertionRef.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: String + * + *

It could be an instance of the 'oneOf' schemas. The oneOf child schemas may themselves be + * a composed schema (allOf, anyOf, oneOf). + */ + @Override + public void setActualInstance(Object instance) { + if (JSON.isInstanceOf(String.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + throw new RuntimeException("Invalid instance type. Must be String"); + } + + /** + * Get the actual instance, which can be the following: String + * + * @return The actual instance (String) + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + /** + * Get the actual instance of `String`. If the actual instance is not `String`, the + * ClassCastException will be thrown. + * + * @return The actual instance of `String` + * @throws ClassCastException if the instance is not `String` + */ + public String getString() throws ClassCastException { + return (String) super.getActualInstance(); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + if (getActualInstance() instanceof String) { + if (getActualInstance() != null) { + joiner.add( + String.format( + "%sone_of_0%s=%s", + prefix, + suffix, + URLEncoder.encode( + String.valueOf(getActualInstance()), + StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + return joiner.toString(); + } + if (getActualInstance() instanceof String) { + if (getActualInstance() != null) { + joiner.add( + String.format( + "%sone_of_1%s=%s", + prefix, + suffix, + URLEncoder.encode( + String.valueOf(getActualInstance()), + StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + return joiner.toString(); + } + if (getActualInstance() instanceof String) { + if (getActualInstance() != null) { + joiner.add( + String.format( + "%sone_of_2%s=%s", + prefix, + suffix, + URLEncoder.encode( + String.valueOf(getActualInstance()), + StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + return joiner.toString(); + } + return null; + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/LCUserInfo.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/LCUserInfo.java new file mode 100644 index 00000000..6986313e --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/LCUserInfo.java @@ -0,0 +1,250 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.JSON; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.StringJoiner; +import java.util.logging.Level; +import java.util.logging.Logger; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +@JsonDeserialize(using = LCUserInfo.LCUserInfoDeserializer.class) +@JsonSerialize(using = LCUserInfo.LCUserInfoSerializer.class) +public class LCUserInfo extends AbstractOpenApiSchema { + private static final Logger log = Logger.getLogger(LCUserInfo.class.getName()); + + public static class LCUserInfoSerializer extends StdSerializer { + public LCUserInfoSerializer(Class t) { + super(t); + } + + public LCUserInfoSerializer() { + this(null); + } + + @Override + public void serialize(LCUserInfo value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static class LCUserInfoDeserializer extends StdDeserializer { + public LCUserInfoDeserializer() { + this(LCUserInfo.class); + } + + public LCUserInfoDeserializer(Class vc) { + super(vc); + } + + @Override + public LCUserInfo deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + Object deserialized = null; + boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); + int match = 0; + JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + // deserialize OidcUserInfo + try { + deserialized = tree.traverse(jp.getCodec()).readValueAs(OidcUserInfo.class); + // TODO: there is no validation against JSON schema constraints + // (min, max, enum, pattern...), this does not perform a strict JSON + // validation, which means the 'match' count may be higher than it should be. + if (((OidcUserInfo) deserialized).getIdToken() != null) { + match++; + + log.log(Level.FINER, "Input data matches schema 'OidcUserInfo'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'OidcUserInfo'", e); + } + + // deserialize SamlUserInfo + try { + deserialized = tree.traverse(jp.getCodec()).readValueAs(SamlUserInfo.class); + // TODO: there is no validation against JSON schema constraints + // (min, max, enum, pattern...), this does not perform a strict JSON + // validation, which means the 'match' count may be higher than it should be. + if (((SamlUserInfo) deserialized).getResponseXml() != null) { + match++; + log.log(Level.FINER, "Input data matches schema 'SamlUserInfo'"); + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'SamlUserInfo'", e); + } + + if (match == 1) { + LCUserInfo ret = new LCUserInfo(); + ret.setActualInstance(deserialized); + return ret; + } + throw new IOException( + String.format( + "Failed deserialization for LCUserInfo: %d classes match result," + + " expected 1", + match)); + } + + /** Handle deserialization of the 'null' value. */ + @Override + public LCUserInfo getNullValue(DeserializationContext ctxt) throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "LCUserInfo cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + public LCUserInfo() { + super("oneOf", Boolean.FALSE); + } + + public LCUserInfo(OidcUserInfo o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + public LCUserInfo(SamlUserInfo o) { + super("oneOf", Boolean.FALSE); + setActualInstance(o); + } + + static { + schemas.put("OidcUserInfo", OidcUserInfo.class); + schemas.put("SamlUserInfo", SamlUserInfo.class); + JSON.registerDescendants(LCUserInfo.class, Collections.unmodifiableMap(schemas)); + } + + @Override + public Map> getSchemas() { + return LCUserInfo.schemas; + } + + /** + * Set the instance that matches the oneOf child schema, check the instance parameter is valid + * against the oneOf child schemas: OidcUserInfo, SamlUserInfo + * + *

It could be an instance of the 'oneOf' schemas. The oneOf child schemas may themselves be + * a composed schema (allOf, anyOf, oneOf). + */ + @Override + public void setActualInstance(Object instance) { + if (JSON.isInstanceOf(OidcUserInfo.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + if (JSON.isInstanceOf(SamlUserInfo.class, instance, new HashSet>())) { + super.setActualInstance(instance); + return; + } + + throw new RuntimeException("Invalid instance type. Must be OidcUserInfo, SamlUserInfo"); + } + + /** + * Get the actual instance, which can be the following: OidcUserInfo, SamlUserInfo + * + * @return The actual instance (OidcUserInfo, SamlUserInfo) + */ + @Override + public Object getActualInstance() { + return super.getActualInstance(); + } + + /** + * Get the actual instance of `OidcUserInfo`. If the actual instance is not `OidcUserInfo`, the + * ClassCastException will be thrown. + * + * @return The actual instance of `OidcUserInfo` + * @throws ClassCastException if the instance is not `OidcUserInfo` + */ + public OidcUserInfo getOidcUserInfo() throws ClassCastException { + return (OidcUserInfo) super.getActualInstance(); + } + + /** + * Get the actual instance of `SamlUserInfo`. If the actual instance is not `SamlUserInfo`, the + * ClassCastException will be thrown. + * + * @return The actual instance of `SamlUserInfo` + * @throws ClassCastException if the instance is not `SamlUserInfo` + */ + public SamlUserInfo getSamlUserInfo() throws ClassCastException { + return (SamlUserInfo) super.getActualInstance(); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + if (getActualInstance() instanceof SamlUserInfo) { + if (getActualInstance() != null) { + joiner.add( + ((SamlUserInfo) getActualInstance()) + .toUrlQueryString(prefix + "one_of_0" + suffix)); + } + return joiner.toString(); + } + if (getActualInstance() instanceof OidcUserInfo) { + if (getActualInstance() != null) { + joiner.add( + ((OidcUserInfo) getActualInstance()) + .toUrlQueryString(prefix + "one_of_1" + suffix)); + } + return joiner.toString(); + } + return null; + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/OidcUserInfo.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/OidcUserInfo.java new file mode 100644 index 00000000..872e4eb6 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/OidcUserInfo.java @@ -0,0 +1,170 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.StringJoiner; + +/** OidcUserInfo */ +@JsonPropertyOrder({OidcUserInfo.JSON_PROPERTY_ID_TOKEN, OidcUserInfo.JSON_PROPERTY_CLAIMS_TOKEN}) +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public class OidcUserInfo { + public static final String JSON_PROPERTY_ID_TOKEN = "id_token"; + private String idToken; + + public static final String JSON_PROPERTY_CLAIMS_TOKEN = "claims_token"; + private String claimsToken; + + public OidcUserInfo() {} + + public OidcUserInfo idToken(String idToken) { + this.idToken = idToken; + return this; + } + + /** + * A JWT representation of a signed SPID/CIE OIDC Idp + * + * @return idToken + */ + @javax.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_ID_TOKEN) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getIdToken() { + return idToken; + } + + @JsonProperty(JSON_PROPERTY_ID_TOKEN) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setIdToken(String idToken) { + this.idToken = idToken; + } + + public OidcUserInfo claimsToken(String claimsToken) { + this.claimsToken = claimsToken; + return this; + } + + /** + * A JWT representation of a signed SPID/CIE OIDC Idp + * + * @return claimsToken + */ + @javax.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_CLAIMS_TOKEN) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getClaimsToken() { + return claimsToken; + } + + @JsonProperty(JSON_PROPERTY_CLAIMS_TOKEN) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setClaimsToken(String claimsToken) { + this.claimsToken = claimsToken; + } + + /** Return true if this OidcUserInfo object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + OidcUserInfo oidcUserInfo = (OidcUserInfo) o; + return Objects.equals(this.idToken, oidcUserInfo.idToken) + && Objects.equals(this.claimsToken, oidcUserInfo.claimsToken); + } + + @Override + public int hashCode() { + return Objects.hash(idToken, claimsToken); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class OidcUserInfo {\n"); + sb.append(" idToken: ").append(toIndentedString(idToken)).append("\n"); + sb.append(" claimsToken: ").append(toIndentedString(claimsToken)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first + * line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `id_token` to the URL query string + if (getIdToken() != null) { + joiner.add( + String.format( + "%sid_token%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getIdToken()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `claims_token` to the URL query string + if (getClaimsToken() != null) { + joiner.add( + String.format( + "%sclaims_token%s=%s", + prefix, + suffix, + URLEncoder.encode( + String.valueOf(getClaimsToken()), + StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/ProblemJson.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/ProblemJson.java new file mode 100644 index 00000000..91b8588a --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/ProblemJson.java @@ -0,0 +1,291 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.StringJoiner; + +/** ProblemJson */ +@JsonPropertyOrder({ + ProblemJson.JSON_PROPERTY_TYPE, + ProblemJson.JSON_PROPERTY_TITLE, + ProblemJson.JSON_PROPERTY_STATUS, + ProblemJson.JSON_PROPERTY_DETAIL, + ProblemJson.JSON_PROPERTY_INSTANCE +}) +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public class ProblemJson { + public static final String JSON_PROPERTY_TYPE = "type"; + private URI type = URI.create("about:blank"); + + public static final String JSON_PROPERTY_TITLE = "title"; + private String title; + + public static final String JSON_PROPERTY_STATUS = "status"; + private Integer status; + + public static final String JSON_PROPERTY_DETAIL = "detail"; + private String detail; + + public static final String JSON_PROPERTY_INSTANCE = "instance"; + private URI instance; + + public ProblemJson() {} + + public ProblemJson type(URI type) { + this.type = type; + return this; + } + + /** + * An absolute URI that identifies the problem type. When dereferenced, it SHOULD provide + * human-readable documentation for the problem type (e.g., using HTML). + * + * @return type + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public URI getType() { + return type; + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setType(URI type) { + this.type = type; + } + + public ProblemJson title(String title) { + this.title = title; + return this; + } + + /** + * A short, summary of the problem type. Written in english and readable for engineers (usually + * not suited for non technical stakeholders and not localized); example: Service Unavailable + * + * @return title + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getTitle() { + return title; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setTitle(String title) { + this.title = title; + } + + public ProblemJson status(Integer status) { + this.status = status; + return this; + } + + /** + * The HTTP status code generated by the origin server for this occurrence of the problem. + * minimum: 100 maximum: 600 + * + * @return status + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public Integer getStatus() { + return status; + } + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setStatus(Integer status) { + this.status = status; + } + + public ProblemJson detail(String detail) { + this.detail = detail; + return this; + } + + /** + * A human readable explanation specific to this occurrence of the problem. + * + * @return detail + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_DETAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getDetail() { + return detail; + } + + @JsonProperty(JSON_PROPERTY_DETAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setDetail(String detail) { + this.detail = detail; + } + + public ProblemJson instance(URI instance) { + this.instance = instance; + return this; + } + + /** + * An absolute URI that identifies the specific occurrence of the problem. It may or may not + * yield further information if dereferenced. + * + * @return instance + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_INSTANCE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public URI getInstance() { + return instance; + } + + @JsonProperty(JSON_PROPERTY_INSTANCE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setInstance(URI instance) { + this.instance = instance; + } + + /** Return true if this ProblemJson object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProblemJson problemJson = (ProblemJson) o; + return Objects.equals(this.type, problemJson.type) + && Objects.equals(this.title, problemJson.title) + && Objects.equals(this.status, problemJson.status) + && Objects.equals(this.detail, problemJson.detail) + && Objects.equals(this.instance, problemJson.instance); + } + + @Override + public int hashCode() { + return Objects.hash(type, title, status, detail, instance); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ProblemJson {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" detail: ").append(toIndentedString(detail)).append("\n"); + sb.append(" instance: ").append(toIndentedString(instance)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first + * line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `type` to the URL query string + if (getType() != null) { + joiner.add( + String.format( + "%stype%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getType()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `title` to the URL query string + if (getTitle() != null) { + joiner.add( + String.format( + "%stitle%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getTitle()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `status` to the URL query string + if (getStatus() != null) { + joiner.add( + String.format( + "%sstatus%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getStatus()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `detail` to the URL query string + if (getDetail() != null) { + joiner.add( + String.format( + "%sdetail%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getDetail()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `instance` to the URL query string + if (getInstance() != null) { + joiner.add( + String.format( + "%sinstance%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getInstance()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} diff --git a/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/SamlUserInfo.java b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/SamlUserInfo.java new file mode 100644 index 00000000..a751ce96 --- /dev/null +++ b/assertion-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/internal/model/SamlUserInfo.java @@ -0,0 +1,131 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.StringJoiner; + +/** SamlUserInfo */ +@JsonPropertyOrder({SamlUserInfo.JSON_PROPERTY_RESPONSE_XML}) +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-04T15:48:28.175942900+02:00[Europe/Paris]") +public class SamlUserInfo { + public static final String JSON_PROPERTY_RESPONSE_XML = "response_xml"; + private String responseXml; + + public SamlUserInfo() {} + + public SamlUserInfo responseXml(String responseXml) { + this.responseXml = responseXml; + return this; + } + + /** + * A string representation of a signed SPID/CIE response + * + * @return responseXml + */ + @javax.annotation.Nonnull + @JsonProperty(JSON_PROPERTY_RESPONSE_XML) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public String getResponseXml() { + return responseXml; + } + + @JsonProperty(JSON_PROPERTY_RESPONSE_XML) + @JsonInclude(value = JsonInclude.Include.ALWAYS) + public void setResponseXml(String responseXml) { + this.responseXml = responseXml; + } + + /** Return true if this SamlUserInfo object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + SamlUserInfo samlUserInfo = (SamlUserInfo) o; + return Objects.equals(this.responseXml, samlUserInfo.responseXml); + } + + @Override + public int hashCode() { + return Objects.hash(responseXml); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class SamlUserInfo {\n"); + sb.append(" responseXml: ").append(toIndentedString(responseXml)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first + * line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `response_xml` to the URL query string + if (getResponseXml() != null) { + joiner.add( + String.format( + "%sresponse_xml%s=%s", + prefix, + suffix, + URLEncoder.encode( + String.valueOf(getResponseXml()), + StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} diff --git a/assertion-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientTest.java b/assertion-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientTest.java new file mode 100644 index 00000000..14e8f951 --- /dev/null +++ b/assertion-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientTest.java @@ -0,0 +1,58 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple; + +import static it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientTestUtils.*; +import static org.mockserver.integration.ClientAndServer.startClientAndServer; + +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.internal.ApiClient; +import it.pagopa.tech.lollipop.consumer.exception.LollipopAssertionNotFoundException; +import it.pagopa.tech.lollipop.consumer.exception.OidcAssertionNotSupported; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.mockserver.integration.ClientAndServer; + +class AssertionSimpleClientTest { + + private static AssertionSimpleClient assertionSimpleClient; + private static AssertionSimpleClientConfig assertionConfig; + private static ClientAndServer mockServer; + + @BeforeAll + public static void startServer() { + assertionConfig = Mockito.spy(AssertionSimpleClientConfig.builder().build()); + assertionConfig.setBaseUri("http://localhost:2000"); + ApiClient client = new ApiClient(assertionConfig); + assertionSimpleClient = new AssertionSimpleClient(client); + mockServer = startClientAndServer(2000); + } + + @Test + void samlAssertionFound() throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + AssertionSimpleClientTestUtils.createExpectationAssertionFound(); + SamlAssertion response = assertionSimpleClient.getAssertion(JWT, ASSERTION_REF); + + Assertions.assertNotNull(response); + Assertions.assertNotNull(response.getAssertionRef()); + Assertions.assertNotNull(response.getAssertionData()); + Assertions.assertEquals(ASSERTION_REF, response.getAssertionRef()); + Assertions.assertEquals(XML_STRING, response.getAssertionData()); + } + + @Test + void assertionNotFound() { + AssertionSimpleClientTestUtils.createExpectationAssertionNotFound(); + // setup + Assertions.assertThrows( + LollipopAssertionNotFoundException.class, + () -> assertionSimpleClient.getAssertion(JWT, WRONG_ASSERTION_REF)); + } + + @AfterAll + public static void stopServer() { + mockServer.stop(); + } +} diff --git a/assertion-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientTestUtils.java b/assertion-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientTestUtils.java new file mode 100644 index 00000000..48b2ef3d --- /dev/null +++ b/assertion-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/client/simple/AssertionSimpleClientTestUtils.java @@ -0,0 +1,284 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client.simple; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import org.mockserver.client.MockServerClient; +import org.mockserver.model.Header; + +public class AssertionSimpleClientTestUtils { + + public static final String XML_STRING = + " https://posteid.poste.it" + + " " + + " " + + " " + + " " + + " IYCwE8NJNGLAGdL8zA/W/kuTLdlHMMXMeP2hei8LYqU=" + + " " + + " GI9CwzYfcmTBE9Lf7Hvqr2bgCkfbuq6vZPwZaaCmxq5cicDf7+k6TYussUx147iAdngl4vMixAjA" + + " eABU0cSrZllLW0Gqxm+EPvylwMc4O1tNYlpvjnZzW7PIRns5M22KSzfHBDdGZG7Dq4uDHVbGCENu" + + " TV5UaGNQJ2sNTD95Qaz6pmQtx0guehDc/m5ldFvChqZTKrOcVu+qTRFkW+OabbnkmKXPSWpTN9WE" + + " 4RramWgWkNE/sn4z0Rwmnei8oEhcBKSpOMrenbMgpCYjoRto5lDGGJrMkeKsJ1PzD1ZCrE/GkzX9" + + " HtXHIaYE6cZ7vBXQh4SVpl26JGQ87tu2YLeoZw== " + + " MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc" + + " BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx" + + " GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz" + + " MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE" + + " AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC" + + " aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd" + + " lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE" + + " w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4" + + " fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR" + + " qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH" + + " MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw" + + " Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1" + + " MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD" + + " VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G" + + " A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc" + + " aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO" + + " aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ" + + " bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO" + + " 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz" + + " NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD" + + " DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0" + + " uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH" + + " V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy" + + " VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC" + + " TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc=" + + " " + + " " + + " https://posteid.poste.it " + + " " + + " " + + " " + + " ViSjPfKj683dCuO7FdSzbQjw+vECYfoxgTeiVSgxr+I=" + + " " + + " O9lmrtHPudDz2fzzNH3DQxWy2rlXE56G54Siq7OPMYwps/cyo3wKo7+PwMJYNhhz1l57OYJ5e/MF" + + " ctVtYyl2rWo3QZOidWhg8WINIEqtFXIpk+ht5i2t3P1132/iL/gnY+fgemhnbOV/otEspHA4Wsio" + + " I8xWjekAFlHBTOTtO9vzzqTtf+yalf+6pZmRLtOYrMMV4W3QZ2oLr7C2vTgcl5eVXJyGf0U8Y2bf" + + " 7OPRHJNnVs4S8ztWQEwqZLFA1SvyCx1Nx6f+xd9lT7Lo1h81MRMdvRTk3rAaWYaqAmU9mxVnzsw4" + + " xaLjxR4rE2drY3eb+O8uHZbzFlOhPtaINRPILg== " + + " MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc" + + " BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx" + + " GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz" + + " MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE" + + " AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC" + + " aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd" + + " lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE" + + " w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4" + + " fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR" + + " qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH" + + " MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw" + + " Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1" + + " MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD" + + " VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G" + + " A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc" + + " aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO" + + " aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ" + + " bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO" + + " 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz" + + " NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD" + + " DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0" + + " uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH" + + " V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy" + + " VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC" + + " TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc=" + + " SPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + " " + + " " + + " " + + " https://app-backend.io.italia.it" + + " " + + " https://www.spid.gov.it/SpidL2" + + " " + + " " + + " " + + " TINIT-AAAAAA89S20I111X " + + " "; + public static final String RESPONSE_STRING = + "{\"response_xml\": \" " + + " https://posteid.poste.it" + + " " + + " " + + " " + + " " + + " " + + " " + + " IYCwE8NJNGLAGdL8zA/W/kuTLdlHMMXMeP2hei8LYqU=" + + " " + + " GI9CwzYfcmTBE9Lf7Hvqr2bgCkfbuq6vZPwZaaCmxq5cicDf7+k6TYussUx147iAdngl4vMixAjA" + + " eABU0cSrZllLW0Gqxm+EPvylwMc4O1tNYlpvjnZzW7PIRns5M22KSzfHBDdGZG7Dq4uDHVbGCENu" + + " TV5UaGNQJ2sNTD95Qaz6pmQtx0guehDc/m5ldFvChqZTKrOcVu+qTRFkW+OabbnkmKXPSWpTN9WE" + + " 4RramWgWkNE/sn4z0Rwmnei8oEhcBKSpOMrenbMgpCYjoRto5lDGGJrMkeKsJ1PzD1ZCrE/GkzX9" + + " HtXHIaYE6cZ7vBXQh4SVpl26JGQ87tu2YLeoZw== " + + " MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc" + + " BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx" + + " GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz" + + " MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE" + + " AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC" + + " aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd" + + " lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE" + + " w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4" + + " fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR" + + " qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH" + + " MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw" + + " Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1" + + " MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD" + + " VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G" + + " A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc" + + " aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO" + + " aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ" + + " bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO" + + " 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz" + + " NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD" + + " DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0" + + " uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH" + + " V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy" + + " VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC" + + " TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc=" + + " " + + " " + + " " + + " https://posteid.poste.it " + + " " + + " " + + " " + + " " + + " " + + " ViSjPfKj683dCuO7FdSzbQjw+vECYfoxgTeiVSgxr+I=" + + " " + + " O9lmrtHPudDz2fzzNH3DQxWy2rlXE56G54Siq7OPMYwps/cyo3wKo7+PwMJYNhhz1l57OYJ5e/MF" + + " ctVtYyl2rWo3QZOidWhg8WINIEqtFXIpk+ht5i2t3P1132/iL/gnY+fgemhnbOV/otEspHA4Wsio" + + " I8xWjekAFlHBTOTtO9vzzqTtf+yalf+6pZmRLtOYrMMV4W3QZ2oLr7C2vTgcl5eVXJyGf0U8Y2bf" + + " 7OPRHJNnVs4S8ztWQEwqZLFA1SvyCx1Nx6f+xd9lT7Lo1h81MRMdvRTk3rAaWYaqAmU9mxVnzsw4" + + " xaLjxR4rE2drY3eb+O8uHZbzFlOhPtaINRPILg== " + + " MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc" + + " BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx" + + " GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz" + + " MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE" + + " AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC" + + " aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd" + + " lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE" + + " w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4" + + " fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR" + + " qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH" + + " MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw" + + " Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1" + + " MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD" + + " VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G" + + " A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc" + + " aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO" + + " aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ" + + " bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO" + + " 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz" + + " NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD" + + " DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0" + + " uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH" + + " V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy" + + " VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC" + + " TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc=" + + " SPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + " " + + " " + + " " + + " https://app-backend.io.italia.it" + + " " + + " https://www.spid.gov.it/SpidL2" + + " " + + " " + + " TINIT-AAAAAA89S20I111X" + + " " + + " \"}"; + public static final String ASSERTION_REF = "sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg"; + public static final String WRONG_ASSERTION_REF = + "sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfXXXXX"; + public static final String JWT = "Bearer aValidJWT"; + + public static void createExpectationAssertionFound() { + new MockServerClient("localhost", 2000) + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(200).withBody(RESPONSE_STRING)); + } + + public static void createExpectationAssertionNotFound() { + new MockServerClient("localhost", 2000) + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", WRONG_ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(404).withBody("{}")); + } +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..a45e4f45 --- /dev/null +++ b/build.gradle @@ -0,0 +1,204 @@ +plugins { + id 'java' + id 'org.graalvm.buildtools.native' version '0.9.20' + id("com.diffplug.spotless") version "6.17.0" + id("nebula.lint") version "18.0.3" + id("org.kordamp.gradle.reproducible") version "0.50.0" + id "org.sonarqube" version "3.5.0.2730" + id 'jacoco' +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } + gradlePluginPortal() +} + +allprojects { + version = "1.0.0-RC1" + group = 'it.pagopa.tech.lollipop-consumer-java-sdk' + sourceCompatibility = '11' + targetCompatibility = '11' + apply plugin: 'com.diffplug.spotless' + apply plugin: 'nebula.lint' + gradleLint.rules = [] + + configurations { + spotless { + resolutionStrategy { + disableDependencyVerification() + } + } + gradleLint { + resolutionStrategy { + disableDependencyVerification() + } + } + gradleEnterprise { + resolutionStrategy { + disableDependencyVerification() + } + } + } + + spotless { + // optional: limit format enforcement to just the files changed by this feature branch + ratchetFrom 'origin/main' + + format 'misc', { + // define the files to apply `misc` to + target '*.gradle', '.gitignore' + + // define the steps to apply to those files + trimTrailingWhitespace() + indentWithTabs() // or spaces. Takes an integer argument if you don't like 4 + endWithNewline() + } + java { + // don't need to set target, it is inferred from java + + // apply a specific flavor of google-java-format + googleJavaFormat('1.15.0').aosp().reflowLongStrings() + // fix formatting of type annotations + formatAnnotations() + // make sure every file has the following copyright header. + // optionally, Spotless can set copyright years by digging + // through git history (see "license" section below) + licenseHeader '/* (C)$YEAR */' + } + } +} + + +dependencies { + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.14.1' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.1' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformats-text:2.14.1' + implementation 'com.fasterxml.jackson.module:jackson-modules-java8:2.14.1' + implementation 'com.fasterxml.jackson.module:jackson-module-parameter-names:2.14.1' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.14.1' +} + +// Settings for allowing reproducible build +tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true +} + + +subprojects { + + apply plugin: 'java' + apply plugin: 'org.kordamp.gradle.reproducible' + apply plugin: 'maven-publish' + apply plugin: 'jacoco' + apply plugin: 'org.graalvm.buildtools.native' + + + java { + withSourcesJar() + withJavadocJar() + } + + tasks.withType(JavaCompile) { + options.compilerArgs.add('-Xlint:all') + } + + config { + reproducible { + enabled + additionalProperties + additionalArtifacts + } + } + + // Settings for allowing reproducible build + tasks.withType(AbstractArchiveTask).configureEach { + preserveFileTimestamps = false + reproducibleFileOrder = true + } + + + // Generate MD5 checksum on eng-lollipop-consumer-java-sdk jar file + jar.doLast { task -> + ant.checksum file: task.archivePath + } + + publishing { + publications { + maven(MavenPublication) { + groupId project.group + artifactId project.name + version project.version + from components.java + } + } + repositories { + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/pagopa/eng-lollipop-consumer-java-sdk" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } + + sonar { + properties { + property "sonar.sources", "src/main/java" + property "sonar.tests", "src/test/java" + } + } + + + test { + finalizedBy jacocoTestReport // report is always generated after tests run + jacoco { + excludes = ["**/config/*","**/*Mock*","**/model/**","**/entity/*","**/*Stub*","**/*Config*","**/*Exception*"] + + } + } + jacocoTestReport { + dependsOn test // tests are required to run before generating the report + reports { + xml.enabled true + } + } + + } + +} + +project(":test-coverage") { + sonar { + skipProject = true + } + sonarqube { + skipProject = true + } +} + +project(":redis-storage") { + sonar { + skipProject = true + } + sonarqube { + skipProject = true + } +} + +graalvmNative { + binaries { + main { + sharedLibrary=true + } + } +} diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 00000000..b3b7b0e3 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,67 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'java-library' + id("io.freefair.lombok") version "8.0.0" +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } +} + +configurations { + instrumentedJars { + canBeConsumed = true + canBeResolved = false + attributes { + attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY)) + attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME)) + attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL)) + attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, JavaVersion.current().majorVersion.toInteger()) + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-core-jar')) + } + } +} + +dependencies { + implementation 'javax.inject:javax.inject:1' + implementation 'com.typesafe:config:1.4.2' + implementation 'com.nimbusds:nimbus-jose-jwt:9.31' + implementation 'ch.qos.logback:logback-classic:1.4.6' + implementation 'ch.qos.logback:logback-core:1.4.6' + implementation 'org.codehaus.janino:janino:3.1.9' + implementation 'org.apache.wss4j:wss4j-ws-security-common:2.4.1' + + implementation 'javax.servlet:javax.servlet-api:3.0.1' + + implementation 'com.typesafe:config:1.3.3' + + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.mockito:mockito-core:5.2.0' + testImplementation 'org.mockito:mockito-junit-jupiter:5.2.0' + testImplementation 'org.assertj:assertj-core:3.24.2' + testImplementation 'org.springframework:spring-test:5.3.26' + +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/AssertionService.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/AssertionService.java new file mode 100644 index 00000000..56b143c6 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/AssertionService.java @@ -0,0 +1,21 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion; + +import it.pagopa.tech.lollipop.consumer.exception.LollipopAssertionNotFoundException; +import it.pagopa.tech.lollipop.consumer.exception.OidcAssertionNotSupported; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; + +/** Interface of the assertion service, it defines the methods for managing the assertions */ +public interface AssertionService { + + /** + * Retrieve a SAML assertion using the provided jwt and the assertion reference + * + * @param jwt the jwt + * @param assertionRef the assertion reference + * @return the requested SAML assertion or null if the assertion is not supported + * @throws LollipopAssertionNotFoundException if some error occurred retrieving the request + */ + SamlAssertion getAssertion(String jwt, String assertionRef) + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/AssertionServiceFactory.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/AssertionServiceFactory.java new file mode 100644 index 00000000..386da2e1 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/AssertionServiceFactory.java @@ -0,0 +1,11 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion; + +/** Interface for the factory used to create instances of {@link AssertionServiceFactory} */ +public interface AssertionServiceFactory { + + /** + * @return instance of AssertionService + */ + AssertionService create(); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/AssertionClient.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/AssertionClient.java new file mode 100644 index 00000000..e05429a6 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/AssertionClient.java @@ -0,0 +1,12 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client; + +import it.pagopa.tech.lollipop.consumer.exception.LollipopAssertionNotFoundException; +import it.pagopa.tech.lollipop.consumer.exception.OidcAssertionNotSupported; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; + +public interface AssertionClient { + + SamlAssertion getAssertion(String jwt, String assertionRef) + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/AssertionClientProvider.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/AssertionClientProvider.java new file mode 100644 index 00000000..f5758b35 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/client/AssertionClientProvider.java @@ -0,0 +1,7 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.client; + +public interface AssertionClientProvider { + + AssertionClient provideClient(); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceFactoryImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceFactoryImpl.java new file mode 100644 index 00000000..6bf2d5ab --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceFactoryImpl.java @@ -0,0 +1,41 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.impl; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionService; +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import javax.inject.Inject; + +/** + * Implementation of {@link AssertionServiceFactory}, used to create instances of {@link + * AssertionServiceImpl} + */ +public class AssertionServiceFactoryImpl implements AssertionServiceFactory { + private final AssertionStorageProvider assertionStorageProvider; + private final AssertionClientProvider assertionClientProvider; + private final StorageConfig storageConfig; + + @Inject + public AssertionServiceFactoryImpl( + AssertionStorageProvider assertionStorageProvider, + AssertionClientProvider assertionClientProvider, + StorageConfig storageConfig) { + this.assertionStorageProvider = assertionStorageProvider; + this.assertionClientProvider = assertionClientProvider; + this.storageConfig = storageConfig; + } + + /** + * Factory for creating an instance of {@link AssertionServiceImpl} + * + * @return an instance of {@link AssertionServiceImpl} + */ + @Override + public AssertionService create() { + return new AssertionServiceImpl( + assertionStorageProvider.provideStorage(storageConfig), + assertionClientProvider.provideClient()); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceImpl.java new file mode 100644 index 00000000..14c0f59e --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceImpl.java @@ -0,0 +1,69 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.impl; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionService; +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClient; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorage; +import it.pagopa.tech.lollipop.consumer.exception.LollipopAssertionNotFoundException; +import it.pagopa.tech.lollipop.consumer.exception.OidcAssertionNotSupported; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import javax.inject.Inject; + +/** Service for managing the assertion */ +public class AssertionServiceImpl implements AssertionService { + + private final AssertionStorage assertionStorage; + private final AssertionClient assertionClient; + + @Inject + public AssertionServiceImpl( + AssertionStorage assertionStorage, AssertionClient assertionClient) { + this.assertionStorage = assertionStorage; + this.assertionClient = assertionClient; + } + + /** + * {@inheritDoc} + * + *

Retrieve the SAML assertion, first looking in the storage if enabled ({@link + * it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig}) and then if not found + * through the client {@link AssertionClient}. If the storage is enabled ({@link + * it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig}) and the assertion is + * retrieved through the client, it store the assertion. + * + * @param jwt the jwt + * @param assertionRef the assertion reference + * @return the SAML assertion or null if the assertion is not supported (not SAML) + * @throws LollipopAssertionNotFoundException if some error occurred retrieving the assertion + * through the client + */ + @Override + public SamlAssertion getAssertion(String jwt, String assertionRef) + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + + if (jwt == null || jwt.isBlank() || assertionRef == null || assertionRef.isBlank()) { + String errMsg = + String.format( + "Cannot retrieve the assertion, jwt [%s] or assertion reference [%s]" + + " missing", + jwt, assertionRef); + throw new IllegalArgumentException(errMsg); + } + + SamlAssertion samlAssertion = assertionStorage.getAssertion(assertionRef); + + if (samlAssertion != null) { + return samlAssertion; + } + + samlAssertion = assertionClient.getAssertion(jwt, assertionRef); + + if (samlAssertion == null) { + return null; + } + + assertionStorage.saveAssertion(assertionRef, samlAssertion); + + return samlAssertion; + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/AssertionStorage.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/AssertionStorage.java new file mode 100644 index 00000000..b18f3bf2 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/AssertionStorage.java @@ -0,0 +1,24 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.storage; + +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; + +/** Interface of the storage used for storing the assertion retrieved for validation */ +public interface AssertionStorage { + + /** + * Retrieve the assertion associated with the provided assertion reference + * + * @param assertionRef the assertion reference + * @return the SAML assertion if found, otherwise null + */ + SamlAssertion getAssertion(String assertionRef); + + /** + * Store the provided assertion + * + * @param assertionRef the assertion reference + * @param assertion the SAML assertion + */ + void saveAssertion(String assertionRef, SamlAssertion assertion); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/AssertionStorageProvider.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/AssertionStorageProvider.java new file mode 100644 index 00000000..66bde23c --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/AssertionStorageProvider.java @@ -0,0 +1,11 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.storage; + +/** Interface for the provider used to create instances of {@link AssertionStorage} */ +public interface AssertionStorageProvider { + + /** + * @return instance of {@link AssertionStorage} + */ + AssertionStorage provideStorage(StorageConfig storageConfig); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorage.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorage.java new file mode 100644 index 00000000..c01bf5da --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorage.java @@ -0,0 +1,184 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.storage; + +import it.pagopa.tech.lollipop.consumer.model.DelayedCacheObject; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import java.lang.ref.SoftReference; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; +import javax.inject.Inject; +import lombok.extern.slf4j.Slf4j; + +/** + * Implementation of the {@link AssertionStorage} interface as a simple in memory storage. + * + *

The storage can be configured via the {@link StorageConfig} configuration class. + * + *

It store in a in memory {@link java.util.HashMap} the assertions and the associated scheduled + * eviction operations, every time an assertion is accessed the associated eviction operation is + * rescheduled. + */ +@Slf4j +public class SimpleAssertionStorage implements AssertionStorage { + + private final ConcurrentHashMap> cache; + private final DelayQueue> cleaningUpQueue; + private Thread cleanerThread; + + private AtomicInteger numberOfElements; + private final StorageConfig storageConfig; + + private Executor executor; + + @Inject + public SimpleAssertionStorage(StorageConfig storageConfig) { + cache = new ConcurrentHashMap<>(); + cleaningUpQueue = new DelayQueue<>(); + initCleanerThread(); + this.storageConfig = storageConfig; + executor = new ScheduledThreadPoolExecutor(1); + numberOfElements = new AtomicInteger(0); + } + + @Inject + protected SimpleAssertionStorage( + ConcurrentHashMap> concurrentHashMap, + DelayQueue> queue, + StorageConfig storageConfig) { + cache = concurrentHashMap; + cleaningUpQueue = queue; + initCleanerThread(); + this.storageConfig = storageConfig; + executor = new ScheduledThreadPoolExecutor(1); + numberOfElements = new AtomicInteger(0); + } + + private void initCleanerThread() { + this.cleanerThread = + new Thread( + () -> { + DelayedCacheObject delayedCacheObject; + while (!Thread.currentThread().isInterrupted()) { + try { + delayedCacheObject = this.cleaningUpQueue.take(); + this.cache.remove( + delayedCacheObject.getKey(), + delayedCacheObject.getReference()); + CompletableFuture.supplyAsync( + () -> { + numberOfElements.decrementAndGet(); + return true; + }, + executor); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + }); + this.cleanerThread.setDaemon(true); + this.cleanerThread.start(); + } + + /** + * Retrieve the assertion associated with the provided assertion reference if the storage is + * enabled {@link StorageConfig}, otherwise no operation is performed. + * + *

Before the assertion is returned the associated eviction operation is rescheduled with the + * delay configured via {@link StorageConfig} + * + * @param assertionRef the assertion reference + * @return the SAML assertion if found, null if the assertion is not present in the storage or + * the storage is disabled + */ + @Override + public SamlAssertion getAssertion(String assertionRef) { + if (!storageConfig.isAssertionStorageEnabled()) { + return null; + } + + SamlAssertion samlAssertion = + Optional.ofNullable(cache.get(assertionRef)).map(SoftReference::get).orElse(null); + + if (samlAssertion != null) { + CompletableFuture.supplyAsync( + () -> { + cleaningUpQueue.removeIf( + cacheObject -> + cacheObject + .getKey() + .equals(samlAssertion.getAssertionRef())); + numberOfElements.decrementAndGet(); + saveAssertion(samlAssertion.getAssertionRef(), samlAssertion); + return true; + }, + executor); + } + + return samlAssertion; + } + + /** + * Store the assertion if the storage is enabled {@link StorageConfig}, otherwise no operation + * is performed. + * + *

Once the assertion is stored an eviction operation is scheduled with a delay configured + * via {@link StorageConfig} + * + * @param assertionRef the assertion reference + * @param assertion the SAML assertion + */ + @Override + public void saveAssertion(String assertionRef, SamlAssertion assertion) { + if (!storageConfig.isAssertionStorageEnabled()) { + return; + } + + if (assertionRef == null) { + return; + } + if (assertion == null) { + cache.remove(assertionRef); + } else { + CompletableFuture.supplyAsync( + () -> { + if (numberOfElements.get() >= storageConfig.getMaxNumberOfElements()) { + removeDelayedObject(); + } + long expiryTime = + System.currentTimeMillis() + + TimeUnit.MILLISECONDS.convert( + storageConfig.getStorageEvictionDelay(), + storageConfig.getStorageEvictionDelayTimeUnit()); + SoftReference reference = new SoftReference<>(assertion); + cache.put(assertionRef, reference); + cleaningUpQueue.put( + new DelayedCacheObject<>(assertionRef, reference, expiryTime)); + numberOfElements.incrementAndGet(); + return true; + }, + executor); + } + } + + protected void removeDelayedObject() { + DelayedCacheObject delayedCacheObject = cleaningUpQueue.peek(); + boolean isRemoved = cleaningUpQueue.remove(delayedCacheObject); + if (isRemoved) { + assert delayedCacheObject != null; + SoftReference removedElement = cache.remove(delayedCacheObject.getKey()); + if (removedElement != null && removedElement.get() != null) { + log.trace( + "Removed object: " + + Objects.requireNonNull(removedElement.get()).getAssertionRef()); + } + } + } + + public void close() { + this.cleanerThread.interrupt(); + this.cleaningUpQueue.clear(); + this.cache.clear(); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorageProvider.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorageProvider.java new file mode 100644 index 00000000..1901d365 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorageProvider.java @@ -0,0 +1,17 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.storage; + +/** Implementation of {@link AssertionStorageProvider} interface. It provides an instance of the */ +public class SimpleAssertionStorageProvider implements AssertionStorageProvider { + + /** + * {@inheritDoc} + * + * @param storageConfig the storage configuration + * @return an instance of {@link SimpleAssertionStorage} + */ + @Override + public AssertionStorage provideStorage(StorageConfig storageConfig) { + return new SimpleAssertionStorage(storageConfig); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/StorageConfig.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/StorageConfig.java new file mode 100644 index 00000000..36e1a97e --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/assertion/storage/StorageConfig.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.storage; + +import java.util.concurrent.TimeUnit; +import lombok.Data; + +/** Configuration class for the assertion storage */ +@Data +public class StorageConfig { + + private boolean assertionStorageEnabled = true; + private long storageEvictionDelay = 1L; + private TimeUnit storageEvictionDelayTimeUnit = TimeUnit.MINUTES; + private long maxNumberOfElements = 100; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/LollipopConsumerCommand.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/LollipopConsumerCommand.java new file mode 100644 index 00000000..bb8662c9 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/LollipopConsumerCommand.java @@ -0,0 +1,16 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.command; + +import it.pagopa.tech.lollipop.consumer.model.CommandResult; + +/** Interface for the command executing the lollipop request consumption */ +public interface LollipopConsumerCommand { + + /** + * Command that execute all necessary method for validating a Lollipop request: HTTP message + * verification and Saml assertion verification + * + * @return {@link CommandResult} object with result code and message of request verification + */ + CommandResult doExecute(); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/LollipopConsumerCommandBuilder.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/LollipopConsumerCommandBuilder.java new file mode 100644 index 00000000..eed04f74 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/LollipopConsumerCommandBuilder.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.command; + +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; + +/** Builder class for creating command instance */ +public interface LollipopConsumerCommandBuilder { + + /** + * Builder for creating an instance of {@link LollipopConsumerCommand} + * + * @return an instance of {@link LollipopConsumerCommand} + */ + LollipopConsumerCommand createCommand(LollipopConsumerRequest lollipopConsumerRequest); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandBuilderImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandBuilderImpl.java new file mode 100644 index 00000000..40aa78fc --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandBuilderImpl.java @@ -0,0 +1,38 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.command.impl; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommand; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.helper.LollipopConsumerFactoryHelper; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import javax.inject.Inject; + +/** + * Implementation of {@link LollipopConsumerCommandBuilder}, used to create instances of {@link + * LollipopConsumerCommandImpl} + */ +public class LollipopConsumerCommandBuilderImpl implements LollipopConsumerCommandBuilder { + + private final LollipopConsumerFactoryHelper factoryHelper; + + @Inject + public LollipopConsumerCommandBuilderImpl(LollipopConsumerFactoryHelper factoryHelper) { + this.factoryHelper = factoryHelper; + } + + /** + * Builder for creating an instance of {@link LollipopConsumerCommand} + * + * @return an instance of {@link LollipopConsumerCommand} + */ + @Override + public LollipopConsumerCommand createCommand(LollipopConsumerRequest lollipopConsumerRequest) { + return new LollipopConsumerCommandImpl( + factoryHelper.getLollipopConsumerRequestConfig(), + factoryHelper.getHttpMessageVerifierService(), + factoryHelper.getAssertionVerifierService(), + factoryHelper.getRequestValidationService(), + factoryHelper.getLollipopLoggerService(), + lollipopConsumerRequest); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandImpl.java new file mode 100644 index 00000000..653e7bb0 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandImpl.java @@ -0,0 +1,231 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.command.impl; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommand; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.enumeration.AssertionVerificationResultCode; +import it.pagopa.tech.lollipop.consumer.enumeration.HttpMessageVerificationResultCode; +import it.pagopa.tech.lollipop.consumer.exception.*; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerService; +import it.pagopa.tech.lollipop.consumer.model.CommandResult; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.service.AssertionVerifierService; +import it.pagopa.tech.lollipop.consumer.service.HttpMessageVerifierService; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import java.io.UnsupportedEncodingException; +import javax.inject.Inject; + +/** Implementation of the {@link LollipopConsumerCommand} */ +public class LollipopConsumerCommandImpl implements LollipopConsumerCommand { + + private final LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + private final HttpMessageVerifierService messageVerifierService; + private final AssertionVerifierService assertionVerifierService; + private final LollipopConsumerRequestValidationService requestValidationService; + private final LollipopConsumerRequest request; + private final LollipopLoggerService lollipopLoggerService; + + private static final String CODE_AND_MESSAGE = " with error code: %s and message: %s"; + public static final String VERIFICATION_SUCCESS_CODE = "SUCCESS"; + public static final String REQUEST_PARAMS_VALIDATION_FAILED = + "REQUEST PARAMS VALIDATION FAILED"; + + @Inject + public LollipopConsumerCommandImpl( + LollipopConsumerRequestConfig lollipopConsumerRequestConfig, + HttpMessageVerifierService messageVerifierService, + AssertionVerifierService assertionVerifierService, + LollipopConsumerRequestValidationService requestValidationService, + LollipopLoggerService lollipopLoggerService, + LollipopConsumerRequest lollipopConsumerRequest) { + this.lollipopConsumerRequestConfig = lollipopConsumerRequestConfig; + this.messageVerifierService = messageVerifierService; + this.assertionVerifierService = assertionVerifierService; + this.requestValidationService = requestValidationService; + this.lollipopLoggerService = lollipopLoggerService; + this.request = lollipopConsumerRequest; + } + + /** + * Command that execute all necessary method for validating a Lollipop request: HTTP message + * verification and Saml assertion verification + * + * @return {@link CommandResult} object with result code and message of request verification + */ + @Override + public CommandResult doExecute() { + + CommandResult commandResult; + + try { + requestValidationService.validateLollipopRequest(request); + + CommandResult messageVerificationResult = getHttpMessageVerificationResult(request); + if (!messageVerificationResult + .getResultCode() + .equals( + HttpMessageVerificationResultCode.HTTP_MESSAGE_VALIDATION_SUCCESS + .name())) { + logRequestAndResponse(request, messageVerificationResult); + return messageVerificationResult; + } + CommandResult assertionVerificationResult = getAssertionVerificationResult(request); + if (!assertionVerificationResult + .getResultCode() + .equals( + AssertionVerificationResultCode.ASSERTION_VERIFICATION_SUCCESS + .name())) { + logRequestAndResponse(request, assertionVerificationResult); + return assertionVerificationResult; + } + + } catch (LollipopRequestContentValidationException e) { + String message = + String.format( + "Error validating Lollipop request header or body, validation failed" + + " with error code %s and message: %s", + e.getErrorCode(), e.getMessage()); + commandResult = buildCommandResult(REQUEST_PARAMS_VALIDATION_FAILED, message); + logRequestAndResponse(request, commandResult); + return commandResult; + } + + commandResult = + buildCommandResult( + VERIFICATION_SUCCESS_CODE, "Verification completed successfully"); + logRequestAndResponse(request, commandResult); + return commandResult; + } + + private CommandResult getAssertionVerificationResult(LollipopConsumerRequest request) { + boolean result; + try { + result = assertionVerifierService.validateLollipop(request); + } catch (ErrorRetrievingAssertionException e) { + String message = + String.format( + "Cannot obtain the assertion, validation failed" + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + AssertionVerificationResultCode.ERROR_RETRIEVING_ASSERTION.name(), message); + } catch (AssertionPeriodException e) { + String message = + String.format( + "Assertion validation failed on verifying period" + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + AssertionVerificationResultCode.PERIOD_VALIDATION_ERROR.name(), message); + } catch (AssertionThumbprintException e) { + String message = + String.format( + "Assertion validation failed on verifying thumbprint" + + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + AssertionVerificationResultCode.THUMBPRINT_VALIDATION_ERROR.name(), message); + } catch (AssertionUserIdException e) { + String message = + String.format( + "Assertion validation failed on verifying user id" + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + AssertionVerificationResultCode.USER_ID_VALIDATION_ERROR.name(), message); + } catch (ErrorValidatingAssertionSignature e) { + String message = + String.format( + "Assertion validation failed on verifying signature" + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + AssertionVerificationResultCode.SIGNATURE_VALIDATION_ERROR.name(), message); + } catch (ErrorRetrievingIdpCertDataException e) { + String message = + String.format( + "Assertion validation failed on retrieving identity provider's" + + " certification data" + + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + AssertionVerificationResultCode.IDP_CERT_DATA_RETRIEVING_ERROR.name(), message); + } + + if (!result) { + return buildCommandResult( + AssertionVerificationResultCode.ASSERTION_VERIFICATION_FAILED.name(), + "Validation of SAML assertion failed, authentication failed"); + } + + return buildCommandResult( + AssertionVerificationResultCode.ASSERTION_VERIFICATION_SUCCESS.name(), + "SAML assertion validated successfully"); + } + + private CommandResult getHttpMessageVerificationResult(LollipopConsumerRequest request) { + boolean result; + try { + result = messageVerifierService.verifyHttpMessage(request); + } catch (LollipopDigestException e) { + String message = + String.format( + "HTTP message validation failed on verifying digest" + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + HttpMessageVerificationResultCode.DIGEST_VALIDATION_ERROR.name(), message); + } catch (LollipopSignatureException e) { + String message = + String.format( + "HTTP message validation failed on verifying signatures" + + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + HttpMessageVerificationResultCode.SIGNATURE_VALIDATION_ERROR.name(), message); + } catch (UnsupportedEncodingException e) { + String message = + String.format( + "HTTP message validation failed on encoding request body with message:" + + " %s", + e.getMessage()); + return buildCommandResult( + HttpMessageVerificationResultCode.UNSUPPORTED_ENCODING.name(), message); + } catch (LollipopVerifierException e) { + String message = + String.format( + "HTTP message validation failed on content validation" + + CODE_AND_MESSAGE, + e.getErrorCode(), + e.getMessage()); + return buildCommandResult( + HttpMessageVerificationResultCode.REQUEST_VALIDATION_ERROR.name(), message); + } + + if (!result) { + return buildCommandResult( + HttpMessageVerificationResultCode.HTTP_MESSAGE_VALIDATION_FAILED.name(), + "Validation of HTTP message failed, authentication failed"); + } + return buildCommandResult( + HttpMessageVerificationResultCode.HTTP_MESSAGE_VALIDATION_SUCCESS.name(), + "HTTP message validated successfully"); + } + + private CommandResult buildCommandResult(String resultCode, String message) { + return new CommandResult(resultCode, message); + } + + private void logRequestAndResponse( + LollipopConsumerRequest lollipopConsumerRequest, CommandResult commandResult) { + if (lollipopConsumerRequestConfig.isEnableConsumerLogging()) { + lollipopLoggerService.log( + "Lollipop validation for request: {} completed with the result: {}", + lollipopConsumerRequest, + commandResult); + } + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/config/LollipopConsumerRequestConfig.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/config/LollipopConsumerRequestConfig.java new file mode 100644 index 00000000..e46d147e --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/config/LollipopConsumerRequestConfig.java @@ -0,0 +1,53 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.config; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class LollipopConsumerRequestConfig { + + @Builder.Default private boolean strictDigestVerify = false; + + // request headers + @Builder.Default private String signatureHeader = "signature"; + @Builder.Default private String signatureInputHeader = "signature-input"; + @Builder.Default private String contentEncodingHeader = "content-encoding"; + @Builder.Default private String contentDigestHeader = "content-digest"; + @Builder.Default private String originalMethodHeader = "x-pagopa-lollipop-original-method"; + @Builder.Default private String originalURLHeader = "x-pagopa-lollipop-original-url"; + @Builder.Default private String assertionRefHeader = "x-pagopa-lollipop-assertion-ref"; + @Builder.Default private String assertionTypeHeader = "x-pagopa-lollipop-assertion-type"; + @Builder.Default private String userIdHeader = "x-pagopa-lollipop-user-id"; + @Builder.Default private String publicKeyHeader = "x-pagopa-lollipop-public-key"; + @Builder.Default private String authJWTHeader = "x-pagopa-lollipop-auth-jwt"; + + @Builder.Default private String expectedFirstLcOriginalMethod = "POST"; + + @Builder.Default + private String expectedFirstLcOriginalUrl = "https://api-app.io.pagopa.it/first-lollipop/sign"; + + // assertion validation parameters + @Builder.Default private int assertionExpireInDays = 30; + @Builder.Default private String assertionNotBeforeDateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + @Builder.Default private String assertionInstantDateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; + + @Builder.Default + private String samlNamespaceAssertion = "urn:oasis:names:tc:SAML:2.0:assertion"; + + @Builder.Default private String assertionNotBeforeTag = "Conditions"; + @Builder.Default private String assertionFiscalCodeTag = "Attribute"; + @Builder.Default private String assertionInResponseToTag = "SubjectConfirmationData"; + + @Builder.Default private boolean enableConsumerLogging = true; + @Builder.Default private boolean enableAssertionLogging = true; + @Builder.Default private boolean enableIdpCertDataLogging = true; + + @Builder.Default private String assertionEntityIdTag = "Issuer"; + @Builder.Default private String assertionInstantTag = "Assertion"; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionRefAlgorithms.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionRefAlgorithms.java new file mode 100644 index 00000000..cbe1adb8 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionRefAlgorithms.java @@ -0,0 +1,52 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.enumeration; + +import java.util.regex.Pattern; + +/** Supported AssertionRef algorithms */ +public enum AssertionRefAlgorithms { + SHA256("sha256", AlgorithmName.SHA_256, Pattern.compile("^(sha256-[A-Za-z0-9-_=]{1,44})$")), + SHA384("sha384", AlgorithmName.SHA_384, Pattern.compile("^(sha384-[A-Za-z0-9-_=]{1,66})$")), + SHA512("sha512", AlgorithmName.SHA_512, Pattern.compile("^(sha512-[A-Za-z0-9-_=]{1,88})$")); + + private final String algorithmName; + private final String hashAlgorithm; + private final Pattern pattern; + + AssertionRefAlgorithms(String algorithmName, String hashAlgorithm, Pattern pattern) { + this.algorithmName = algorithmName; + this.hashAlgorithm = hashAlgorithm; + this.pattern = pattern; + } + + public Pattern getPattern() { + return pattern; + } + + public String getHashAlgorithm() { + return hashAlgorithm; + } + + public String getAlgorithmName() { + return algorithmName; + } + + public static AssertionRefAlgorithms getAlgorithmFromHash(String algorithmName) { + switch (algorithmName) { + case AlgorithmName.SHA_256: + return SHA256; + case AlgorithmName.SHA_384: + return SHA384; + case AlgorithmName.SHA_512: + return SHA512; + default: + throw new UnsupportedOperationException("Unsupported algorithm: " + algorithmName); + } + } + + private static class AlgorithmName { + public static final String SHA_256 = "SHA-256"; + public static final String SHA_384 = "SHA-384"; + public static final String SHA_512 = "SHA-512"; + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionType.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionType.java new file mode 100644 index 00000000..71701573 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionType.java @@ -0,0 +1,9 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.enumeration; + +/** Supported assertion type */ +public enum AssertionType { + SAML, + // Not supported yet: OIDC + +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionVerificationResultCode.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionVerificationResultCode.java new file mode 100644 index 00000000..9c88baad --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/AssertionVerificationResultCode.java @@ -0,0 +1,30 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.enumeration; + +/** Result codes to classify Saml assertion validation */ +public enum AssertionVerificationResultCode { + + /** Saml assertion validation failed on retrieving the assertion */ + ERROR_RETRIEVING_ASSERTION, + + /** Saml assertion validation failed on period validation */ + PERIOD_VALIDATION_ERROR, + + /** Saml assertion validation failed on user id validation */ + USER_ID_VALIDATION_ERROR, + + /** Saml assertion validation failed on thumbprint validation */ + THUMBPRINT_VALIDATION_ERROR, + + /** Saml assertion validation failed on signature validation */ + SIGNATURE_VALIDATION_ERROR, + + /** Saml assertion validation failed on retrieving identity provider's certification data */ + IDP_CERT_DATA_RETRIEVING_ERROR, + + /** Saml assertion validation failed without throwing an exception */ + ASSERTION_VERIFICATION_FAILED, + + /** Saml assertion validation completed successfully */ + ASSERTION_VERIFICATION_SUCCESS +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/HttpMessageVerificationResultCode.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/HttpMessageVerificationResultCode.java new file mode 100644 index 00000000..9532d878 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/HttpMessageVerificationResultCode.java @@ -0,0 +1,23 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.enumeration; + +/** Result codes to classify HTTP message validation */ +public enum HttpMessageVerificationResultCode { + + /** Request validation failed throwing an exception */ + REQUEST_VALIDATION_ERROR, + /** Digest validation failed throwing an exception */ + DIGEST_VALIDATION_ERROR, + + /** Signature validation failed throwing an exception */ + SIGNATURE_VALIDATION_ERROR, + + /** No supported encoding detected when encoding request body */ + UNSUPPORTED_ENCODING, + + /** HTTP message validation failed without throwing an exception */ + HTTP_MESSAGE_VALIDATION_FAILED, + + /** HTTP message validation completed successfully */ + HTTP_MESSAGE_VALIDATION_SUCCESS; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/LollipopRequestMethod.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/LollipopRequestMethod.java new file mode 100644 index 00000000..fe1b87a8 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/enumeration/LollipopRequestMethod.java @@ -0,0 +1,11 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.enumeration; + +/** Supported Lollipop request methods */ +public enum LollipopRequestMethod { + GET, + POST, + PUT, + PATCH, + DELETE +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionPeriodException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionPeriodException.java new file mode 100644 index 00000000..1ecce2f9 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionPeriodException.java @@ -0,0 +1,49 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** Thrown in case of problems when verifying assertion period */ +public class AssertionPeriodException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public AssertionPeriodException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public AssertionPeriodException(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + ERROR_PARSING_ASSERTION_NOT_BEFORE_DATE, + INVALID_ASSERTION_PERIOD + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionThumbprintException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionThumbprintException.java new file mode 100644 index 00000000..bfd28080 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionThumbprintException.java @@ -0,0 +1,51 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** Thrown in case of problems when verifying assertion thumbprint */ +public class AssertionThumbprintException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public AssertionThumbprintException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public AssertionThumbprintException(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + IN_RESPONSE_TO_FIELD_NOT_FOUND, + IN_RESPONSE_TO_ALGORITHM_NOT_VALID, + ERROR_CALCULATING_ASSERTION_THUMBPRINT, + INVALID_IN_RESPONSE_TO + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionUserIdException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionUserIdException.java new file mode 100644 index 00000000..1c334b98 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/AssertionUserIdException.java @@ -0,0 +1,49 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** Thrown in case of problems when verifying assertion user id */ +public class AssertionUserIdException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public AssertionUserIdException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public AssertionUserIdException(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + INVALID_USER_ID, + FISCAL_CODE_FIELD_NOT_FOUND + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/CertDataNotFoundException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/CertDataNotFoundException.java new file mode 100644 index 00000000..a50423c4 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/CertDataNotFoundException.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +/** Thrown in case of problems retrieving idp certification data */ +public class CertDataNotFoundException extends Exception { + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public CertDataNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/EntityIdNotFoundException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/EntityIdNotFoundException.java new file mode 100644 index 00000000..fed2d51f --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/EntityIdNotFoundException.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +/** Thrown in case of problems finding the right data for the given entityId */ +public class EntityIdNotFoundException extends Exception { + + /** + * Constructs new exception with provided message + * + * @param message Detail message + */ + public EntityIdNotFoundException(String message) { + super(message); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorRetrievingAssertionException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorRetrievingAssertionException.java new file mode 100644 index 00000000..f8476d6d --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorRetrievingAssertionException.java @@ -0,0 +1,50 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** Thrown in case of problems on retrieving the assertion */ +public class ErrorRetrievingAssertionException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public ErrorRetrievingAssertionException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public ErrorRetrievingAssertionException(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + SAML_ASSERTION_NOT_FOUND, + OIDC_TYPE_NOT_SUPPORTED, + ERROR_PARSING_ASSERTION + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorRetrievingIdpCertDataException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorRetrievingIdpCertDataException.java new file mode 100644 index 00000000..bc0a5ca9 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorRetrievingIdpCertDataException.java @@ -0,0 +1,51 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** Thrown in case of problems on retrieving the IDP data */ +public class ErrorRetrievingIdpCertDataException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public ErrorRetrievingIdpCertDataException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public ErrorRetrievingIdpCertDataException( + ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + ENTITY_ID_FIELD_NOT_FOUND, + INSTANT_FIELD_NOT_FOUND, + IDP_CERT_DATA_NOT_FOUND + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorValidatingAssertionSignature.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorValidatingAssertionSignature.java new file mode 100644 index 00000000..e8be1a94 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/ErrorValidatingAssertionSignature.java @@ -0,0 +1,49 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** Thrown in case of problems when validating assertion signature */ +public class ErrorValidatingAssertionSignature extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public ErrorValidatingAssertionSignature(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public ErrorValidatingAssertionSignature(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + ERROR_PARSING_ASSERTION, + MISSING_ASSERTION_SIGNATURE + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/InvalidInstantFormatException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/InvalidInstantFormatException.java new file mode 100644 index 00000000..3d23afbd --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/InvalidInstantFormatException.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +/** Thrown when the given instant is not a valid timestamp */ +public class InvalidInstantFormatException extends Exception { + + /** + * Constructs new exception with provided message + * + * @param message Detail message + */ + public InvalidInstantFormatException(String message) { + super(message); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopAssertionNotFoundException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopAssertionNotFoundException.java new file mode 100644 index 00000000..7fe6f67f --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopAssertionNotFoundException.java @@ -0,0 +1,16 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +/** Thrown in case of problems retrieving assertion */ +public class LollipopAssertionNotFoundException extends Exception { + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public LollipopAssertionNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopDigestException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopDigestException.java new file mode 100644 index 00000000..c0bedde6 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopDigestException.java @@ -0,0 +1,66 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** + * Thrown in case of problems when computing or verifying digest, or when verified digest is + * incorrect + */ +public class LollipopDigestException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public LollipopDigestException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public LollipopDigestException(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Digest Exceptions */ + public enum ErrorCode { + MISSING_DIGEST, + MISSING_PAYLOAD, + /** + * No supported hash algorithms detected when verifying or processing + * Want-...-Digest headers. + */ + UNSUPPORTED_ALGORITHM, + + /** When verifying, provided digest is different from the computed one */ + INCORRECT_DIGEST, + + /** + * Parsed ...-Digest or Want-...-Digest header is not syntactically + * correct + */ + INVALID_HEADER, + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopRequestContentValidationException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopRequestContentValidationException.java new file mode 100644 index 00000000..833e3bd4 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopRequestContentValidationException.java @@ -0,0 +1,77 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** Thrown in case of problem during Lollipop request header and body validation */ +public class LollipopRequestContentValidationException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public LollipopRequestContentValidationException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public LollipopRequestContentValidationException( + ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + UNEXPECTED_ORIGINAL_METHOD, + UNEXPECTED_ORIGINAL_URL, + + MISSING_SIGNATURE, + INVALID_SIGNATURE, + + MISSING_SIGNATURE_INPUT, + INVALID_SIGNATURE_INPUT, + + MISSING_ASSERTION_REF, + INVALID_ASSERTION_REF, + + MISSING_ASSERTION_TYPE, + INVALID_ASSERTION_TYPE, + + MISSING_USER_ID, + INVALID_USER_ID, + + MISSING_AUTH_JWT, + INVALID_AUTH_JWT, + + MISSING_ORIGINAL_METHOD, + INVALID_ORIGINAL_METHOD, + + MISSING_ORIGINAL_URL, + INVALID_ORIGINAL_URL, + + MISSING_PUBLIC_KEY, + INVALID_PUBLIC_KEY + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopSignatureException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopSignatureException.java new file mode 100644 index 00000000..7d75abcd --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopSignatureException.java @@ -0,0 +1,54 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** + * Thrown in case of problems when computing or verifying signatures, or when verified signature is + * incorrect + */ +public class LollipopSignatureException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public LollipopSignatureException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public LollipopSignatureException(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Signature Exceptions */ + public enum ErrorCode { + INVALID_SIGNATURE_NUMBER, + INVALID_SIGNATURE_ALG, + MISSING_PUBLIC_KEY, + INVALID_SIGNATURE, + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopVerifierException.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopVerifierException.java new file mode 100644 index 00000000..2bd3c9e5 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/LollipopVerifierException.java @@ -0,0 +1,54 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +import java.util.Objects; + +/** + * Thrown in case of problems when computing or verifying lollipop request through the + * http-signature flow + */ +public class LollipopVerifierException extends Exception { + + /** Error code of this exception */ + private final ErrorCode errorCode; + + /** + * Constructs new exception with provided error code and message + * + * @param errorCode Error code + * @param message Detail message + */ + public LollipopVerifierException(ErrorCode errorCode, String message) { + super(message); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Constructs new exception with provided error code, message and cause + * + * @param errorCode Error code + * @param message Detail message + * @param cause Exception causing the constructed one + */ + public LollipopVerifierException(ErrorCode errorCode, String message, Throwable cause) { + super(message, cause); + this.errorCode = Objects.requireNonNull(errorCode); + } + + /** + * Returns error code + * + * @return Error code of this exception + */ + public ErrorCode getErrorCode() { + return errorCode; + } + + /** Error codes to classify Lollipop Request Exceptions */ + public enum ErrorCode { + UNAVAILABLE_ENCODING, + MISSING_SIGNATURE, + + MISSING_SIGNATURE_INPUT + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/OidcAssertionNotSupported.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/OidcAssertionNotSupported.java new file mode 100644 index 00000000..b6754d58 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/exception/OidcAssertionNotSupported.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.exception; + +/** Thrown in case an oidc assertion is retrieved, oidc assertions are not supported yet */ +public class OidcAssertionNotSupported extends Exception { + + /** + * Constructs new exception with provided message and cause + * + * @param message Detail message + */ + public OidcAssertionNotSupported(String message) { + super(message); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/helper/LollipopConsumerFactoryHelper.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/helper/LollipopConsumerFactoryHelper.java new file mode 100644 index 00000000..93bf3555 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/helper/LollipopConsumerFactoryHelper.java @@ -0,0 +1,165 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.helper; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionService; +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProvider; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerService; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.service.AssertionVerifierService; +import it.pagopa.tech.lollipop.consumer.service.HttpMessageVerifierService; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import it.pagopa.tech.lollipop.consumer.service.impl.AssertionVerifierServiceImpl; +import it.pagopa.tech.lollipop.consumer.service.impl.HttpMessageVerifierServiceImpl; +import javax.inject.Inject; + +/** Helper class for retrieving instances */ +public class LollipopConsumerFactoryHelper { + + private final LollipopLoggerServiceFactory lollipopLoggerServiceFactory; + + private final HttpMessageVerifierFactory httpMessageVerifierFactory; + private final IdpCertProviderFactory idpCertProviderFactory; + private final LollipopConsumerRequestValidationService lollipopConsumerRequestValidationService; + private final AssertionServiceFactory assertionServiceFactory; + + private IdpCertProvider idpCertProvider; + private AssertionService assertionService; + + private HttpMessageVerifierService httpMessageVerifierService; + private AssertionVerifierService assertionVerifierService; + private LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + + private LollipopLoggerService lollipopLoggerService; + + @Inject + public LollipopConsumerFactoryHelper( + LollipopLoggerServiceFactory lollipopLoggerServiceFactory, + HttpMessageVerifierFactory httpMessageVerifierFactory, + IdpCertProviderFactory idpCertProviderFactory, + AssertionServiceFactory assertionServiceFactory, + LollipopConsumerRequestValidationService lollipopConsumerRequestValidationService, + LollipopConsumerRequestConfig lollipopConsumerRequestConfig) { + this.httpMessageVerifierFactory = httpMessageVerifierFactory; + this.idpCertProviderFactory = idpCertProviderFactory; + this.assertionServiceFactory = assertionServiceFactory; + this.lollipopConsumerRequestValidationService = lollipopConsumerRequestValidationService; + this.lollipopConsumerRequestConfig = lollipopConsumerRequestConfig; + this.lollipopLoggerServiceFactory = lollipopLoggerServiceFactory; + } + + /** + * Utility method for retrieving an instance of {@link HttpMessageVerifierService} + * + * @return an instance of {@link HttpMessageVerifierService} + */ + public HttpMessageVerifierService getHttpMessageVerifierService() { + return this.httpMessageVerifierService != null + ? this.httpMessageVerifierService + : createMessageVerifierService(); + } + + private HttpMessageVerifierService createMessageVerifierService() { + + if (this.httpMessageVerifierService == null) { + this.httpMessageVerifierService = + new HttpMessageVerifierServiceImpl( + getHttpMessageVerifierFactory().create(), + getLollipopConsumerRequestConfig()); + } + return this.httpMessageVerifierService; + } + + /** + * Utility method for retrieving an instance of {@link AssertionVerifierService} + * + * @return an instance of {@link AssertionVerifierService} + */ + public AssertionVerifierService getAssertionVerifierService() { + + return this.assertionVerifierService != null + ? this.assertionVerifierService + : createAssertionVerifierService(); + } + + private synchronized AssertionVerifierService createAssertionVerifierService() { + if (this.assertionVerifierService == null) { + assertionVerifierService = + new AssertionVerifierServiceImpl( + getLollipopLoggerService(), + createIdpCertProvider(), + createAssertionService(), + getLollipopConsumerRequestConfig()); + } + return this.assertionVerifierService; + } + + public HttpMessageVerifierFactory getHttpMessageVerifierFactory() { + return httpMessageVerifierFactory; + } + + public IdpCertProviderFactory getIdpCertProviderFactory() { + return idpCertProviderFactory; + } + + public AssertionServiceFactory getAssertionServiceFactory() { + return assertionServiceFactory; + } + + public LollipopConsumerRequestConfig getLollipopConsumerRequestConfig() { + return lollipopConsumerRequestConfig != null + ? lollipopConsumerRequestConfig + : createDefaultConfig(); + } + + private synchronized LollipopConsumerRequestConfig createDefaultConfig() { + + if (this.lollipopConsumerRequestConfig == null) { + this.lollipopConsumerRequestConfig = LollipopConsumerRequestConfig.builder().build(); + } + + return this.lollipopConsumerRequestConfig; + } + + private synchronized IdpCertProvider createIdpCertProvider() { + + if (this.idpCertProvider == null) { + this.idpCertProvider = getIdpCertProviderFactory().create(); + } + + return this.idpCertProvider; + } + + private synchronized AssertionService createAssertionService() { + + if (this.assertionService == null) { + this.assertionService = getAssertionServiceFactory().create(); + } + + return this.assertionService; + } + + public LollipopConsumerRequestValidationService getRequestValidationService() { + return lollipopConsumerRequestValidationService; + } + + public void setAssertionVerifierService(AssertionVerifierService assertionVerifierService) { + this.assertionVerifierService = assertionVerifierService; + } + + public LollipopLoggerService getLollipopLoggerService() { + return this.lollipopLoggerService != null + ? lollipopLoggerService + : createLollipopLoggerService(); + } + + private synchronized LollipopLoggerService createLollipopLoggerService() { + if (this.lollipopLoggerService == null) { + this.lollipopLoggerService = this.lollipopLoggerServiceFactory.create(); + } + return this.lollipopLoggerService; + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/HttpMessageVerifier.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/HttpMessageVerifier.java new file mode 100644 index 00000000..317f730f --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/HttpMessageVerifier.java @@ -0,0 +1,42 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.http_verifier; + +import it.pagopa.tech.lollipop.consumer.exception.LollipopDigestException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopSignatureException; +import java.io.UnsupportedEncodingException; +import java.util.Map; + +/** + * Interface of the Http Verifier. This interface provides services through an implementation of the + * http-signature draft + */ +public interface HttpMessageVerifier { + + /** + * Checks whether the calculated digest of the payload matches the content-digest of the http + * request + * + * @param digest Content-Digest for the request payload + * @param requestBody Request payload + * @param encoding Encoding to be used for the payload. To be used if the underlying + * implementation checks uses the content byte array + * @return boolean set to true if the digest check is valid + * @throws LollipopDigestException + * @throws UnsupportedEncodingException + */ + boolean verifyDigest(String digest, String requestBody, String encoding) + throws LollipopDigestException, UnsupportedEncodingException; + + /** + * Checks whether the calculated signatures of the required parameter matches with those + * provided within the Signature param + * + * @param signature Input parameter containing the expected signature + * @param signatureInput Input parameter containing the expected signature base for validation + * @param parameters Header parameters to be used in signature validation + * @return boolean set to true if the signature check is valid + */ + boolean verifyHttpSignature( + String signature, String signatureInput, Map parameters) + throws LollipopSignatureException; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/HttpMessageVerifierFactory.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/HttpMessageVerifierFactory.java new file mode 100644 index 00000000..571555fd --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/HttpMessageVerifierFactory.java @@ -0,0 +1,11 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.http_verifier; + +/** Interface for the factory used to create instances of {@link HttpMessageVerifier} */ +public interface HttpMessageVerifierFactory { + + /** + * @return instance of HttpMessageVerifier + */ + HttpMessageVerifier create(); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/IdpCertProvider.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/IdpCertProvider.java new file mode 100644 index 00000000..c977a5a8 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/IdpCertProvider.java @@ -0,0 +1,12 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp; + +import it.pagopa.tech.lollipop.consumer.exception.CertDataNotFoundException; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.util.List; + +public interface IdpCertProvider { + + List getIdpCertData(String assertionInstant, String entityId) + throws CertDataNotFoundException; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/IdpCertProviderFactory.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/IdpCertProviderFactory.java new file mode 100644 index 00000000..901084ce --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/IdpCertProviderFactory.java @@ -0,0 +1,7 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp; + +public interface IdpCertProviderFactory { + + IdpCertProvider create(); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/IdpCertClient.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/IdpCertClient.java new file mode 100644 index 00000000..330af5bf --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/IdpCertClient.java @@ -0,0 +1,11 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client; + +import it.pagopa.tech.lollipop.consumer.exception.CertDataNotFoundException; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.util.List; + +public interface IdpCertClient { + + List getCertData(String entityId, String instant) throws CertDataNotFoundException; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/IdpCertClientProvider.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/IdpCertClientProvider.java new file mode 100644 index 00000000..af94fd0d --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/IdpCertClientProvider.java @@ -0,0 +1,7 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client; + +public interface IdpCertClientProvider { + + IdpCertClient provideClient(); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderFactoryImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderFactoryImpl.java new file mode 100644 index 00000000..121da224 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderFactoryImpl.java @@ -0,0 +1,31 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.impl; + +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProvider; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.idp.client.IdpCertClientProvider; +import javax.inject.Inject; + +/** + * Implementation of {@link IdpCertProviderFactory}, used to create instances of {@link + * IdpCertProviderImpl} + */ +public class IdpCertProviderFactoryImpl implements IdpCertProviderFactory { + + private final IdpCertClientProvider idpCertClientProvider; + + @Inject + public IdpCertProviderFactoryImpl(IdpCertClientProvider idpCertClientProvider) { + this.idpCertClientProvider = idpCertClientProvider; + } + + /** + * Factory for creating an instance of {@link IdpCertProvider} + * + * @return an instance of {@link IdpCertProviderImpl} + */ + @Override + public IdpCertProvider create() { + return new IdpCertProviderImpl(idpCertClientProvider.provideClient()); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderImpl.java new file mode 100644 index 00000000..e6cb40fa --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderImpl.java @@ -0,0 +1,53 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.impl; + +import it.pagopa.tech.lollipop.consumer.exception.CertDataNotFoundException; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.IdpCertClient; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.util.List; +import javax.inject.Inject; + +public class IdpCertProviderImpl implements IdpCertProvider { + + private final IdpCertClient idpCertClient; + + @Inject + public IdpCertProviderImpl(IdpCertClient idpCertClient) { + this.idpCertClient = idpCertClient; + } + + /** + * {@inheritDoc} + * + *

Retrieve the certification data of the given entityId issued in the same timeframe as the + * issue instant of the SAML assertion, first looking in the storage if enabled ({@link + * IdpCertStorageConfig}) and then, if not found, through the client {@link IdpCertClient}. If + * the storage is enabled ({@link IdpCertStorageConfig}) the IdpCertData will be stored, after + * being retrieved by the client. + * + * @param entityId Identity Provider ID + * @param assertionInstant Assertion Issue Instant + * @return the certifications issued before and after the timestamp instant + * @throws CertDataNotFoundException if an error occurred retrieving the certification XML or if + * data for the given entityId were not found + */ + @Override + public List getIdpCertData(String assertionInstant, String entityId) + throws CertDataNotFoundException { + if (assertionInstant == null + || assertionInstant.isBlank() + || entityId == null + || entityId.isBlank()) { + String errMsg = + String.format( + "Cannot retrieve the identity provider cert data, assertion instant" + + " [%s] or entity id [%s] missing", + assertionInstant, entityId); + throw new IllegalArgumentException(errMsg); + } + + return idpCertClient.getCertData(entityId, assertionInstant); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorage.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorage.java new file mode 100644 index 00000000..ef2f827a --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorage.java @@ -0,0 +1,27 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.storage; + +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; + +/** + * Interface of the storage used for storing the identity provider certification data retrieved for + * validation + */ +public interface IdpCertStorage { + + /** + * Retrieve the idp cert data associated with the provided tag + * + * @param tag + * @return the list of idpCertData found + */ + IdpCertData getIdpCertData(String tag); + + /** + * Store the provided idpCertData + * + * @param tag the idpCertData issue instance + * @param idpCertData + */ + void saveIdpCertData(String tag, IdpCertData idpCertData); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorageConfig.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorageConfig.java new file mode 100644 index 00000000..f6fe13a4 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorageConfig.java @@ -0,0 +1,14 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.storage; + +import java.util.concurrent.TimeUnit; +import lombok.Data; + +/** Configuration class for the idpCertData storage */ +@Data +public class IdpCertStorageConfig { + + private boolean idpCertDataStorageEnabled = true; + private long storageEvictionDelay = 1L; + private TimeUnit storageEvictionDelayTimeUnit = TimeUnit.MINUTES; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorageProvider.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorageProvider.java new file mode 100644 index 00000000..c73d835a --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/idp/storage/IdpCertStorageProvider.java @@ -0,0 +1,11 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.storage; + +/** Interface for the provider used to create instances of {@link IdpCertStorage} */ +public interface IdpCertStorageProvider { + + /** + * @return instance of {@link IdpCertStorage} + */ + IdpCertStorage provideStorage(IdpCertStorageConfig storageConfig); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/LollipopLoggerService.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/LollipopLoggerService.java new file mode 100644 index 00000000..aee7617e --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/LollipopLoggerService.java @@ -0,0 +1,6 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.logger; + +public interface LollipopLoggerService { + void log(String message, Object... args); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/LollipopLoggerServiceFactory.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/LollipopLoggerServiceFactory.java new file mode 100644 index 00000000..8919bb93 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/LollipopLoggerServiceFactory.java @@ -0,0 +1,7 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.logger; + +public interface LollipopLoggerServiceFactory { + + LollipopLoggerService create(); +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerService.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerService.java new file mode 100644 index 00000000..f612dc65 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerService.java @@ -0,0 +1,14 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.logger.impl; + +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerService; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class LollipopLogbackLoggerService implements LollipopLoggerService { + + @Override + public void log(String message, Object... args) { + log.info("LollipopAudit: " + message, args); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerServiceFactory.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerServiceFactory.java new file mode 100644 index 00000000..f8f7fa1c --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerServiceFactory.java @@ -0,0 +1,12 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.logger.impl; + +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerService; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; + +public class LollipopLogbackLoggerServiceFactory implements LollipopLoggerServiceFactory { + @Override + public LollipopLoggerService create() { + return new LollipopLogbackLoggerService(); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/CommandResult.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/CommandResult.java new file mode 100644 index 00000000..8113c1b0 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/CommandResult.java @@ -0,0 +1,17 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@AllArgsConstructor +@ToString +public class CommandResult { + + private String resultCode; + private String resultMessage; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/DelayedCacheObject.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/DelayedCacheObject.java new file mode 100644 index 00000000..ab2b4ad6 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/DelayedCacheObject.java @@ -0,0 +1,28 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.model; + +import java.lang.ref.SoftReference; +import java.util.concurrent.Delayed; +import java.util.concurrent.TimeUnit; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; + +@AllArgsConstructor +@EqualsAndHashCode +public class DelayedCacheObject implements Delayed { + + @Getter private final String key; + @Getter private final SoftReference reference; + private final long expiryTime; + + @Override + public long getDelay(TimeUnit unit) { + return unit.convert(expiryTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS); + } + + @Override + public int compareTo(Delayed o) { + return Long.compare(expiryTime, ((DelayedCacheObject) o).expiryTime); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/IdpCertData.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/IdpCertData.java new file mode 100644 index 00000000..ad69d217 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/IdpCertData.java @@ -0,0 +1,18 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.model; + +import java.util.List; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode +public class IdpCertData { + private String entityId; + private String tag; + private List certData; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/LollipopConsumerRequest.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/LollipopConsumerRequest.java new file mode 100644 index 00000000..1d7c076c --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/LollipopConsumerRequest.java @@ -0,0 +1,16 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.model; + +import java.util.Map; +import lombok.Builder; +import lombok.Data; +import lombok.ToString; + +@Data +@ToString +@Builder +public class LollipopConsumerRequest { + private String requestBody; + private Map requestParams; + private Map headerParams; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/SamlAssertion.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/SamlAssertion.java new file mode 100644 index 00000000..a8f5894d --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/model/SamlAssertion.java @@ -0,0 +1,17 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.model; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@ToString +@EqualsAndHashCode +public class SamlAssertion { + + private String assertionRef; + private String assertionData; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/AssertionVerifierService.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/AssertionVerifierService.java new file mode 100644 index 00000000..691c32b2 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/AssertionVerifierService.java @@ -0,0 +1,30 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service; + +import it.pagopa.tech.lollipop.consumer.exception.*; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; + +/** + * Interface of the service to be used for validating SAML Assertions obtained using the content + * passed in the {@link LollipopConsumerRequest} + */ +public interface AssertionVerifierService { + + /** + * Validates Lollipop request Assertion, using the assertion-ref within the request header + * params + * + * @param request the Lollipop request + * @return true if the assertion is valid + * @throws ErrorRetrievingAssertionException thrown for errors when retrieving the assertion + * @throws AssertionPeriodException thrown for error in assertion period validation + * @throws AssertionThumbprintException thrown for error in assertion thumbprint validation + * @throws AssertionUserIdException thrown for error in user id validation + * @throws ErrorRetrievingIdpCertDataException thrown for errors when retrieving the IDP data + * @throws ErrorValidatingAssertionSignature thrown for error in signature validation + */ + boolean validateLollipop(LollipopConsumerRequest request) + throws ErrorRetrievingAssertionException, AssertionPeriodException, + AssertionThumbprintException, AssertionUserIdException, + ErrorRetrievingIdpCertDataException, ErrorValidatingAssertionSignature; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/HttpMessageVerifierService.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/HttpMessageVerifierService.java new file mode 100644 index 00000000..f9dce202 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/HttpMessageVerifierService.java @@ -0,0 +1,28 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service; + +import it.pagopa.tech.lollipop.consumer.exception.LollipopDigestException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopSignatureException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopVerifierException; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import java.io.UnsupportedEncodingException; + +/** + * Interface of the service to be used for validating the http {@link LollipopConsumerRequest} + * through the digest and signature validation of the http-signature draft + */ +public interface HttpMessageVerifierService { + + /** + * Validates the http request + * + * @param request instance of lollipop request to validate + * @return flag to true if the request is validated + * @throws UnsupportedEncodingException thrown if the provided encoding is invalid + * @throws LollipopDigestException thrown for digest validation exceptions + * @throws LollipopVerifierException thrown for general errors in the verification process + */ + boolean verifyHttpMessage(LollipopConsumerRequest request) + throws UnsupportedEncodingException, LollipopDigestException, LollipopVerifierException, + LollipopSignatureException; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/LollipopConsumerRequestValidationService.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/LollipopConsumerRequestValidationService.java new file mode 100644 index 00000000..0c479f1b --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/LollipopConsumerRequestValidationService.java @@ -0,0 +1,18 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service; + +import it.pagopa.tech.lollipop.consumer.exception.LollipopRequestContentValidationException; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; + +/** Interface of the service used to verify Lollipop request header params */ +public interface LollipopConsumerRequestValidationService { + + /** + * Validates all request headers + * + * @param request the Lollipop request + * @throws LollipopRequestContentValidationException if some error occurred during validation + */ + void validateLollipopRequest(LollipopConsumerRequest request) + throws LollipopRequestContentValidationException; +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/AssertionVerifierServiceImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/AssertionVerifierServiceImpl.java new file mode 100644 index 00000000..608cba4c --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/AssertionVerifierServiceImpl.java @@ -0,0 +1,458 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service.impl; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.ThumbprintUtils; +import com.nimbusds.jose.util.Base64URL; +import it.pagopa.tech.lollipop.consumer.assertion.AssertionService; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.enumeration.AssertionRefAlgorithms; +import it.pagopa.tech.lollipop.consumer.exception.*; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProvider; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerService; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import it.pagopa.tech.lollipop.consumer.service.AssertionVerifierService; +import it.pagopa.tech.lollipop.consumer.utils.LollipopSamlAssertionWrapper; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.StringReader; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Base64; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import lombok.extern.slf4j.Slf4j; +import org.apache.wss4j.common.ext.WSSecurityException; +import org.apache.wss4j.common.saml.SAMLKeyInfo; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +/** Standard implementation of {@link AssertionVerifierService} */ +@Slf4j +public class AssertionVerifierServiceImpl implements AssertionVerifierService { + + private final LollipopLoggerService lollipopLoggerService; + + private final IdpCertProvider idpCertProvider; + private final AssertionService assertionService; + private final LollipopConsumerRequestConfig lollipopRequestConfig; + + private static final String IN_RESPONSE_TO = "InResponseTo"; + private static final String ISSUE_INSTANT = "IssueInstant"; + private static final String NOT_BEFORE = "NotBefore"; + + @Inject + public AssertionVerifierServiceImpl( + LollipopLoggerService lollipopLoggerService, + IdpCertProvider idpCertProvider, + AssertionService assertionService, + LollipopConsumerRequestConfig lollipopRequestConfig) { + this.lollipopLoggerService = lollipopLoggerService; + this.idpCertProvider = idpCertProvider; + this.assertionService = assertionService; + this.lollipopRequestConfig = lollipopRequestConfig; + } + + /** + * @see AssertionVerifierService#validateLollipop(LollipopConsumerRequest) + */ + @Override + public boolean validateLollipop(LollipopConsumerRequest request) + throws ErrorRetrievingAssertionException, AssertionPeriodException, + AssertionThumbprintException, AssertionUserIdException, + ErrorRetrievingIdpCertDataException, ErrorValidatingAssertionSignature { + Map headerParams = request.getHeaderParams(); + + Document assertionDoc = + getAssertion( + headerParams.get(lollipopRequestConfig.getAuthJWTHeader()), + headerParams.get(lollipopRequestConfig.getAssertionRefHeader())); + + boolean isAssertionPeriodValid = validateAssertionPeriod(assertionDoc); + if (!isAssertionPeriodValid) { + throw new AssertionPeriodException( + AssertionPeriodException.ErrorCode.INVALID_ASSERTION_PERIOD, + "The assertion has expired"); + } + + boolean isUserIdValid = validateUserId(request, assertionDoc); + if (!isUserIdValid) { + throw new AssertionUserIdException( + AssertionUserIdException.ErrorCode.INVALID_USER_ID, + "The user id in the assertion does not match the request header"); + } + + boolean isInResponseToValid = validateInResponseTo(request, assertionDoc); + if (!isInResponseToValid) { + throw new AssertionThumbprintException( + AssertionThumbprintException.ErrorCode.INVALID_IN_RESPONSE_TO, + "The hash of provided public key do not match the InResponseTo in the" + + " assertion"); + } + + List idpCertDataList = getIdpCertData(assertionDoc); + return validateSignature(assertionDoc, idpCertDataList); + } + + private Document getAssertion(String jwt, String assertionRef) + throws ErrorRetrievingAssertionException { + SamlAssertion assertion; + try { + assertion = assertionService.getAssertion(jwt, assertionRef); + assertionCheckLogging( + "SAML Assertion found using assertionRef {} and jwt {}: {}", + assertionRef, + jwt, + assertion); + } catch (OidcAssertionNotSupported e) { + throw new ErrorRetrievingAssertionException( + ErrorRetrievingAssertionException.ErrorCode.OIDC_TYPE_NOT_SUPPORTED, + e.getMessage(), + e); + } catch (LollipopAssertionNotFoundException e) { + throw new ErrorRetrievingAssertionException( + ErrorRetrievingAssertionException.ErrorCode.SAML_ASSERTION_NOT_FOUND, + e.getMessage(), + e); + } + return buildDocumentFromAssertion(assertion); + } + + protected boolean validateAssertionPeriod(Document assertionDoc) + throws AssertionPeriodException { + NodeList listElements = + assertionDoc.getElementsByTagNameNS( + lollipopRequestConfig.getSamlNamespaceAssertion(), + lollipopRequestConfig.getAssertionNotBeforeTag()); + if (isElementNotFound(listElements, NOT_BEFORE)) { + return false; + } + String notBefore = + listElements.item(0).getAttributes().getNamedItem(NOT_BEFORE).getNodeValue(); + + long notBeforeMilliseconds; + try { + notBeforeMilliseconds = + new SimpleDateFormat(lollipopRequestConfig.getAssertionNotBeforeDateFormat()) + .parse(notBefore) + .getTime(); + } catch (ParseException e) { + throw new AssertionPeriodException( + AssertionPeriodException.ErrorCode.ERROR_PARSING_ASSERTION_NOT_BEFORE_DATE, + e.getMessage(), + e); + } + long dateNowMilliseconds = new Date().getTime(); + long expiresAfterMilliseconds = + TimeUnit.DAYS.toMillis(lollipopRequestConfig.getAssertionExpireInDays()); + long dateNowLessNotBefore = (dateNowMilliseconds - notBeforeMilliseconds); + + return 0 <= dateNowLessNotBefore && (dateNowLessNotBefore <= expiresAfterMilliseconds); + } + + protected boolean validateUserId(LollipopConsumerRequest request, Document assertionDoc) + throws AssertionUserIdException { + String userIdHeader = + request.getHeaderParams().get(lollipopRequestConfig.getUserIdHeader()); + + String userIdFromAssertion = getUserIdFromAssertion(assertionDoc); + if (userIdFromAssertion == null) { + throw new AssertionUserIdException( + AssertionUserIdException.ErrorCode.FISCAL_CODE_FIELD_NOT_FOUND, + "Missing or invalid Fiscal Code in the retrieved saml assertion."); + } + + return userIdFromAssertion.equals(userIdHeader); + } + + protected boolean validateInResponseTo(LollipopConsumerRequest request, Document assertionDoc) + throws AssertionThumbprintException { + NodeList listElements = + assertionDoc.getElementsByTagNameNS( + lollipopRequestConfig.getSamlNamespaceAssertion(), + lollipopRequestConfig.getAssertionInResponseToTag()); + if (isElementNotFound(listElements, IN_RESPONSE_TO)) { + throw new AssertionThumbprintException( + AssertionThumbprintException.ErrorCode.IN_RESPONSE_TO_FIELD_NOT_FOUND, + "Missing request id in the retrieved saml assertion"); + } + String inResponseTo = + listElements.item(0).getAttributes().getNamedItem(IN_RESPONSE_TO).getNodeValue(); + + String inResponseToAlgorithm = retrieveInResponseToAlgorithm(inResponseTo); + + String publicKey = + request.getHeaderParams().get(lollipopRequestConfig.getPublicKeyHeader()); + String calculatedThumbprint = calculateThumbprint(inResponseToAlgorithm, publicKey); + String assertionRefHeader = + request.getHeaderParams().get(lollipopRequestConfig.getAssertionRefHeader()); + + return inResponseTo.equals(calculatedThumbprint) && inResponseTo.equals(assertionRefHeader); + } + + protected List getIdpCertData(Document assertionDoc) + throws ErrorRetrievingIdpCertDataException { + NodeList listElements = + assertionDoc.getElementsByTagNameNS( + lollipopRequestConfig.getSamlNamespaceAssertion(), + lollipopRequestConfig.getAssertionInstantTag()); + if (isElementNotFound(listElements, ISSUE_INSTANT)) { + throw new ErrorRetrievingIdpCertDataException( + ErrorRetrievingIdpCertDataException.ErrorCode.INSTANT_FIELD_NOT_FOUND, + "Missing instant field in the retrieved saml assertion"); + } + String instant = + listElements.item(0).getAttributes().getNamedItem(ISSUE_INSTANT).getNodeValue(); + + String entityId = getEntityId(listElements.item(0).getChildNodes()); + if (entityId == null) { + throw new ErrorRetrievingIdpCertDataException( + ErrorRetrievingIdpCertDataException.ErrorCode.ENTITY_ID_FIELD_NOT_FOUND, + "Missing entity id field in the retrieved saml assertion"); + } + instant = parseInstantToMillis(instant); + try { + entityId = entityId.trim(); + List idpCertData = idpCertProvider.getIdpCertData(instant, entityId); + this.idpCertDataLogging( + "IdP certificates has been found for entityId {} at instant {}: {}", + entityId, + instant, + idpCertData); + return idpCertData; + } catch (CertDataNotFoundException e) { + throw new ErrorRetrievingIdpCertDataException( + ErrorRetrievingIdpCertDataException.ErrorCode.IDP_CERT_DATA_NOT_FOUND, + "Some error occurred in retrieving certification data from IDP", + e); + } + } + + protected boolean validateSignature(Document assertionDoc, List idpCertDataList) + throws ErrorValidatingAssertionSignature { + LollipopSamlAssertionWrapper wrapper; + try { + wrapper = + new LollipopSamlAssertionWrapper( + (Element) + assertionDoc + .getElementsByTagNameNS( + lollipopRequestConfig + .getSamlNamespaceAssertion(), + lollipopRequestConfig.getAssertionInstantTag()) + .item(0)); + } catch (WSSecurityException e) { + throw new ErrorValidatingAssertionSignature( + ErrorValidatingAssertionSignature.ErrorCode.ERROR_PARSING_ASSERTION, + "Failed to build SAML object from assertion", + e); + } + + return validateSignature(idpCertDataList, wrapper); + } + + private boolean validateSignature( + List idpCertDataList, LollipopSamlAssertionWrapper wrapper) + throws ErrorValidatingAssertionSignature { + for (IdpCertData idpCertData : idpCertDataList) { + for (String certData : idpCertData.getCertData()) { + try { + X509Certificate x509Certificate = getX509Certificate(certData); + wrapper.verifySignatureLollipop( + new SAMLKeyInfo(new X509Certificate[] {x509Certificate})); + } catch (CertificateException | WSSecurityException e) { + // CertificateException: Failed to generate X509 certificate from IDP metadata + // or + // WSSecurityException: Failed to validate assertion signature + // this exceptions are ignored because if the signature validation fail for one + // certificate it may pass with one of the other certificates + continue; + } + return true; + } + } + return false; + } + + private X509Certificate getX509Certificate(String idpCertificate) throws CertificateException { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + InputStream fileStream = + new ByteArrayInputStream(Base64.getMimeDecoder().decode(idpCertificate)); + return (X509Certificate) certificateFactory.generateCertificate(fileStream); + } + + private static Document buildDocumentFromAssertion(SamlAssertion assertion) + throws ErrorRetrievingAssertionException { + String stringXml = assertion.getAssertionData(); + try { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + // completely disable DOCTYPE declaration: + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(new InputSource(new StringReader(stringXml))); + } catch (ParserConfigurationException | SAXException | IOException e) { + throw new ErrorRetrievingAssertionException( + ErrorRetrievingAssertionException.ErrorCode.ERROR_PARSING_ASSERTION, + e.getMessage(), + e); + } + } + + private boolean isElementNotFound(NodeList listElements, String elementName) { + return listElements == null + || listElements.getLength() <= 0 + || listElements.item(0) == null + || !listElements.item(0).hasAttributes() + || listElements.item(0).getAttributes().getNamedItem(elementName) == null + || listElements.item(0).getAttributes().getNamedItem(elementName).getNodeValue() + == null; + } + + private String getUserIdFromAssertion(Document assertionDoc) throws AssertionUserIdException { + NodeList listElements = + assertionDoc.getElementsByTagNameNS( + lollipopRequestConfig.getSamlNamespaceAssertion(), + lollipopRequestConfig.getAssertionFiscalCodeTag()); + if (listElements == null || listElements.getLength() <= 0) { + throw new AssertionUserIdException( + AssertionUserIdException.ErrorCode.FISCAL_CODE_FIELD_NOT_FOUND, + "Missing or invalid Fiscal Code in the retrieved saml assertion."); + } + + for (int i = 0; i < listElements.getLength(); i++) { + Node item = listElements.item(i); + if (item == null || item.getAttributes() == null) { + continue; + } + Node name = item.getAttributes().getNamedItem("Name"); + if (name != null + && name.getNodeValue().equals("fiscalNumber") + && item.getTextContent() != null) { + return item.getTextContent().trim().replace("TINIT-", ""); + } + } + return null; + } + + private String retrieveInResponseToAlgorithm(String inResponseTo) + throws AssertionThumbprintException { + boolean matchesSHA256 = + AssertionRefAlgorithms.SHA256.getPattern().matcher(inResponseTo).matches(); + boolean matchesSHA384 = + AssertionRefAlgorithms.SHA384.getPattern().matcher(inResponseTo).matches(); + boolean matchesSHA512 = + AssertionRefAlgorithms.SHA512.getPattern().matcher(inResponseTo).matches(); + + if (matchesSHA256) { + return AssertionRefAlgorithms.SHA256.getHashAlgorithm(); + } + if (matchesSHA384) { + return AssertionRefAlgorithms.SHA384.getHashAlgorithm(); + } + if (matchesSHA512) { + return AssertionRefAlgorithms.SHA512.getHashAlgorithm(); + } + throw new AssertionThumbprintException( + AssertionThumbprintException.ErrorCode.IN_RESPONSE_TO_ALGORITHM_NOT_VALID, + "InResponseTo in the assertion do not contains a valid Assertion Ref or it contains" + + " an invalid algorithm."); + } + + private String calculateThumbprint(String inResponseToAlgorithm, String publicKey) + throws AssertionThumbprintException { + Base64URL thumbprint; + try { + publicKey = getPublicKey(publicKey); + thumbprint = ThumbprintUtils.compute(inResponseToAlgorithm, JWK.parse(publicKey)); + } catch (JOSEException | ParseException e) { + String errMsg = String.format("Can not calculate JwkThumbprint: %S", e.getMessage()); + throw new AssertionThumbprintException( + AssertionThumbprintException.ErrorCode.ERROR_CALCULATING_ASSERTION_THUMBPRINT, + errMsg, + e); + } + AssertionRefAlgorithms algo = + AssertionRefAlgorithms.getAlgorithmFromHash(inResponseToAlgorithm); + String calculatedThumbprint = String.format("%s-%s", algo.getAlgorithmName(), thumbprint); + if (!algo.getPattern().matcher(calculatedThumbprint).matches()) { + throw new AssertionThumbprintException( + AssertionThumbprintException.ErrorCode.ERROR_CALCULATING_ASSERTION_THUMBPRINT, + "The calculated thumbprint does not match the expected pattern: " + + calculatedThumbprint); + } + return calculatedThumbprint; + } + + private String getEntityId(NodeList listElements) { + if (listElements == null) { + return null; + } + for (int i = 0; i < listElements.getLength(); i++) { + Node item = listElements.item(i); + if (item != null + && item.getLocalName() != null + && item.getLocalName().equals(lollipopRequestConfig.getAssertionEntityIdTag()) + && item.getTextContent() != null) { + return item.getTextContent(); + } + } + return null; + } + + private void assertionCheckLogging(String message, Object... args) { + if (lollipopRequestConfig.isEnableConsumerLogging() + && lollipopRequestConfig.isEnableAssertionLogging()) { + lollipopLoggerService.log(message, args); + } + } + + private void idpCertDataLogging(String message, Object... args) { + if (lollipopRequestConfig.isEnableConsumerLogging() + && lollipopRequestConfig.isEnableIdpCertDataLogging()) { + lollipopLoggerService.log(message, args); + } + } + + private String getPublicKey(String publicKey) { + try { + publicKey = new String(Base64.getDecoder().decode(publicKey)); + } catch (Exception e) { + log.debug("Key not in Base64"); + } + return publicKey; + } + + private String parseInstantToMillis(String instant) { + String instantDateFormat = lollipopRequestConfig.getAssertionInstantDateFormat(); + try { + instant = + Long.toString(new SimpleDateFormat(instantDateFormat).parse(instant).getTime()); + } catch (ParseException e) { + String msg = + String.format( + "Retrieved instant %s does not match expected format %s", + instant, instantDateFormat); + log.debug(msg); + } + return instant; + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/HttpMessageVerifierServiceImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/HttpMessageVerifierServiceImpl.java new file mode 100644 index 00000000..286ea7c6 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/HttpMessageVerifierServiceImpl.java @@ -0,0 +1,149 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service.impl; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.exception.LollipopDigestException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopSignatureException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopVerifierException; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifier; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.service.HttpMessageVerifierService; +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import javax.inject.Inject; + +/** Standard implementation of {@link HttpMessageVerifierService} */ +public class HttpMessageVerifierServiceImpl implements HttpMessageVerifierService { + + private HttpMessageVerifier httpMessageVerifier; + private LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + + @Inject + public HttpMessageVerifierServiceImpl( + HttpMessageVerifier httpMessageVerifier, + LollipopConsumerRequestConfig lollipopConsumerRequestConfig) { + this.httpMessageVerifier = httpMessageVerifier; + this.lollipopConsumerRequestConfig = lollipopConsumerRequestConfig; + } + + /** + * @see HttpMessageVerifierService#verifyHttpMessage(LollipopConsumerRequest) + */ + @Override + public boolean verifyHttpMessage(LollipopConsumerRequest lollipopConsumerRequest) + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + LollipopSignatureException { + + Map headerParams = lollipopConsumerRequest.getHeaderParams(); + + String signature = headerParams.get(lollipopConsumerRequestConfig.getSignatureHeader()); + + if (signature == null) { + throw new LollipopVerifierException( + LollipopVerifierException.ErrorCode.MISSING_SIGNATURE, + "Missing Signature Header"); + } + + String signatureInput = + headerParams.get(lollipopConsumerRequestConfig.getSignatureInputHeader()); + + if (signatureInput == null) { + throw new LollipopVerifierException( + LollipopVerifierException.ErrorCode.MISSING_SIGNATURE_INPUT, + "Missing Signature-Input Header"); + } + + if (lollipopConsumerRequestConfig.isStrictDigestVerify() + || hasDigestInSignatureInput(signatureInput)) { + + String contentDigest = + headerParams.get(lollipopConsumerRequestConfig.getContentDigestHeader()); + + String requestBody = lollipopConsumerRequest.getRequestBody(); + + String contentEncoding = + headerParams.get(lollipopConsumerRequestConfig.getContentEncodingHeader()); + + if (!verifyContentDigest(contentDigest, requestBody, contentEncoding)) { + throw new LollipopDigestException( + LollipopDigestException.ErrorCode.INCORRECT_DIGEST, + "Content-Digest does not match the request payload"); + } + } + + return verifyHttpSignature(signature, signatureInput, headerParams); + } + + /** + * Checks if any of the signatures have the content-digest param within the signature input, to + * determine if in non-strict mode the digest should be validated + * + * @param signatureInput request Signature-Input content + * @return flag to determine if the content-digest is present + */ + private boolean hasDigestInSignatureInput(String signatureInput) { + return Arrays.stream(signatureInput.split(";")) + .anyMatch( + signaturePart -> + signaturePart.contains("=") + && signaturePart + .toLowerCase(Locale.ROOT) + .contains( + lollipopConsumerRequestConfig + .getContentDigestHeader() + .toLowerCase(Locale.ROOT))); + } + + /** + * Checks for required params to be used within the digest validation process, and executes the + * validation through the usage of the provided implementation of the {@link + * HttpMessageVerifier} + * + * @param contentDigest content digest provided by the request to check + * @param requestBody payload to be used for digest verification + * @param contentEncoding encoding for the related payload, if necessary + * @return flag to determine if the content digest matches the calculated one with the provided + * request Body + * @throws LollipopDigestException exceptions related to the digest validation process + * @throws UnsupportedEncodingException exceptions related to encoding errors + */ + private boolean verifyContentDigest( + String contentDigest, String requestBody, String contentEncoding) + throws LollipopDigestException, UnsupportedEncodingException { + + if (contentDigest == null) { + throw new LollipopDigestException( + LollipopDigestException.ErrorCode.MISSING_DIGEST, + "Missing required Content-Digest for validation"); + } + + if (requestBody == null) { + throw new LollipopDigestException( + LollipopDigestException.ErrorCode.MISSING_PAYLOAD, + "Missing required payload for digest validation"); + } + + // Attempt to execute digest validation + return httpMessageVerifier.verifyDigest(contentDigest, requestBody, contentEncoding); + } + + /** + * @param signature + * @param signatureInput + * @param headerParams + * @return + * @throws LollipopSignatureException + */ + private boolean verifyHttpSignature( + String signature, String signatureInput, Map headerParams) + throws LollipopSignatureException { + + HashMap headersToProcess = new HashMap<>(headerParams); + headersToProcess.remove(lollipopConsumerRequestConfig.getSignatureHeader()); + headersToProcess.remove(lollipopConsumerRequestConfig.getSignatureInputHeader()); + return httpMessageVerifier.verifyHttpSignature(signature, signatureInput, headersToProcess); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/LollipopConsumerRequestValidationServiceImpl.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/LollipopConsumerRequestValidationServiceImpl.java new file mode 100644 index 00000000..1e629447 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/service/impl/LollipopConsumerRequestValidationServiceImpl.java @@ -0,0 +1,267 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service.impl; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.KeyType; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.enumeration.AssertionRefAlgorithms; +import it.pagopa.tech.lollipop.consumer.enumeration.AssertionType; +import it.pagopa.tech.lollipop.consumer.enumeration.LollipopRequestMethod; +import it.pagopa.tech.lollipop.consumer.exception.LollipopRequestContentValidationException; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import java.text.ParseException; +import java.util.Base64; +import java.util.Map; +import java.util.logging.Level; +import java.util.regex.Pattern; +import lombok.extern.java.Log; + +@Log +public class LollipopConsumerRequestValidationServiceImpl + implements LollipopConsumerRequestValidationService { + + private final LollipopConsumerRequestConfig config; + + public LollipopConsumerRequestValidationServiceImpl(LollipopConsumerRequestConfig config) { + this.config = config; + } + + @Override + public void validateLollipopRequest(LollipopConsumerRequest request) + throws LollipopRequestContentValidationException { + Map headerParams = request.getHeaderParams(); + + validatePublicKey(headerParams.get(this.config.getPublicKeyHeader())); + validateAssertionRefHeader(headerParams.get(this.config.getAssertionRefHeader())); + validateAssertionTypeHeader(headerParams.get(this.config.getAssertionTypeHeader())); + validateUserIdHeader(headerParams.get(this.config.getUserIdHeader())); + validateAuthJWTHeader(headerParams.get(this.config.getAuthJWTHeader())); + validateOriginalMethodHeader(headerParams.get(this.config.getOriginalMethodHeader())); + validateOriginalURLHeader(headerParams.get(this.config.getOriginalURLHeader())); + validateSignatureInputHeader(headerParams.get(this.config.getSignatureInputHeader())); + validateSignatureHeader(headerParams.get(this.config.getSignatureHeader())); + } + + private void validatePublicKey(String publicKey) + throws LollipopRequestContentValidationException { + if (publicKey == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_PUBLIC_KEY, + "Missing Public Key Header"); + } + + try { + publicKey = new String(Base64.getDecoder().decode(publicKey.getBytes())); + } catch (Exception e) { + log.log(Level.FINE, "Key not in Base64"); + } + + if (isNotValidPublicKey(publicKey)) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_PUBLIC_KEY, + "Invalid Public Key Header value"); + } + } + + private boolean isNotValidPublicKey(String publicKey) { + try { + JWK jwk = JWK.parse(publicKey); + KeyType keyType = jwk.getKeyType(); + if (KeyType.EC.equals(keyType)) { + jwk.toECKey().toECPublicKey(); + } else if (KeyType.RSA.equals(keyType)) { + jwk.toRSAKey().toRSAPublicKey(); + } + } catch (JOSEException | ParseException e) { + return true; + } + return false; + } + + private void validateAssertionRefHeader(String assertionRef) + throws LollipopRequestContentValidationException { + if (assertionRef == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_ASSERTION_REF, + "Missing AssertionRef Header"); + } + + if (isNotValidAssertionRef(assertionRef)) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_ASSERTION_REF, + "Invalid AssertionRef Header value"); + } + } + + private boolean isNotValidAssertionRef(String signature) { + boolean matchesSHA256 = + AssertionRefAlgorithms.SHA256.getPattern().matcher(signature).matches(); + boolean matchesSHA384 = + AssertionRefAlgorithms.SHA384.getPattern().matcher(signature).matches(); + boolean matchesSHA512 = + AssertionRefAlgorithms.SHA512.getPattern().matcher(signature).matches(); + return !matchesSHA256 && !matchesSHA384 && !matchesSHA512; + } + + private void validateAssertionTypeHeader(String assertionType) + throws LollipopRequestContentValidationException { + if (assertionType == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_ASSERTION_TYPE, + "Missing Assertion Type Header"); + } + + if ((!isAssertionTypeSupported(assertionType))) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_ASSERTION_TYPE, + "Invalid Assertion Type Header value, type not supported"); + } + } + + private boolean isAssertionTypeSupported(String assertionType) { + for (AssertionType supportedType : AssertionType.values()) { + if (supportedType.name().equals(assertionType)) { + return true; + } + } + return false; + } + + private void validateUserIdHeader(String userId) + throws LollipopRequestContentValidationException { + if (userId == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_USER_ID, + "Missing User Id Header"); + } + + if ((isNotValidFiscalCode(userId))) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_USER_ID, + "Invalid User Id Header value, type not supported"); + } + } + + private boolean isNotValidFiscalCode(String userId) { + return !Pattern.compile( + "^[A-Z]{6}[0-9LMNPQRSTUV]{2}[ABCDEHLMPRST][0-9LMNPQRSTUV]{2}[A-Z][0-9LMNPQRSTUV]{3}[A-Z]$") + .matcher(userId) + .matches(); + } + + private void validateAuthJWTHeader(String authJWT) + throws LollipopRequestContentValidationException { + if (authJWT == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_AUTH_JWT, + "Missing Auth JWT Header"); + } + + if (authJWT.isBlank()) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_AUTH_JWT, + "Invalid Auth JWT Header value, cannot be empty"); + } + } + + private void validateOriginalMethodHeader(String originalMethod) + throws LollipopRequestContentValidationException { + if (originalMethod == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_ORIGINAL_METHOD, + "Missing Original Method Header"); + } + + if (!isRequestMethodSupported(originalMethod)) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_ORIGINAL_METHOD, + "Invalid Original Method Header value, method not supported"); + } + + if (!originalMethod.equals(this.config.getExpectedFirstLcOriginalMethod())) { + String errMsg = String.format("Unexpected original method: %s", originalMethod); + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.UNEXPECTED_ORIGINAL_METHOD, + errMsg); + } + } + + private boolean isRequestMethodSupported(String originalMethod) { + for (LollipopRequestMethod supportedType : LollipopRequestMethod.values()) { + if (supportedType.name().equals(originalMethod)) { + return true; + } + } + return false; + } + + private void validateOriginalURLHeader(String originalURL) + throws LollipopRequestContentValidationException { + if (originalURL == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_ORIGINAL_URL, + "Missing Original URL Header"); + } + + if (isNotValidOriginalURL(originalURL)) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_ORIGINAL_URL, + "Invalid Original URL Header value"); + } + + if (!originalURL.equals(this.config.getExpectedFirstLcOriginalUrl())) { + String errMsg = String.format("Unexpected original url: %s", originalURL); + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.UNEXPECTED_ORIGINAL_URL, + errMsg); + } + } + + private boolean isNotValidOriginalURL(String originalURL) { + return !Pattern.compile("^https://\\S+").matcher(originalURL).matches(); + } + + private void validateSignatureInputHeader(String signatureInput) + throws LollipopRequestContentValidationException { + if (signatureInput == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_SIGNATURE_INPUT, + "Missing Signature Input Header"); + } + + if (isNotValidSignatureInput(signatureInput)) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_SIGNATURE_INPUT, + "Invalid Signature Input Header value"); + } + } + + private boolean isNotValidSignatureInput(String signatureInput) { + return !Pattern.compile("^(((sig[\\d]++)=[^,]*+)(, ?+)?+)++$") + .matcher(signatureInput) + .matches(); + } + + private void validateSignatureHeader(String signature) + throws LollipopRequestContentValidationException { + if (signature == null) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.MISSING_SIGNATURE, + "Missing Signature Header"); + } + + if (isNotValidSignature(signature)) { + throw new LollipopRequestContentValidationException( + LollipopRequestContentValidationException.ErrorCode.INVALID_SIGNATURE, + "Invalid Signature Header value"); + } + } + + private boolean isNotValidSignature(String signature) { + return !Pattern.compile("^((sig[\\d]+)=:[A-Za-z0-9+/=]*+:(, ?)?)+$") + .matcher(signature) + .matches(); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopConsumerConverter.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopConsumerConverter.java new file mode 100644 index 00000000..fd40d2df --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopConsumerConverter.java @@ -0,0 +1,66 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.utils; + +import static it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandImpl.VERIFICATION_SUCCESS_CODE; + +import it.pagopa.tech.lollipop.consumer.model.CommandResult; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.stream.Collectors; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class LollipopConsumerConverter { + + private LollipopConsumerConverter() { + throw new IllegalStateException("Utility class"); + } + + /** + * Utility method to be used to generate a LollipopConsumerRequest from a HttpServletRequest + * + * @param httpServletRequest http request to be converted into a lollipop request + * @return instance of {@link LollipopConsumerRequest} produced from the httpServletRequest + * @throws IOException exception return if body extraction fails + */ + public static LollipopConsumerRequest convertToLollipopRequest( + HttpServletRequest httpServletRequest) throws IOException { + + byte[] requestBody = null; + + String method = httpServletRequest.getMethod(); + + if (method != null && (!method.equals("GET") && !method.equals("DELETE"))) { + InputStream requestInputStream = httpServletRequest.getInputStream(); + requestBody = requestInputStream.readAllBytes(); + } + + return LollipopConsumerRequest.builder() + .requestBody(requestBody != null ? new String(requestBody) : null) + .headerParams( + Collections.list(httpServletRequest.getHeaderNames()).stream() + .collect(Collectors.toMap(h -> h, httpServletRequest::getHeader))) + .requestParams(httpServletRequest.getParameterMap()) + .build(); + } + + /** + * Utility method used to convert the commandResult in a HttpServletResponse + * + * @param commandResult results of the LollipopConsumerCommand's doExecute + * @return instance of HttpServletResponse with the commandResult status code and response + * @throws IOException when failed to send error + */ + public static HttpServletResponse interceptResult( + CommandResult commandResult, HttpServletResponse httpResponse) throws IOException { + + if (!commandResult.getResultCode().equals(VERIFICATION_SUCCESS_CODE)) { + httpResponse.setStatus(401); + httpResponse.getWriter().write(commandResult.getResultMessage()); + } + + return httpResponse; + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopSamlAssertionWrapper.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopSamlAssertionWrapper.java new file mode 100644 index 00000000..af56d2f9 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopSamlAssertionWrapper.java @@ -0,0 +1,38 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.utils; + +import it.pagopa.tech.lollipop.consumer.exception.ErrorValidatingAssertionSignature; +import org.apache.wss4j.common.ext.WSSecurityException; +import org.apache.wss4j.common.saml.SAMLKeyInfo; +import org.apache.wss4j.common.saml.SamlAssertionWrapper; +import org.opensaml.xmlsec.signature.Signature; +import org.w3c.dom.Element; + +/** + * Extension of the {@link SamlAssertionWrapper} class that add the check for the existence of + * signature in the assertion before its validation + */ +public class LollipopSamlAssertionWrapper extends SamlAssertionWrapper { + + public LollipopSamlAssertionWrapper(Element element) throws WSSecurityException { + super(element); + } + + /** + * If the assertion has a signature call the signature validation, otherwise it throws {@link + * ErrorValidatingAssertionSignature} + * + * @see SamlAssertionWrapper#verifySignature(SAMLKeyInfo) + * @throws ErrorValidatingAssertionSignature if the assertion does not have a signature + */ + public void verifySignatureLollipop(SAMLKeyInfo samlKeyInfo) + throws WSSecurityException, ErrorValidatingAssertionSignature { + Signature sig = getSignature(); + if (sig == null) { + throw new ErrorValidatingAssertionSignature( + ErrorValidatingAssertionSignature.ErrorCode.MISSING_ASSERTION_SIGNATURE, + "The given assertion does not have a signature"); + } + verifySignature(samlKeyInfo); + } +} diff --git a/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopTypesafeConfig.java b/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopTypesafeConfig.java new file mode 100644 index 00000000..6a8869e1 --- /dev/null +++ b/core/src/main/java/it/pagopa/tech/lollipop/consumer/utils/LollipopTypesafeConfig.java @@ -0,0 +1,134 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.utils; + +import com.typesafe.config.Config; +import lombok.Getter; + +@Getter +public class LollipopTypesafeConfig { + + boolean ASSERTION_CLIENT_MOCK_ENABLED; + boolean IDP_CLIENT_MOCK_ENABLED; + + // Sample controller endpoint + String SAMPLE_LOLLIPOP_CONSUMER_ENDPOINT; + + // General Lollipop Configs Sample + int LOLLIPOP_ASSERTION_EXPIRE_IN_DAYS; + String LOLLIPOP_EXPECTED_LC_ORIGINAL_URL; + String LOLLIPOP_EXPECTED_LC_ORIGINAL_METHOD; + String LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT; + String LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT; + + // Idp Client Configs + String IDP_CLIENT_CIEID; + String IDP_CLIENT_BASE_URI; + String IDP_CLIENT_CIE_ENDPOINT; + String IDP_CLIENT_SPID_ENDPOINT; + + // Idp Storage Configs + boolean IDP_STORAGE_ENABLED; + int IDP_STORAGE_EVICTION_DELAY; + + // Assertion Client Configs + String ASSERTION_REST_URI; + String ASSERTION_REST_ENDPOINT; + + // Assertion Storage Configs + boolean ASSERTION_STORAGE_ENABLED; + int ASSERTION_STORAGE_EVICTION_DELAY; + + public LollipopTypesafeConfig(Config config) { + this.ASSERTION_CLIENT_MOCK_ENABLED = + getConfigBoolean(config, "lollipop.assertion.client.mock.enabled", false); + this.IDP_CLIENT_MOCK_ENABLED = + getConfigBoolean(config, "lollipop.assertion.client.mock.enabled", false); + + this.SAMPLE_LOLLIPOP_CONSUMER_ENDPOINT = + getConfigString( + config, + "sample.lollipop.consumer.config.endpoint", + "/api/v1/lollipop-consumer"); + + this.LOLLIPOP_ASSERTION_EXPIRE_IN_DAYS = + getConfigInt(config, "lollipop.core.config.assertionExpireInDays", 180); + this.LOLLIPOP_EXPECTED_LC_ORIGINAL_URL = + getConfigString( + config, + "lollipop.-core.config.expectedFirstLcOriginalUrl", + "https://api-app.io.pagopa.it/first-lollipop/sign"); + this.LOLLIPOP_EXPECTED_LC_ORIGINAL_METHOD = + getConfigString( + config, "lollipop.core.config.expectedFirstLcOriginalMethod", "POST"); + this.LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT = + getConfigString( + config, + "lollipop.core.config.assertionNotBeforeDateFormat", + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + this.LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT = + getConfigString( + config, + "lollipop.core.config.assertionInstantDateFormat", + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + + this.IDP_CLIENT_CIEID = + getConfigString( + config, + "lollipop.idp.rest.config.cieEntityId", + "https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO"); + this.IDP_CLIENT_BASE_URI = + getConfigString( + config, "lollipop.idp.rest.config.baseUri", "https://api.is.eng.pagopa.it"); + this.IDP_CLIENT_CIE_ENDPOINT = + getConfigString( + config, "lollipop.idp.rest.config.idpKeysCieEndpoint", "/idp-keys/cie"); + this.IDP_CLIENT_SPID_ENDPOINT = + getConfigString( + config, "lollipop.idp.rest.config.idpKeysSpidEndpoint", "/idp-keys/spid"); + + this.IDP_STORAGE_ENABLED = + getConfigBoolean( + config, "lollipop.idp.storage.config.idpCertDataStorageEnabled", true); + this.IDP_STORAGE_EVICTION_DELAY = + getConfigInt(config, "lollipop.idp.storage.config.storageEvictionDelay", 1); + + this.ASSERTION_REST_URI = + getConfigString( + config, "lollipop.assertion.rest.config.baseUri", "http://localhost:3000"); + this.ASSERTION_REST_ENDPOINT = + getConfigString( + config, + "lollipop.assertion.rest.config.assertionRequestEndpoint", + "/assertions"); + + this.ASSERTION_STORAGE_ENABLED = + getConfigBoolean( + config, "lollipop.assertion.rest.config.assertionStorageEnabled", true); + this.ASSERTION_STORAGE_EVICTION_DELAY = + getConfigInt(config, "lollipop.assertion.rest.config.storageEvictionDelay", 1); + } + + private String getConfigString(Config config, String path, String defaultString) { + if (config.hasPath(path)) { + return config.getString(path); + } else { + return defaultString; + } + } + + private boolean getConfigBoolean(Config config, String path, boolean defaultBoolean) { + if (config.hasPath(path)) { + return config.getBoolean(path); + } else { + return defaultBoolean; + } + } + + private int getConfigInt(Config config, String path, int defaultInt) { + if (config.hasPath(path)) { + return config.getInt(path); + } else { + return defaultInt; + } + } +} diff --git a/core/src/main/resources/it/pagopa/tech/lollipop/logging/logback/includes/defaults.xml b/core/src/main/resources/it/pagopa/tech/lollipop/logging/logback/includes/defaults.xml new file mode 100644 index 00000000..5e282db1 --- /dev/null +++ b/core/src/main/resources/it/pagopa/tech/lollipop/logging/logback/includes/defaults.xml @@ -0,0 +1,63 @@ + + + + + + + ${CONSOLE_LOG_THRESHOLD} + + + ${CONSOLE_LOG_PATTERN} + ${CONSOLE_LOG_CHARSET} + + + + + true + 20000 + 0 + + + + + + + + + + + ${FILE_LOG_THRESHOLD} + + + ${FILE_LOG_PATTERN} + ${FILE_LOG_CHARSET} + + ${LOG_FILE} + + ${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz} + ${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false} + ${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB} + ${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0} + ${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-7} + + + + + true + 20000 + 0 + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/main/resources/it/pagopa/tech/lollipop/logging/logback/logback.xml b/core/src/main/resources/it/pagopa/tech/lollipop/logging/logback/logback.xml new file mode 100644 index 00000000..e66db06f --- /dev/null +++ b/core/src/main/resources/it/pagopa/tech/lollipop/logging/logback/logback.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/TestUtils.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/TestUtils.java new file mode 100644 index 00000000..86161d6a --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/TestUtils.java @@ -0,0 +1,586 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer; + +public class TestUtils { + + public static final String VALID_FISCAL_CODE = "AAAAAA89S20I111X"; + public static final String VALID_JWK = + "{ \"kty\": \"EC\", \"crv\": \"P-256\", \"x\":" + + " \"SVqB4JcUD6lsfvqMr-OKUNUphdNn64Eay60978ZlL74\", \"y\":" + + " \"lf0u0pMj4lGAzZix5u4Cm5CMQIgMNpkwy163wtKYVKI\"}"; + public static final String VALID_SHA_256_ASSERTION_REF = + "sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg"; + public static final String VALID_SHA_384_ASSERTION_REF = + "sha384-lqxC_2kqMdwiBWoD-Us63Fha6e3bE1Y3yUz8G6IJTldohJCIBVDfvS8acB3GJBhw"; + public static final String VALID_SHA_512_ASSERTION_REF = + "sha512-nX5CfUc5R-FoYKYZwvQMuc4Tt-heb7vHi_O-AMUSqHNVCw9kNaN2SVuN-DXtGXyUhrcVcQdCyY6FVzl_vyWXNA"; + public static final String EMPTY_ASSERTION_XML = + ""; + public static final String ASSERTION_XML_WITH_INVALID_PERIOD_DATE_FORMAT = + "https://app-backend.io.italia.it"; + public static final String ASSERTION_XML_WITH_EXPIRED_PERIOD = + "https://app-backend.io.italia.it"; + public static final String ASSERTION_XML_WITHOUT_ATTRIBUTE_TAG = + "https://app-backend.io.italia.it"; + + public static final String ASSERTION_XML_WITHOUT_FISCAL_CODE = + " " + + " " + + " https://app-backend.io.italia.it " + + " \t " + + " " + + " "; + public static final String ASSERTION_XML_WITHOUT_SUBJECTCONFIRMATIONDATA_TAG = + " " + + " " + + " https://app-backend.io.italia.it " + + " \t " + + " TINIT-AAAAAA89S20I111X " + + " " + + " "; + public static final String ASSERTION_XML_WITH_INVALID_INRESPONSETO_ALGORITHM = + " \t\t\t" + + "\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA256_ALGORITHM = + " \t\t\t" + + "\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA384_ALGORITHM = + " \t\t\t" + + "\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA512_ALGORITHM = + " \t\t\t" + + "\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITHOUT_INSTANT_FIELD = + " \t" + + "\t\t\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITHOUT_ENTITY_ID_FIELD = + " \t\t\t" + + "\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITH_INVALID_INSTANT_FORMAT = + " \thttps://posteid.poste.it" + + "\t\t\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITH_VALID_ENTITY_ID_FIELD = + " \thttps://posteid.poste.it" + + "\t\t\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String ASSERTION_XML_WITH_INVALID_SIGNATURE = + " \thttps://posteid.poste.it\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " ViSjPfKj683dCuO7FdSzbQjw+vECYfoxgTeiVSgxr+I=\n" + + " \n" + + " \n" + + " " + + " O9lmrtHPudDz2fzzNH3DQxWy2rlXE56G54Siq7OPMYwps/cyo3wKo7+PwMJYNhhz1l57OYJ5e/MF\n" + + " " + + " ctVtYyl2rWo3QZOidWhg8WINIEqtFXIpk+ht5i2t3P1132/iL/gnY+fgemhnbOV/otEspHA4Wsio\n" + + " " + + " I8xWjekAFlHBTOTtO9vzzqTtf+yalf+6pZmRLtOYrMMV4W3QZ2oLr7C2vTgcl5eVXJyGf0U8Y2bf\n" + + " " + + " 7OPRHJNnVs4S8ztWQEwqZLFA1SvyCx1Nx6f+xd9lT7Lo1h81MRMdvRTk3rAaWYaqAmU9mxVnzsw4\n" + + " xaLjxR4rE2drY3eb+O8uHZbzFlOhPtaINRPILg==\n" + + " \n" + + " \n" + + " " + + " MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc\n" + + " " + + " BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx\n" + + " " + + " GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz\n" + + " " + + " MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE\n" + + " " + + " AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC\n" + + " " + + " aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd\n" + + " " + + " lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE\n" + + " " + + " w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4\n" + + " " + + " fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR\n" + + " " + + " qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH\n" + + " " + + " MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw\n" + + " " + + " Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1\n" + + " " + + " MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD\n" + + " " + + " VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G\n" + + " " + + " A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc\n" + + " " + + " aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO\n" + + " " + + " aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ\n" + + " " + + " bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO\n" + + " " + + " 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz\n" + + " " + + " NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD\n" + + " " + + " DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0\n" + + " " + + " uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH\n" + + " " + + " V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy\n" + + " " + + " VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC\n" + + " " + + " TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc=\n" + + " \n" + + " \n" + + " \t\t\t\tSPID-d4de186b-e103-4b39-8209-0bccc7b1acdd" + + "\t\t\t\t\t\t" + + "\t\t\t" + + "\t\t\t\t\t\t\t\t\t\t" + + "\t\thttps://app-backend.io.italia.it\t\t" + + "\t\t\t\t" + + "\t\t\t\t\t\t\t\tTINIT-AAAAAA89S20I111X\t\t" + + "\t\t\t" + + "\t"; + public static final String VALID_IDP_CERTIFICATE = + "MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ"; + public static final String VALID_ASSERTION_XML = + "\n" + + " https://spid-testenv2:8088s/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " https://spid-testenv2:80884cqgG29TSKgNLy2/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU//hwnefFYe9ghDPy3rDbcNl3JetT07NR/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " https://spid.agid.gov.it/cd\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " https://www.spid.gov.it/SpidL2\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " info@agid.gov.it\n" + + " \n" + + " \n" + + " Mario\n" + + " \n" + + " \n" + + " Bianchi\n" + + " \n" + + " \n" + + " GDNNWA12H81Y874F\n" + + " \n" + + " \n" + + " 1991-12-12\n" + + " \n" + + " \n" + + " \n" + + ""; + + private TestUtils() {} +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceImplTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceImplTest.java new file mode 100644 index 00000000..e3791a9f --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/impl/AssertionServiceImplTest.java @@ -0,0 +1,132 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.impl; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionService; +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClient; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorage; +import it.pagopa.tech.lollipop.consumer.exception.LollipopAssertionNotFoundException; +import it.pagopa.tech.lollipop.consumer.exception.OidcAssertionNotSupported; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class AssertionServiceImplTest { + + private static AssertionStorage assertionStorageMock; + private static AssertionClient assertionClientMock; + private static AssertionService sut; + + private static final String JWT = "jwt"; + private static final String ASSERTION_REF = "assertionRef"; + + @BeforeEach + void setUp() { + assertionClientMock = mock(AssertionClient.class); + assertionStorageMock = mock(AssertionStorage.class); + sut = new AssertionServiceImpl(assertionStorageMock, assertionClientMock); + } + + @Test + void getAssertionFromStorageWithSuccess() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + SamlAssertion assertion = new SamlAssertion(); + doReturn(assertion).when(assertionStorageMock).getAssertion(ASSERTION_REF); + + sut.getAssertion(JWT, ASSERTION_REF); + + verify(assertionStorageMock).getAssertion(ASSERTION_REF); + verify(assertionClientMock, never()).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock, never()).saveAssertion(ASSERTION_REF, assertion); + } + + @Test + void getAssertionFromClientWithSuccess() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + SamlAssertion assertion = new SamlAssertion(); + doReturn(null).when(assertionStorageMock).getAssertion(ASSERTION_REF); + doReturn(assertion).when(assertionClientMock).getAssertion(JWT, ASSERTION_REF); + + sut.getAssertion(JWT, ASSERTION_REF); + + verify(assertionStorageMock).getAssertion(ASSERTION_REF); + verify(assertionClientMock).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock).saveAssertion(ASSERTION_REF, assertion); + } + + @Test + void getUnsupportedAssertionFromClient() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + doReturn(null).when(assertionStorageMock).getAssertion(ASSERTION_REF); + doReturn(null).when(assertionClientMock).getAssertion(JWT, ASSERTION_REF); + + sut.getAssertion(JWT, ASSERTION_REF); + + verify(assertionStorageMock).getAssertion(ASSERTION_REF); + verify(assertionClientMock).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock, never()).saveAssertion(anyString(), any()); + } + + @Test + void getAssertionFromClientWithLollipopAssertionNotFoundException() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + doReturn(null).when(assertionStorageMock).getAssertion(ASSERTION_REF); + doThrow(LollipopAssertionNotFoundException.class) + .when(assertionClientMock) + .getAssertion(JWT, ASSERTION_REF); + + Assertions.assertThrows( + LollipopAssertionNotFoundException.class, + () -> sut.getAssertion(JWT, ASSERTION_REF)); + + verify(assertionStorageMock).getAssertion(ASSERTION_REF); + verify(assertionClientMock).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock, never()).saveAssertion(anyString(), any(SamlAssertion.class)); + } + + @Test + void getAssertionWithEmptyJwtParameterFailure() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + Assertions.assertThrows( + IllegalArgumentException.class, () -> sut.getAssertion("", ASSERTION_REF)); + + verify(assertionStorageMock, never()).getAssertion(anyString()); + verify(assertionClientMock, never()).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock, never()).saveAssertion(anyString(), any(SamlAssertion.class)); + } + + @Test + void getAssertionWithNullJwtParameterFailure() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + Assertions.assertThrows( + IllegalArgumentException.class, () -> sut.getAssertion(null, ASSERTION_REF)); + + verify(assertionStorageMock, never()).getAssertion(anyString()); + verify(assertionClientMock, never()).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock, never()).saveAssertion(anyString(), any(SamlAssertion.class)); + } + + @Test + void getAssertionWithEmptyAssertionRefParameterFailure() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + Assertions.assertThrows(IllegalArgumentException.class, () -> sut.getAssertion(JWT, "")); + + verify(assertionStorageMock, never()).getAssertion(ASSERTION_REF); + verify(assertionClientMock, never()).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock, never()).saveAssertion(anyString(), any(SamlAssertion.class)); + } + + @Test + void getAssertionWithNullAssertionRefParameterFailure() + throws LollipopAssertionNotFoundException, OidcAssertionNotSupported { + Assertions.assertThrows(IllegalArgumentException.class, () -> sut.getAssertion(JWT, null)); + + verify(assertionStorageMock, never()).getAssertion(ASSERTION_REF); + verify(assertionClientMock, never()).getAssertion(JWT, ASSERTION_REF); + verify(assertionStorageMock, never()).saveAssertion(anyString(), any(SamlAssertion.class)); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorageTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorageTest.java new file mode 100644 index 00000000..819971bb --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/assertion/storage/SimpleAssertionStorageTest.java @@ -0,0 +1,189 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.assertion.storage; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import it.pagopa.tech.lollipop.consumer.model.DelayedCacheObject; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import java.lang.ref.SoftReference; +import java.util.concurrent.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SimpleAssertionStorageTest { + + private static final long EVICTION_DELAY = 5000L; + private static StorageConfig storageConfigMock; + private SimpleAssertionStorage sut; + private static final String ASSERTION_REF_1 = "assertionRef1"; + + @BeforeEach + void setUp() { + storageConfigMock = mock(StorageConfig.class); + doReturn(EVICTION_DELAY).when(storageConfigMock).getStorageEvictionDelay(); + doReturn(TimeUnit.MILLISECONDS).when(storageConfigMock).getStorageEvictionDelayTimeUnit(); + } + + @Test + void getExistingAssertionAndResetScheduleEvictionWithStorageEnabled() + throws InterruptedException, ExecutionException { + doReturn(true).when(storageConfigMock).isAssertionStorageEnabled(); + doReturn(1000L).when(storageConfigMock).getStorageEvictionDelay(); + doReturn(TimeUnit.MILLISECONDS).when(storageConfigMock).getStorageEvictionDelayTimeUnit(); + doReturn(100L).when(storageConfigMock).getMaxNumberOfElements(); + + ConcurrentHashMap> assertionMap = + new ConcurrentHashMap<>(); + DelayQueue> delayedCacheObjects = new DelayQueue<>(); + + sut = new SimpleAssertionStorage(assertionMap, delayedCacheObjects, storageConfigMock); + SamlAssertion samlAssertion = new SamlAssertion(); + + sut.saveAssertion(ASSERTION_REF_1, samlAssertion); + delayedCacheObjects.poll(20, TimeUnit.MILLISECONDS); + + SamlAssertion result = sut.getAssertion(ASSERTION_REF_1); + + assertNotNull(result); + assertEquals(samlAssertion, result); + delayedCacheObjects.poll(20, TimeUnit.MILLISECONDS); + assertEquals(1, delayedCacheObjects.size()); + + delayedCacheObjects.poll(1000, TimeUnit.MILLISECONDS); + assertEquals(0, assertionMap.size()); + assertEquals(0, delayedCacheObjects.size()); + + sut.close(); + } + + @Test + void getNotExistingAssertionWithStorageEnabled() { + doReturn(true).when(storageConfigMock).isAssertionStorageEnabled(); + + sut = new SimpleAssertionStorage(storageConfigMock); + + SamlAssertion result = sut.getAssertion(ASSERTION_REF_1); + + assertNull(result); + + sut.close(); + } + + @Test + void saveAssertionAndScheduleEvictionWithStorageEnabled() + throws InterruptedException, ExecutionException { + doReturn(true).when(storageConfigMock).isAssertionStorageEnabled(); + doReturn(1000L).when(storageConfigMock).getStorageEvictionDelay(); + doReturn(TimeUnit.MILLISECONDS).when(storageConfigMock).getStorageEvictionDelayTimeUnit(); + doReturn(100L).when(storageConfigMock).getMaxNumberOfElements(); + + ConcurrentHashMap> assertionMap = + new ConcurrentHashMap<>(); + DelayQueue> delayedCacheObjects = new DelayQueue<>(); + + sut = new SimpleAssertionStorage(assertionMap, delayedCacheObjects, storageConfigMock); + SamlAssertion samlAssertion = new SamlAssertion(); + + sut.saveAssertion(ASSERTION_REF_1, samlAssertion); + delayedCacheObjects.poll(100, TimeUnit.MILLISECONDS); + + assertEquals(1, assertionMap.size()); + assertEquals(1, delayedCacheObjects.size()); + assertEquals(samlAssertion, assertionMap.get(ASSERTION_REF_1).get()); + + delayedCacheObjects.poll(1100, TimeUnit.MILLISECONDS); + assertEquals(0, assertionMap.size()); + assertEquals(0, delayedCacheObjects.size()); + + sut.close(); + } + + @Test + void saveAssertionToMaximumCapacityWithStorageEnabled() throws InterruptedException { + doReturn(true).when(storageConfigMock).isAssertionStorageEnabled(); + doReturn(1000L).when(storageConfigMock).getStorageEvictionDelay(); + doReturn(TimeUnit.MILLISECONDS).when(storageConfigMock).getStorageEvictionDelayTimeUnit(); + doReturn(100L).when(storageConfigMock).getMaxNumberOfElements(); + + ConcurrentHashMap> assertionMap = + new ConcurrentHashMap<>(); + DelayQueue> delayedCacheObjects = new DelayQueue<>(); + + sut = new SimpleAssertionStorage(assertionMap, delayedCacheObjects, storageConfigMock); + SamlAssertion samlAssertion; + + for (int i = 0; i < 120; i++) { + samlAssertion = new SamlAssertion(); + samlAssertion.setAssertionRef(ASSERTION_REF_1 + i); + sut.saveAssertion(ASSERTION_REF_1 + i, samlAssertion); + } + delayedCacheObjects.poll(200, TimeUnit.MILLISECONDS); + + assertEquals(100, delayedCacheObjects.size()); + + sut.close(); + } + + @Test + void getAssertionWithStorageDisabled() { + doReturn(false).when(storageConfigMock).isAssertionStorageEnabled(); + doReturn(1000L).when(storageConfigMock).getStorageEvictionDelay(); + doReturn(TimeUnit.MILLISECONDS).when(storageConfigMock).getStorageEvictionDelayTimeUnit(); + + sut = new SimpleAssertionStorage(storageConfigMock); + + SamlAssertion result = sut.getAssertion(ASSERTION_REF_1); + + assertNull(result); + + sut.close(); + } + + @Test + void savaAssertionWithStorageDisabled() { + doReturn(false).when(storageConfigMock).isAssertionStorageEnabled(); + + ConcurrentHashMap> assertionMap = + new ConcurrentHashMap<>(); + DelayQueue> delayedCacheObjects = new DelayQueue<>(); + + sut = new SimpleAssertionStorage(assertionMap, delayedCacheObjects, storageConfigMock); + + sut.saveAssertion(ASSERTION_REF_1, new SamlAssertion()); + + assertEquals(0, assertionMap.size()); + assertEquals(0, delayedCacheObjects.size()); + + sut.close(); + } + + @Test + void removedCacheObjectIfNotNull() throws InterruptedException { + doReturn(true).when(storageConfigMock).isAssertionStorageEnabled(); + doReturn(10000L).when(storageConfigMock).getStorageEvictionDelay(); + doReturn(TimeUnit.MILLISECONDS).when(storageConfigMock).getStorageEvictionDelayTimeUnit(); + doReturn(100L).when(storageConfigMock).getMaxNumberOfElements(); + + ConcurrentHashMap> assertionMap = + new ConcurrentHashMap<>(); + DelayQueue> delayedCacheObjects = new DelayQueue<>(); + + sut = new SimpleAssertionStorage(assertionMap, delayedCacheObjects, storageConfigMock); + SamlAssertion samlAssertion = new SamlAssertion(); + + sut.saveAssertion(ASSERTION_REF_1, samlAssertion); + delayedCacheObjects.poll(100, TimeUnit.MILLISECONDS); + + assertEquals(1, assertionMap.size()); + assertEquals(1, delayedCacheObjects.size()); + + sut.removeDelayedObject(); + + assertEquals(0, assertionMap.size()); + assertEquals(0, delayedCacheObjects.size()); + + sut.close(); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandBuilderImplTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandBuilderImplTest.java new file mode 100644 index 00000000..d3e60706 --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandBuilderImplTest.java @@ -0,0 +1,34 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.command.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommand; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.helper.LollipopConsumerFactoryHelper; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class LollipopConsumerCommandBuilderImplTest { + + LollipopConsumerCommandBuilder lollipopConsumerCommandBuilder; + + @BeforeEach + void beforeAll() { + LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper = + Mockito.mock(LollipopConsumerFactoryHelper.class); + lollipopConsumerCommandBuilder = + new LollipopConsumerCommandBuilderImpl(lollipopConsumerFactoryHelper); + } + + @Test + void testThatCreatsCommand() { + assertThat( + lollipopConsumerCommandBuilder.createCommand( + Mockito.mock(LollipopConsumerRequest.class))) + .isInstanceOf(LollipopConsumerCommand.class); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandImplTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandImplTest.java new file mode 100644 index 00000000..6cb09b54 --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/command/impl/LollipopConsumerCommandImplTest.java @@ -0,0 +1,382 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.command.impl; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommand; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.enumeration.AssertionVerificationResultCode; +import it.pagopa.tech.lollipop.consumer.enumeration.HttpMessageVerificationResultCode; +import it.pagopa.tech.lollipop.consumer.exception.*; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerService; +import it.pagopa.tech.lollipop.consumer.model.CommandResult; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.service.AssertionVerifierService; +import it.pagopa.tech.lollipop.consumer.service.HttpMessageVerifierService; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import java.io.UnsupportedEncodingException; +import lombok.SneakyThrows; +import org.junit.jupiter.api.*; +import org.mockito.*; + +class LollipopConsumerCommandImplTest { + + private static HttpMessageVerifierService messageVerifierServiceMock; + private static AssertionVerifierService assertionVerifierServiceMock; + private LollipopConsumerRequestValidationService requestValidationServiceMock; + private static LollipopConsumerCommand sut; + + @BeforeEach + void beforeAll() { + messageVerifierServiceMock = Mockito.mock(HttpMessageVerifierService.class); + assertionVerifierServiceMock = Mockito.mock(AssertionVerifierService.class); + requestValidationServiceMock = Mockito.mock(LollipopConsumerRequestValidationService.class); + sut = + Mockito.spy( + new LollipopConsumerCommandImpl( + LollipopConsumerRequestConfig.builder().build(), + messageVerifierServiceMock, + assertionVerifierServiceMock, + requestValidationServiceMock, + new LollipopLogbackLoggerService(), + LollipopConsumerRequest.builder().build())); + } + + @Test + void failedHttpMessageValidationThrowDigestException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doThrow( + new LollipopDigestException( + LollipopDigestException.ErrorCode.INCORRECT_DIGEST, "error")) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + HttpMessageVerificationResultCode.DIGEST_VALIDATION_ERROR.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock, never()) + .validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedHttpMessageValidationThrowSignatureException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + LollipopSignatureException, LollipopRequestContentValidationException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doThrow( + new LollipopSignatureException( + LollipopSignatureException.ErrorCode.INVALID_SIGNATURE, "error")) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + HttpMessageVerificationResultCode.SIGNATURE_VALIDATION_ERROR.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock, never()) + .validateLollipop(any(LollipopConsumerRequest.class)); + } + + @SneakyThrows + @Test + void failedHttpMessageValidationThrowUnsupportedEncodingException() { + + doThrow(UnsupportedEncodingException.class) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + HttpMessageVerificationResultCode.UNSUPPORTED_ENCODING.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock, never()) + .validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedHttpMessageValidationWithoutThrowingException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(false) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + HttpMessageVerificationResultCode.HTTP_MESSAGE_VALIDATION_FAILED.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock, never()) + .validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedAssertionValidationWithoutThrowingException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doReturn(false) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + AssertionVerificationResultCode.ASSERTION_VERIFICATION_FAILED.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void successLollipopRequestValidation() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doReturn(true) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals("SUCCESS", commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedLollipopRequestValidation() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doThrow(LollipopRequestContentValidationException.class) + .when(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals("REQUEST PARAMS VALIDATION FAILED", commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock, never()) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock, never()) + .validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedAssertionValidationThrowErrorRetrievingAssertionException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doThrow(ErrorRetrievingAssertionException.class) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + AssertionVerificationResultCode.ERROR_RETRIEVING_ASSERTION.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedAssertionValidationThrowAssertionPeriodException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doThrow(AssertionPeriodException.class) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + AssertionVerificationResultCode.PERIOD_VALIDATION_ERROR.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedAssertionValidationThrowAssertionThumbprintException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doThrow(AssertionThumbprintException.class) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + AssertionVerificationResultCode.THUMBPRINT_VALIDATION_ERROR.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedAssertionValidationThrowAssertionUserIdException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doThrow(AssertionUserIdException.class) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + AssertionVerificationResultCode.USER_ID_VALIDATION_ERROR.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedAssertionValidationThrowErrorValidatingAssertionSignature() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doThrow(ErrorValidatingAssertionSignature.class) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + AssertionVerificationResultCode.SIGNATURE_VALIDATION_ERROR.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } + + @Test + void failedAssertionValidationThrowErrorRetrievingIdpCertDataException() + throws LollipopDigestException, UnsupportedEncodingException, LollipopVerifierException, + AssertionPeriodException, AssertionThumbprintException, + AssertionUserIdException, ErrorRetrievingAssertionException, + LollipopRequestContentValidationException, LollipopSignatureException, + ErrorValidatingAssertionSignature, ErrorRetrievingIdpCertDataException { + + doReturn(true) + .when(messageVerifierServiceMock) + .verifyHttpMessage(any(LollipopConsumerRequest.class)); + doThrow(ErrorRetrievingIdpCertDataException.class) + .when(assertionVerifierServiceMock) + .validateLollipop(any(LollipopConsumerRequest.class)); + + CommandResult commandResult = sut.doExecute(); + + Assertions.assertEquals( + AssertionVerificationResultCode.IDP_CERT_DATA_RETRIEVING_ERROR.name(), + commandResult.getResultCode()); + + verify(requestValidationServiceMock) + .validateLollipopRequest(any(LollipopConsumerRequest.class)); + verify(messageVerifierServiceMock).verifyHttpMessage(any(LollipopConsumerRequest.class)); + verify(assertionVerifierServiceMock).validateLollipop(any(LollipopConsumerRequest.class)); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/helper/LollipopConsumerFactoryHelperTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/helper/LollipopConsumerFactoryHelperTest.java new file mode 100644 index 00000000..1380c329 --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/helper/LollipopConsumerFactoryHelperTest.java @@ -0,0 +1,68 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.helper; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.*; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.service.AssertionVerifierService; +import it.pagopa.tech.lollipop.consumer.service.HttpMessageVerifierService; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class LollipopConsumerFactoryHelperTest { + + IdpCertProviderFactory idpCertProviderFactory; + HttpMessageVerifierFactory httpMessageVerifierFactory; + LollipopLoggerServiceFactory lollipopLoggerServiceFactory; + + AssertionServiceFactory assertionServiceFactory; + + LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper; + + LollipopConsumerRequestValidationService lollipopConsumerRequestValidationService; + + public LollipopConsumerFactoryHelperTest() { + idpCertProviderFactory = Mockito.mock(IdpCertProviderFactory.class); + lollipopLoggerServiceFactory = Mockito.mock(LollipopLoggerServiceFactory.class); + httpMessageVerifierFactory = Mockito.mock(HttpMessageVerifierFactory.class); + assertionServiceFactory = Mockito.mock(AssertionServiceFactory.class); + lollipopConsumerRequestValidationService = + Mockito.mock(LollipopConsumerRequestValidationService.class); + } + + @BeforeEach + public void init() { + Mockito.reset( + idpCertProviderFactory, + lollipopLoggerServiceFactory, + httpMessageVerifierFactory, + assertionServiceFactory); + lollipopConsumerFactoryHelper = + new LollipopConsumerFactoryHelper( + lollipopLoggerServiceFactory, + httpMessageVerifierFactory, + idpCertProviderFactory, + assertionServiceFactory, + lollipopConsumerRequestValidationService, + LollipopConsumerRequestConfig.builder().build()); + } + + @Test + void testThatRetunsAssertionVerifierService() { + assertThat(lollipopConsumerFactoryHelper.getAssertionVerifierService()) + .isInstanceOf(AssertionVerifierService.class); + } + + @Test + void testThatRetunsHttpMessageVerifierService() { + assertThat(lollipopConsumerFactoryHelper.getHttpMessageVerifierService()) + .isInstanceOf(HttpMessageVerifierService.class); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderImplTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderImplTest.java new file mode 100644 index 00000000..0be372db --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/idp/impl/IdpCertProviderImplTest.java @@ -0,0 +1,66 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.impl; + +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.IdpCertClient; +import java.nio.charset.StandardCharsets; +import java.util.Random; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class IdpCertProviderImplTest { + + private IdpCertClient idpCertClientMock; + private IdpCertProvider sut; + + private final Random random = new Random(); + + @BeforeEach + void setUp() { + idpCertClientMock = Mockito.mock(IdpCertClient.class); + sut = new IdpCertProviderImpl(idpCertClientMock); + } + + @Test + void getIdpCertDataSuccess() { + String randomString1 = generateRandomString(); + String randomString2 = generateRandomString(); + Assertions.assertDoesNotThrow(() -> sut.getIdpCertData(randomString1, randomString2)); + } + + @Test + void getIdpCertDataErrorInvalidInstantNull() { + String randomString = generateRandomString(); + Assertions.assertThrows( + IllegalArgumentException.class, () -> sut.getIdpCertData(null, randomString)); + } + + @Test + void getIdpCertDataErrorInvalidInstantEmpty() { + String randomString = generateRandomString(); + Assertions.assertThrows( + IllegalArgumentException.class, () -> sut.getIdpCertData("", randomString)); + } + + @Test + void getIdpCertDataErrorInvalidEntityIdNull() { + String randomString = generateRandomString(); + Assertions.assertThrows( + IllegalArgumentException.class, () -> sut.getIdpCertData(randomString, null)); + } + + @Test + void getIdpCertDataErrorInvalidEntityIdEmpty() { + String randomString = generateRandomString(); + Assertions.assertThrows( + IllegalArgumentException.class, () -> sut.getIdpCertData(randomString, "")); + } + + private String generateRandomString() { + byte[] array = new byte[7]; + random.nextBytes(array); + return new String(array, StandardCharsets.UTF_8); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerServiceFactoryTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerServiceFactoryTest.java new file mode 100644 index 00000000..b19fa71f --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/logger/impl/LollipopLogbackLoggerServiceFactoryTest.java @@ -0,0 +1,17 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.logger.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import lombok.SneakyThrows; +import org.junit.jupiter.api.Test; + +class LollipopLogbackLoggerServiceFactoryTest { + + @SneakyThrows + @Test + void instanceIsCreated() { + assertThat(new LollipopLogbackLoggerServiceFactory().create()) + .isInstanceOf(LollipopLogbackLoggerService.class); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/AssertionVerifierServiceImplTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/AssertionVerifierServiceImplTest.java new file mode 100644 index 00000000..3e681c7d --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/AssertionVerifierServiceImplTest.java @@ -0,0 +1,709 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service.impl; + +import static it.pagopa.tech.lollipop.consumer.TestUtils.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionService; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.exception.*; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProvider; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerService; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import java.text.ParseException; +import java.util.Collections; +import java.util.HashMap; +import lombok.SneakyThrows; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Document; + +class AssertionVerifierServiceImplTest { + + private IdpCertProvider idpCertProviderMock; + private AssertionService assertionServiceMock; + private static LollipopConsumerRequestConfig lollipopRequestConfigMock; + + private AssertionVerifierServiceImpl sut; + + @BeforeEach + void setUp() { + idpCertProviderMock = mock(IdpCertProvider.class); + assertionServiceMock = mock(AssertionService.class); + lollipopRequestConfigMock = spy(LollipopConsumerRequestConfig.builder().build()); + + sut = + spy( + new AssertionVerifierServiceImpl( + new LollipopLogbackLoggerService(), + idpCertProviderMock, + assertionServiceMock, + lollipopRequestConfigMock)); + } + + @SneakyThrows + @Test + void validateLollipopGetAssertionFailureWithOidcAssertionException() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + doThrow(OidcAssertionNotSupported.class) + .when(assertionServiceMock) + .getAssertion(anyString(), anyString()); + + ErrorRetrievingAssertionException e = + assertThrows( + ErrorRetrievingAssertionException.class, + () -> sut.validateLollipop(request)); + + Assertions.assertTrue(e.getCause() instanceof OidcAssertionNotSupported); + Assertions.assertEquals( + ErrorRetrievingAssertionException.ErrorCode.OIDC_TYPE_NOT_SUPPORTED, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopGetAssertionFailureWithAssertionNotFoundException() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + doThrow(LollipopAssertionNotFoundException.class) + .when(assertionServiceMock) + .getAssertion(anyString(), anyString()); + + ErrorRetrievingAssertionException e = + assertThrows( + ErrorRetrievingAssertionException.class, + () -> sut.validateLollipop(request)); + + Assertions.assertTrue(e.getCause() instanceof LollipopAssertionNotFoundException); + Assertions.assertEquals( + ErrorRetrievingAssertionException.ErrorCode.SAML_ASSERTION_NOT_FOUND, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopGetAssertionFailureForBuildAssertionDocError() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(""); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + + ErrorRetrievingAssertionException e = + assertThrows( + ErrorRetrievingAssertionException.class, + () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + ErrorRetrievingAssertionException.ErrorCode.ERROR_PARSING_ASSERTION, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopGetAssertionSuccess() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(EMPTY_ASSERTION_XML); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.emptyList()).when(sut).getIdpCertData(any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void validateLollipopValidatePeriodFailureWithFieldNotFoundInAssertionDoc() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(EMPTY_ASSERTION_XML); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + + AssertionPeriodException e = + assertThrows(AssertionPeriodException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionPeriodException.ErrorCode.INVALID_ASSERTION_PERIOD, e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidatePeriodFailureWithInvalidDateFormat() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_INVALID_PERIOD_DATE_FORMAT); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + + AssertionPeriodException e = + assertThrows(AssertionPeriodException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionPeriodException.ErrorCode.ERROR_PARSING_ASSERTION_NOT_BEFORE_DATE, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidatePeriodFailureWithExpiredAssertion() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_EXPIRED_PERIOD); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + + AssertionPeriodException e = + assertThrows(AssertionPeriodException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionPeriodException.ErrorCode.INVALID_ASSERTION_PERIOD, e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidatePeriodSuccess() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(VALID_ASSERTION_XML); + + doReturn(365 * 20).when(lollipopRequestConfigMock).getAssertionExpireInDays(); + doReturn("yyyy-MM-dd'T'HH:mm:ss'Z'") + .when(lollipopRequestConfigMock) + .getAssertionNotBeforeDateFormat(); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.emptyList()).when(sut).getIdpCertData(any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void validateLollipopValidateUserIdFailureWithoutAttributeTagInAssertionDoc() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITHOUT_ATTRIBUTE_TAG); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + + AssertionUserIdException e = + assertThrows(AssertionUserIdException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionUserIdException.ErrorCode.FISCAL_CODE_FIELD_NOT_FOUND, e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateUserIdFailureWithoutFiscalCodeInAssertionDoc() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITHOUT_FISCAL_CODE); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + + AssertionUserIdException e = + assertThrows(AssertionUserIdException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionUserIdException.ErrorCode.FISCAL_CODE_FIELD_NOT_FOUND, e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateUserIdFailureWithInvalidUserIdHeader() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITHOUT_SUBJECTCONFIRMATIONDATA_TAG); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + + AssertionUserIdException e = + assertThrows(AssertionUserIdException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionUserIdException.ErrorCode.INVALID_USER_ID, e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateUserIdSuccess() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", VALID_FISCAL_CODE); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITHOUT_SUBJECTCONFIRMATIONDATA_TAG); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.emptyList()).when(sut).getIdpCertData(any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void + validateLollipopValidateThumbprintFailureWithoutSubjectConfirmationDataTagInAssertionDoc() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITHOUT_SUBJECTCONFIRMATIONDATA_TAG); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + + AssertionThumbprintException e = + assertThrows( + AssertionThumbprintException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionThumbprintException.ErrorCode.IN_RESPONSE_TO_FIELD_NOT_FOUND, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateThumbprintFailureWithInvalidInResponseToAlgorithm() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_INVALID_INRESPONSETO_ALGORITHM); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + + AssertionThumbprintException e = + assertThrows( + AssertionThumbprintException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionThumbprintException.ErrorCode.IN_RESPONSE_TO_ALGORITHM_NOT_VALID, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateThumbprintFailureWithErrorCalculatingThumbprint() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA256_ALGORITHM); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + + AssertionThumbprintException e = + assertThrows( + AssertionThumbprintException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionThumbprintException.ErrorCode.ERROR_CALCULATING_ASSERTION_THUMBPRINT, + e.getErrorCode()); + Assertions.assertTrue(e.getCause() instanceof ParseException); + } + + @SneakyThrows + @Test + void + validateLollipopValidateThumbprintFailureWithDifferentAssertionRefAndCalculatedThumbprint() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", VALID_JWK, ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA256_ALGORITHM); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + + AssertionThumbprintException e = + assertThrows( + AssertionThumbprintException.class, () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + AssertionThumbprintException.ErrorCode.INVALID_IN_RESPONSE_TO, e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateThumbprintSHA256Success() { + LollipopConsumerRequest request = + getLollipopConsumerRequest(VALID_SHA_256_ASSERTION_REF, VALID_JWK, ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA256_ALGORITHM); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.emptyList()).when(sut).getIdpCertData(any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void validateLollipopValidateThumbprintSHA384Success() { + LollipopConsumerRequest request = + getLollipopConsumerRequest(VALID_SHA_384_ASSERTION_REF, VALID_JWK, ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA384_ALGORITHM); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.emptyList()).when(sut).getIdpCertData(any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void validateLollipopValidateThumbprintSHA512Success() { + LollipopConsumerRequest request = + getLollipopConsumerRequest(VALID_SHA_512_ASSERTION_REF, VALID_JWK, ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA512_ALGORITHM); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.emptyList()).when(sut).getIdpCertData(any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void validateLollipopGetIdpCertDataFailureForMissingInstantField() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITHOUT_INSTANT_FIELD); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + + ErrorRetrievingIdpCertDataException e = + assertThrows( + ErrorRetrievingIdpCertDataException.class, + () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + ErrorRetrievingIdpCertDataException.ErrorCode.INSTANT_FIELD_NOT_FOUND, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopGetIdpCertDataFailureForMissingEntityIdField() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITHOUT_ENTITY_ID_FIELD); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + + ErrorRetrievingIdpCertDataException e = + assertThrows( + ErrorRetrievingIdpCertDataException.class, + () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + ErrorRetrievingIdpCertDataException.ErrorCode.ENTITY_ID_FIELD_NOT_FOUND, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopGetIdpCertDataFailureForIdpCertDataNotFoundException() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_ENTITY_ID_FIELD); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doThrow(CertDataNotFoundException.class) + .when(idpCertProviderMock) + .getIdpCertData(anyString(), anyString()); + + ErrorRetrievingIdpCertDataException e = + assertThrows( + ErrorRetrievingIdpCertDataException.class, + () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + ErrorRetrievingIdpCertDataException.ErrorCode.IDP_CERT_DATA_NOT_FOUND, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopGetIdpCertDataSuccessWithWarningForInvalidInstantDateFormat() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_INVALID_INSTANT_FORMAT); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void validateLollipopGetIdpCertDataSuccess() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_ENTITY_ID_FIELD); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true).when(sut).validateSignature(any(Document.class), anyList()); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + @SneakyThrows + @Test + void validateLollipopValidateSignatureFailureForErrorUnmarshalAssertion() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(EMPTY_ASSERTION_XML); + + IdpCertData idpCertData = new IdpCertData(); + idpCertData.setCertData(Collections.singletonList(VALID_IDP_CERTIFICATE)); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.singletonList(idpCertData)) + .when(sut) + .getIdpCertData(any(Document.class)); + + ErrorValidatingAssertionSignature e = + assertThrows( + ErrorValidatingAssertionSignature.class, + () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + ErrorValidatingAssertionSignature.ErrorCode.ERROR_PARSING_ASSERTION, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateSignatureFailureForMissingSignature() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_VALID_INRESPONSETO_SHA256_ALGORITHM); + + IdpCertData idpCertData = new IdpCertData(); + idpCertData.setCertData(Collections.singletonList(VALID_IDP_CERTIFICATE)); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.singletonList(idpCertData)) + .when(sut) + .getIdpCertData(any(Document.class)); + + ErrorValidatingAssertionSignature e = + assertThrows( + ErrorValidatingAssertionSignature.class, + () -> sut.validateLollipop(request)); + + Assertions.assertEquals( + ErrorValidatingAssertionSignature.ErrorCode.MISSING_ASSERTION_SIGNATURE, + e.getErrorCode()); + } + + @SneakyThrows + @Test + void validateLollipopValidateSignatureFailureForSignatureNotValid() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(ASSERTION_XML_WITH_INVALID_SIGNATURE); + + IdpCertData idpCertData = new IdpCertData(); + idpCertData.setCertData(Collections.singletonList(VALID_IDP_CERTIFICATE)); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.singletonList(idpCertData)) + .when(sut) + .getIdpCertData(any(Document.class)); + + boolean result = sut.validateLollipop(request); + + assertFalse(result); + } + + @SneakyThrows + @Test + void validateLollipopSuccess() { + LollipopConsumerRequest request = getLollipopConsumerRequest("", "", ""); + + SamlAssertion assertion = new SamlAssertion(); + assertion.setAssertionData(VALID_ASSERTION_XML); + + IdpCertData idpCertData = new IdpCertData(); + idpCertData.setCertData(Collections.singletonList(VALID_IDP_CERTIFICATE)); + + doReturn(assertion).when(assertionServiceMock).getAssertion(anyString(), anyString()); + doReturn(true).when(sut).validateAssertionPeriod(any(Document.class)); + doReturn(true) + .when(sut) + .validateUserId(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(true) + .when(sut) + .validateInResponseTo(any(LollipopConsumerRequest.class), any(Document.class)); + doReturn(Collections.singletonList(idpCertData)) + .when(sut) + .getIdpCertData(any(Document.class)); + + boolean result = sut.validateLollipop(request); + + assertTrue(result); + } + + private LollipopConsumerRequest getLollipopConsumerRequest( + String assertionRef, String publicKey, String userId) { + HashMap headers = new HashMap<>(); + headers.put(lollipopRequestConfigMock.getAuthJWTHeader(), ""); + headers.put(lollipopRequestConfigMock.getAssertionRefHeader(), assertionRef); + headers.put(lollipopRequestConfigMock.getUserIdHeader(), userId); + headers.put(lollipopRequestConfigMock.getPublicKeyHeader(), publicKey); + + return LollipopConsumerRequest.builder().headerParams(headers).build(); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/HttpMessageVerifierServiceImplTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/HttpMessageVerifierServiceImplTest.java new file mode 100644 index 00000000..c642c729 --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/HttpMessageVerifierServiceImplTest.java @@ -0,0 +1,235 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service.impl; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.Mockito.when; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.exception.LollipopDigestException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopSignatureException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopVerifierException; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifier; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +class HttpMessageVerifierServiceImplTest { + + final String VALID_SIGNATURE_INPUT = + "sig1=(\"content-digest\" \"x-pagopa-lollipop-original-method\" " + + "\"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=" + + "\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg"; + final String VALID_SIGNATURE = + "sig1=:lTuoRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:"; + + final String INVALID_SIGNATURE = + "sig1=:aTuoRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:"; + + final String VALID_DIGEST = "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw="; + final String VALID_PAYLOAD = "a valid message payload"; + final String INVALID_PAYLOAD = "an invalid payload"; + final String VALID_ENCODING = "UTF-8"; + + private final LollipopConsumerRequestConfig httpMessageVerifierConfig; + private final HttpMessageVerifier httpMessageVerifier; + + private HttpMessageVerifierServiceImpl httpMessageVerifierService; + + public HttpMessageVerifierServiceImplTest() { + MockitoAnnotations.openMocks(this); + this.httpMessageVerifierConfig = + LollipopConsumerRequestConfig.builder() + .contentDigestHeader("Content-Digest") + .contentEncodingHeader("Content-Encoding") + .signatureHeader("Signature") + .signatureInputHeader("Signature-Input") + .build(); + this.httpMessageVerifier = Mockito.mock(HttpMessageVerifier.class); + } + + @BeforeEach + public void beforeEach() + throws LollipopDigestException, UnsupportedEncodingException, + LollipopSignatureException { + Mockito.reset(httpMessageVerifier); + when(httpMessageVerifier.verifyDigest( + Mockito.eq(VALID_DIGEST), + Mockito.eq(VALID_PAYLOAD), + Mockito.eq(VALID_ENCODING))) + .thenReturn(true); + when(httpMessageVerifier.verifyHttpSignature( + Mockito.eq(VALID_SIGNATURE), + Mockito.eq(VALID_SIGNATURE_INPUT), + Mockito.any())) + .thenReturn(true); + when(httpMessageVerifier.verifyHttpSignature( + Mockito.eq(INVALID_SIGNATURE), + Mockito.eq(VALID_SIGNATURE_INPUT), + Mockito.any())) + .thenReturn(false); + when(httpMessageVerifier.verifyDigest( + Mockito.eq(VALID_DIGEST), + Mockito.eq(INVALID_PAYLOAD), + Mockito.eq(VALID_ENCODING))) + .thenReturn(false); + this.httpMessageVerifierService = + new HttpMessageVerifierServiceImpl(httpMessageVerifier, httpMessageVerifierConfig); + } + + @Test + void validRequestIsProcessed() { + assertThatNoException() + .isThrownBy( + () -> + httpMessageVerifierService.verifyHttpMessage( + getLollipopConsumerRequest())); + } + + @Test + void invalidRequestIsProcessedWithStrictDigestValidation() { + LollipopConsumerRequest lollipopConsumerRequest = getLollipopConsumerRequest(); + this.httpMessageVerifierConfig.setStrictDigestVerify(true); + lollipopConsumerRequest.setRequestBody(INVALID_PAYLOAD); + lollipopConsumerRequest + .getHeaderParams() + .put( + "Signature-Input", + "sig1=(\"x-pagopa-lollipop-original-method\" " + + "\"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=" + + "\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg"); + // execute & verify + assertThatThrownBy( + () -> httpMessageVerifierService.verifyHttpMessage(lollipopConsumerRequest)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e) + .hasMessageContaining( + "Content-Digest does not match the request payload"); + assertThat(e.getErrorCode()) + .isEqualTo(LollipopDigestException.ErrorCode.INCORRECT_DIGEST); + }); + } + + @Test + void requestWithInvalidDigestThrowsException() { + LollipopConsumerRequest lollipopConsumerRequest = getLollipopConsumerRequest(); + lollipopConsumerRequest.setRequestBody(INVALID_PAYLOAD); + // execute & verify + assertThatThrownBy( + () -> httpMessageVerifierService.verifyHttpMessage(lollipopConsumerRequest)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e) + .hasMessageContaining( + "Content-Digest does not match the request payload"); + assertThat(e.getErrorCode()) + .isEqualTo(LollipopDigestException.ErrorCode.INCORRECT_DIGEST); + }); + } + + @Test + void requestWithoutContentDigestToValidateThrowsException() { + LollipopConsumerRequest lollipopConsumerRequest = getLollipopConsumerRequest(); + lollipopConsumerRequest.getHeaderParams().remove("Content-Digest"); + // execute & verify + assertThatThrownBy( + () -> httpMessageVerifierService.verifyHttpMessage(lollipopConsumerRequest)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e) + .hasMessageContaining( + "Missing required Content-Digest for validation"); + assertThat(e.getErrorCode()) + .isEqualTo(LollipopDigestException.ErrorCode.MISSING_DIGEST); + }); + } + + @Test + void requestWithoutRequestBodyToValidateThrowsException() { + LollipopConsumerRequest lollipopConsumerRequest = getLollipopConsumerRequest(); + lollipopConsumerRequest.setRequestBody(null); + // execute & verify + assertThatThrownBy( + () -> httpMessageVerifierService.verifyHttpMessage(lollipopConsumerRequest)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e) + .hasMessageContaining( + "Missing required payload for digest validation"); + assertThat(e.getErrorCode()) + .isEqualTo(LollipopDigestException.ErrorCode.MISSING_PAYLOAD); + }); + } + + @Test + void requestWithoutSignatureToValidateThrowsException() { + LollipopConsumerRequest lollipopConsumerRequest = getLollipopConsumerRequest(); + lollipopConsumerRequest.getHeaderParams().remove("Signature"); + // execute & verify + assertThatThrownBy( + () -> httpMessageVerifierService.verifyHttpMessage(lollipopConsumerRequest)) + .isInstanceOfSatisfying( + LollipopVerifierException.class, + e -> { + assertThat(e).hasMessageContaining("Missing Signature Header"); + assertThat(e.getErrorCode()) + .isEqualTo( + LollipopVerifierException.ErrorCode.MISSING_SIGNATURE); + }); + } + + @Test + void requestWithoutSignatureInputToValidateThrowsException() { + LollipopConsumerRequest lollipopConsumerRequest = getLollipopConsumerRequest(); + lollipopConsumerRequest.getHeaderParams().remove("Signature-Input"); + // execute & verify + assertThatThrownBy( + () -> httpMessageVerifierService.verifyHttpMessage(lollipopConsumerRequest)) + .isInstanceOfSatisfying( + LollipopVerifierException.class, + e -> { + assertThat(e).hasMessageContaining("Missing Signature-Input Header"); + assertThat(e.getErrorCode()) + .isEqualTo( + LollipopVerifierException.ErrorCode + .MISSING_SIGNATURE_INPUT); + }); + } + + @Test + void requestWithInvalidSignatureRetunsFalse() { + AtomicBoolean result = new AtomicBoolean(false); + LollipopConsumerRequest lollipopConsumerRequest = getLollipopConsumerRequest(); + lollipopConsumerRequest.getHeaderParams().put("Signature", INVALID_SIGNATURE); + assertThatNoException() + .isThrownBy( + () -> + result.set( + httpMessageVerifierService.verifyHttpMessage( + lollipopConsumerRequest))); + assertThat(result).isFalse(); + } + + private LollipopConsumerRequest getLollipopConsumerRequest() { + + HashMap lollipopHeaderParams = new HashMap<>(); + lollipopHeaderParams.put("Content-Digest", VALID_DIGEST); + lollipopHeaderParams.put("Content-Encoding", VALID_ENCODING); + lollipopHeaderParams.put("Signature-Input", VALID_SIGNATURE_INPUT); + lollipopHeaderParams.put("Signature", VALID_SIGNATURE); + + return LollipopConsumerRequest.builder() + .requestBody(VALID_PAYLOAD) + .headerParams(lollipopHeaderParams) + .build(); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/LollipopConsumerRequestValidationServiceImplTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/LollipopConsumerRequestValidationServiceImplTest.java new file mode 100644 index 00000000..c3812057 --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/LollipopConsumerRequestValidationServiceImplTest.java @@ -0,0 +1,502 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service.impl; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.spy; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.enumeration.AssertionType; +import it.pagopa.tech.lollipop.consumer.exception.LollipopRequestContentValidationException; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class LollipopConsumerRequestValidationServiceImplTest { + + private LollipopConsumerRequestConfig config; + private LollipopConsumerRequestValidationService sut; + + private final Random random = new Random(); + + public static final String VALID_EC_PUBLIC_KEY = + "{ \"kty\": \"EC\", \"x\": \"FqFDuwEgu4MUXERPMVL-85pGv2D3YmL4J1gfMkdbc24\", \"y\":" + + " \"hdV0oxmWFSxMoJUDpdihr76rS8VRBEqMFebYyAfK9-k\", \"crv\": \"P-256\"}"; + public static final String VALID_RSA_PUBLIC_KEY = + "{ \"alg\": \"RS256\", \"e\": \"65537\", \"kty\": \"RSA\", \"n\":" + + " \"16664736175603627996319962836030881026179675012391119517975514948152431214653585662880486636564539745534321011181408561816254231231298259205135081219875827651147217038442994953270212442857910417611387549687536933145745249602198835932059392377695498325446146715840517338191125529557810596070318285357964276748438650077150378696894010172596714187128214451872453277619054588751139432194135913672107689362828514055714059473608142304229480488308405791341245363647711560656764853819020066812645413910427819478301754525254844345246642430554339909098721902422359723272095429198014557278590405542226255562568066559844209030611\"}"; + public static final String VALID_ASSERTION_REF = + "sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg"; + public static final String VALID_FISCAL_CODE = "AAAAAA89S20I111X"; + public static final String VALID_JWT = "aValidJWT"; + public static final String VALID_SIGNATURE_INPUT = + "sig1=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + public static final String VALID_SIGNATURE = + "sig1=:lTuoRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:"; + + @BeforeEach + void setUp() { + config = spy(LollipopConsumerRequestConfig.builder().build()); + sut = new LollipopConsumerRequestValidationServiceImpl(config); + } + + @Test + void validatePublicKeyFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_PUBLIC_KEY, + e.getErrorCode()); + } + + @Test + void validatePublicKeyFailureHeaderInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), generateRandomString()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_PUBLIC_KEY, + e.getErrorCode()); + } + + @Test + void validateAssertionRefFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_ASSERTION_REF, + e.getErrorCode()); + } + + @Test + void validateAssertionRefFailureInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), generateRandomString()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_ASSERTION_REF, + e.getErrorCode()); + } + + @Test + void validateAssertionTypeFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_ASSERTION_TYPE, + e.getErrorCode()); + } + + @Test + void validateAssertionTypeFailureInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), generateRandomString()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_ASSERTION_TYPE, + e.getErrorCode()); + } + + @Test + void validateUserIdFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_USER_ID, + e.getErrorCode()); + } + + @Test + void validateUserIdFailureInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), generateRandomString()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_USER_ID, + e.getErrorCode()); + } + + @Test + void validateAuthJWKFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_AUTH_JWT, + e.getErrorCode()); + } + + @Test + void validateAuthJWKFailureInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), ""); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_AUTH_JWT, + e.getErrorCode()); + } + + @Test + void validateOriginalMethodFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_ORIGINAL_METHOD, + e.getErrorCode()); + } + + @Test + void validateOriginalMethodFailureNotSupported() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), "INVALID_METHOD"); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_ORIGINAL_METHOD, + e.getErrorCode()); + } + + @Test + void validateOriginalMethodFailureDifferentFromExpectedMethod() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), "PUT"); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.UNEXPECTED_ORIGINAL_METHOD, + e.getErrorCode()); + } + + @Test + void validateOriginalURLFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_ORIGINAL_URL, + e.getErrorCode()); + } + + @Test + void validateOriginalURLFailureInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), generateRandomString()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_ORIGINAL_URL, + e.getErrorCode()); + } + + @Test + void validateOriginalURLFailureDifferentFromExpectedMethod() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), "https://pagopa.it"); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.UNEXPECTED_ORIGINAL_URL, + e.getErrorCode()); + } + + @Test + void validateSignatureInputFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), config.getExpectedFirstLcOriginalUrl()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_SIGNATURE_INPUT, + e.getErrorCode()); + } + + @Test + void validateSignatureInputFailureInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), config.getExpectedFirstLcOriginalUrl()); + headers.put(config.getSignatureInputHeader(), generateRandomString()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_SIGNATURE_INPUT, + e.getErrorCode()); + } + + @Test + void validateSignatureFailureHeaderNotPresent() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), config.getExpectedFirstLcOriginalUrl()); + headers.put(config.getSignatureInputHeader(), VALID_SIGNATURE_INPUT); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.MISSING_SIGNATURE, + e.getErrorCode()); + } + + @Test + void validateSignatureFailureInvalidFormat() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), config.getExpectedFirstLcOriginalUrl()); + headers.put(config.getSignatureInputHeader(), VALID_SIGNATURE_INPUT); + headers.put(config.getSignatureHeader(), generateRandomString()); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + LollipopRequestContentValidationException e = + assertThrows( + LollipopRequestContentValidationException.class, + () -> sut.validateLollipopRequest(request)); + + assertEquals( + LollipopRequestContentValidationException.ErrorCode.INVALID_SIGNATURE, + e.getErrorCode()); + } + + @Test + void validateRequestSuccessWithECPublicKey() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_EC_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), config.getExpectedFirstLcOriginalUrl()); + headers.put(config.getSignatureInputHeader(), VALID_SIGNATURE_INPUT); + headers.put(config.getSignatureHeader(), VALID_SIGNATURE); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + assertDoesNotThrow(() -> sut.validateLollipopRequest(request)); + } + + @Test + void validateRequestSuccessWithRSAPublicKey() { + HashMap headers = new HashMap<>(); + headers.put(config.getPublicKeyHeader(), VALID_RSA_PUBLIC_KEY); + headers.put(config.getAssertionRefHeader(), VALID_ASSERTION_REF); + headers.put(config.getAssertionTypeHeader(), AssertionType.SAML.name()); + headers.put(config.getUserIdHeader(), VALID_FISCAL_CODE); + headers.put(config.getAuthJWTHeader(), VALID_JWT); + headers.put(config.getOriginalMethodHeader(), config.getExpectedFirstLcOriginalMethod()); + headers.put(config.getOriginalURLHeader(), config.getExpectedFirstLcOriginalUrl()); + headers.put(config.getSignatureInputHeader(), VALID_SIGNATURE_INPUT); + headers.put(config.getSignatureHeader(), VALID_SIGNATURE); + LollipopConsumerRequest request = + LollipopConsumerRequest.builder().headerParams(headers).build(); + + assertDoesNotThrow(() -> sut.validateLollipopRequest(request)); + } + + private String generateRandomString() { + byte[] array = new byte[7]; + random.nextBytes(array); + return new String(array, StandardCharsets.UTF_8); + } +} diff --git a/core/src/test/java/it/pagopa/tech/lollipop/consumer/utils/LollipopConsumerConverterTest.java b/core/src/test/java/it/pagopa/tech/lollipop/consumer/utils/LollipopConsumerConverterTest.java new file mode 100644 index 00000000..7ce16b5f --- /dev/null +++ b/core/src/test/java/it/pagopa/tech/lollipop/consumer/utils/LollipopConsumerConverterTest.java @@ -0,0 +1,83 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.utils; + +import static it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandImpl.VERIFICATION_SUCCESS_CODE; + +import it.pagopa.tech.lollipop.consumer.model.CommandResult; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import java.io.IOException; +import java.util.Enumeration; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +class LollipopConsumerConverterTest { + + String REQUEST_BODY_STRING = "{\"message\":\"a valid message payload\"}"; + String COMMAND_RESPONSE_SUCCESS = "SAML assertion validated successfully"; + String COMMAND_RESPONSE_FAILED = "Validation of SAML assertion failed, authentication failed"; + byte[] REQUEST_BODY = REQUEST_BODY_STRING.getBytes(); + + static Enumeration REQUEST_HEADERS; + + static MockHttpServletRequest mockRequest; + + @BeforeAll + static void setUp() { + mockRequest = new MockHttpServletRequest(); + mockRequest.addHeader("content-digest", "sha-256=:test:"); + mockRequest.setParameter("testParam", "value1", "value2"); + } + + @Test + void convertGetHttpRequest() throws IOException { + mockRequest.setMethod("GET"); + + LollipopConsumerRequest request = + LollipopConsumerConverter.convertToLollipopRequest(mockRequest); + Assertions.assertNotNull(request.getHeaderParams()); + Assertions.assertNotNull(request.getRequestParams()); + } + + @Test + void convertPostHttpRequest() throws IOException { + mockRequest.setMethod("POST"); + mockRequest.setContent(REQUEST_BODY); + + LollipopConsumerRequest request = + LollipopConsumerConverter.convertToLollipopRequest(mockRequest); + Assertions.assertNotNull(request.getRequestBody()); + Assertions.assertNotNull(request.getHeaderParams()); + Assertions.assertNotNull(request.getRequestParams()); + } + + @Test + void convertSuccessResponse() throws IOException { + CommandResult result = + new CommandResult(VERIFICATION_SUCCESS_CODE, COMMAND_RESPONSE_SUCCESS); + int MOCK_RESPONSE_STATUS = 200; + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + mockResponse.setStatus(MOCK_RESPONSE_STATUS); + + HttpServletResponse response = + LollipopConsumerConverter.interceptResult(result, mockResponse); + + Assertions.assertEquals(MOCK_RESPONSE_STATUS, response.getStatus()); + } + + @Test + void convertUnauthorizedResponse() throws IOException { + CommandResult result = new CommandResult("FAILED", COMMAND_RESPONSE_FAILED); + MockHttpServletResponse mockResponse = new MockHttpServletResponse(); + + HttpServletResponse response = + LollipopConsumerConverter.interceptResult(result, mockResponse); + + Assertions.assertEquals(401, response.getStatus()); + Assertions.assertEquals( + COMMAND_RESPONSE_FAILED, ((MockHttpServletResponse) response).getContentAsString()); + } +} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 00000000..5e79e165 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,56 @@ +version: "3.9" +services: + mockserverAssertion: + image: mockoon/cli:latest + command: [ "--data", "data", "--port", "3000" ] + volumes: + - ./e2e/mockoon/mockoonAssertions.json:/data:readonly + mockserverIDP: + image: mockoon/cli:latest + command: [ "--data", "data", "--port", "3001" ] + volumes: + - ./e2e/mockoon/mockoonIDP.json:/data:readonly + web: + build: . + ports: + - "8080:8080" + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:8080/actuator/health || exit 1" ] + interval: 10s + timeout: 5s + retries: 2 + start_period: 15s + environment: + ##Client mocks config + ASSERTION_CLIENT_MOCK_ENABLED: ${ASSERTION_CLIENT_MOCK_ENABLED:-false} + IDP_CLIENT_MOCK_ENABLED: ${IDP_CLIENT_MOCK_ENABLED:-false} + + ##Sample controller endpoint + SAMPLE_LOLLIPOP_CONSUMER_ENDPOINT: ${SAMPLE_LOLLIPOP_CONSUMER_ENDPOINT:-/api/v1/lollipop-consumer} + + ##General Lollipop Configs Sample + LOLLIPOP_ASSERTION_EXPIRE_IN_DAYS: ${LOLLIPOP_ASSERTION_EXPIRE_IN_DAYS:-180} + LOLLIPOP_EXPECTED_LC_ORIGINAL_URL: ${LOLLIPOP_EXPECTED_LC_ORIGINAL_URL:-https://api-app.io.pagopa.it/first-lollipop/sign} + LOLLIPOP_EXPECTED_LC_ORIGINAL_METHOD: ${LOLLIPOP_EXPECTED_LC_ORIGINAL_METHOD:-POST} + LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT: ${LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT:-yyyy-MM-dd'T'HH:mm:ss.SSS'Z'} + LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT: ${LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT:-yyyy-MM-dd'T'HH:mm:ss.SSS'Z'} + + ###Idp Client Configs + IDP_CLIENT_CIEID: ${IDP_CLIENT_CIEID:-https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO} + IDP_CLIENT_BASE_URI: ${IDP_CLIENT_BASE_URI:-https://api.is.eng.pagopa.it} + IDP_CLIENT_CIE_ENDPOINT: ${IDP_CLIENT_CIE_ENDPOINT:-/idp-keys/cie} + IDP_CLIENT_SPID_ENDPOINT: ${IDP_CLIENT_SPID_ENDPOINT:-/idp-keys/spid} + + ###Idp Storage Configs + IDP_STORAGE_ENABLED: ${IDP_STORAGE_ENABLED:-true} + IDP_STORAGE_EVICTION_DELAY: ${IDP_STORAGE_EVICTION_DELAY:-1} + + ##Assertion Client Configs + ASSERTION_REST_URI: ${ASSERTION_REST_URI:-http://localhost:3000} + ASSERTION_REST_ENDPOINT: ${ASSERTION_REST_ENDPOINT:-/assertions} + + ##Assertion Storage Configs + ASSERTION_STORAGE_ENABLED: ${ASSERTION_STORAGE_ENABLED:-true} + ASSERTION_STORAGE_EVICTION_DELAY: ${ASSERTION_STORAGE_EVICTION_DELAY:-1} + + diff --git a/e2e/.env b/e2e/.env new file mode 100644 index 00000000..e69de29b diff --git a/e2e/.env.dev b/e2e/.env.dev new file mode 100644 index 00000000..46b3031e --- /dev/null +++ b/e2e/.env.dev @@ -0,0 +1,4 @@ +ASSERTION_REST_URI = "http://mockserverAssertion:3000" +IDP_CLIENT_BASE_URI = "http://mockserverIDP:3001" +LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'" +LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'" \ No newline at end of file diff --git a/e2e/README.md b/e2e/README.md new file mode 100644 index 00000000..f54c64ac --- /dev/null +++ b/e2e/README.md @@ -0,0 +1,92 @@ +# End-to-end test + +### Prerequisites ++ [Node.js](https://nodejs.org/en/download) ++ [Gradle](https://gradle.org/install/) ++ [Docker Compose](https://docs.docker.com/compose/) ++ Define in your system the following environment variables: + + GITHUB_ACTOR : [github username](https://github.com/settings/profile) + + GITHUB_TOKEN : [github personal access token (classic)](https://github.com/settings/tokens) with read permissions + +### Testing models + ++ #### Publish library dependencies to maven local +In the root folder, update gradlew permission and publish the dependencies + +```bash +chmod +x ./gradlew +./gradlew publishToMavenLocal +``` + ++ #### Build spring sample application +Change directory to ./samples/spring folder, +update also this gradlew file permission and build the sample + +```bash +cd samples/spring +chmod +x ./gradlew +./gradlew bootJar +``` + ++ #### Run docker container +Return to root folder and run docker compose in the e2e folder + +```bash +cd .. +cd e2e +docker compose --env-file .env.dev up --build +``` + ++ ##### Prepare newman +Staying in the e2e folder, install [newman](https://www.npmjs.com/package/newman) with npm + +```bash +npm i newman +``` + ++ #### Run tests +Finally, when all docker's containers are healthy, you can run the tests with + +```bash +npm run execute-test +``` + ++ #### View report +Newman generates a detailed report in the "newman" folder, you can open it with your preferred browser + +### Configuration +The sample configuration can be changed with environment variables in the .env.dev file +(or using a different .env file in the docker compose command) + +The configurable variables are the following: + +| VARIABLE | DEFAULT | USAGE | +|-------------------------------------------|-----------------------------------------------------------------------|--------------------------------------------------------------------| +| ASSERTION_CLIENT_MOCK_ENABLED | false | Enable Mockserver client | +| IDP_CLIENT_MOCK_ENABLED | false | Enable Mockserver client | +| SAMPLE_LOLLIPOP_CONSUMER_ENDPOINT | /api/v1/lollipop-consumer | Define sample controller endpoint | +| LOLLIPOP_ASSERTION_EXPIRE_IN_DAYS | 180 | Define after how many days assertion expires | +| LOLLIPOP_EXPECTED_LC_ORIGINAL_URL | https://api-app.io.pagopa.it/first-lollipop/sign | Define original url expected in request's header | +| LOLLIPOP_EXPECTED_LC_ORIGINAL_METHOD | POST | Define original method expected in request's header | +| LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT | yyyy-MM-dd'T'HH:mm:ss.SSS'Z' | Define the date format used in the Assertion's notBefore field | +| LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT | yyyy-MM-dd'T'HH:mm:ss.SSS'Z' | Define the date format used in the Assertion's Issue Instant field | +| IDP_CLIENT_CIEID | https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO | Define entity id for CIE identity provider | +| IDP_CLIENT_BASE_URI | https://api.is.eng.pagopa.it | Define base uri to retrieve IDP certification data | +| IDP_CLIENT_CIE_ENDPOINT | /idp-keys/cie | Define endpoint to IDP_CLIENT_BASE_URI for CIE's certification | +| IDP_CLIENT_SPID_ENDPOINT | /idp-keys/spid | Define endpoint to IDP_CLIENT_BASE_URI for SPID's certification | +| IDP_STORAGE_ENABLED | true | Enable internal cache storage for IDP certification data | +| IDP_STORAGE_EVICTION_DELAY | 1 | Define storage eviction delay for IDP's storage (in Minutes by default) | +| ASSERTION_REST_URI | http://localhost:3000 | Define base uri to retrieve the Assertion | +| ASSERTION_REST_ENDPOINT | /assertions | Define endpoint to ASSERTION_REST_URI | +| ASSERTION_STORAGE_ENABLED | true | Enable internal cache storage for assertions | +| ASSERTION_STORAGE_EVICTION_DELAY | 1 | Define storage eviction delay for assertion's storage (in Minutes by default) | + +### Troubleshooting + +- ##### Docker image incompatibility + + The docker image [eclipse-temurin:11-jdk-alpine](https://hub.docker.com/layers/library/eclipse-temurin/11-jdk-alpine/images/sha256-ea0ec99f8cfbaff4d61fec32af9430097e152860ec58b3cf2cb06454d75c61b0?context=explore) + used to build the sample is compatible only with amd64 cpus and not with + apple's silicon cpus (arc64), if you have an arc cpu and having trouble building the docker image + change it in the Dockerfile to [eclipse-temurin:11-jre-jammy](https://hub.docker.com/layers/library/eclipse-temurin/11-jre-jammy/images/sha256-18c3e334425f4fbf3a53f2f0df713e4d206894fb00ab2edde6df0311f5b63550?context=explore). + diff --git a/e2e/automatic-test.js b/e2e/automatic-test.js new file mode 100644 index 00000000..f711411d --- /dev/null +++ b/e2e/automatic-test.js @@ -0,0 +1,20 @@ +const newman = require('newman'); + +newman.run({ + collection: require('./collections/lollipopSDKTest.postman_collection.json'), + environment: './env/lollipopEnvironmentVariables.postman_environment.json', + reporters: ['cli', 'htmlextra'], + bail: true, + verbose: true +}, function (err, summary) { + if (err) { throw err; } + if(summary?.run?.error){ throw 'collection run encountered an error.';} + + if(summary?.run?.failures?.length > 0){ + const errors = summary.run.failures; + + throw `following collection tests failed: ${errors.map((er) => `\n${er.source.name}`)}`; + } + + console.info('collection run completed.'); +}); \ No newline at end of file diff --git a/e2e/collections/lollipopSDKTest.postman_collection.json b/e2e/collections/lollipopSDKTest.postman_collection.json new file mode 100644 index 00000000..48649221 --- /dev/null +++ b/e2e/collections/lollipopSDKTest.postman_collection.json @@ -0,0 +1,691 @@ +{ + "info": { + "_postman_id": "3b7deb11-2758-4c4f-9c1d-4ff1b9d627b4", + "name": "LollipopSDKTest", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "25397617" + }, + "item": [ + { + "name": "Success", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Expected HTTP Status\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Digest", + "value": "{{validContentDigest}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-url", + "value": "{{expectedOriginalURL}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-method", + "value": "{{expectedOriginalMethod}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-public-key", + "value": "{{validPublicKey}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-ref", + "value": "{{validAssertionRef}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-type", + "value": "{{validAssertionType}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-auth-jwt", + "value": "{{validAuthJWT}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-user-id", + "value": "{{validUserId}}", + "type": "text" + }, + { + "key": "Signature-Input", + "value": "{{validSignatureInput}}", + "type": "text" + }, + { + "key": "Signature", + "value": "{{validSignature}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\":\"a valid message payload\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROTOCOL}}{{BASE_URL}}", + "host": [ + "{{PROTOCOL}}{{BASE_URL}}" + ] + } + }, + "response": [] + }, + { + "name": "Success with multi signature", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Expected HTTP Status\", function () {\r", + " pm.response.to.have.status(200);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Digest", + "value": "{{validContentDigest}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-url", + "value": "{{expectedOriginalURL}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-method", + "value": "{{expectedOriginalMethod}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-public-key", + "value": "{{validPublicKey}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-ref", + "value": "{{validAssertionRef}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-type", + "value": "{{validAssertionType}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-auth-jwt", + "value": "{{validAuthJWT}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-user-id", + "value": "{{validUserId}}", + "type": "text" + }, + { + "key": "x-io-sign-qtspclauses", + "value": "anIoSignClauses", + "type": "text" + }, + { + "key": "Signature-Input", + "value": "sig1=(\"x-io-sign-qtspclauses\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\", sig2=(\"content-digest\" \"x-pagopa-lollipop-original-method\" \"x-pagopa-lollipop-original-url\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\"", + "type": "text" + }, + { + "key": "Signature", + "value": "sig1=:8MB/iT9iZO2HfVjMds6WdFMQeutkPnoyBDhzeyvIQDhb/tX0nE6HeRSoRBsrl4GUzo6OItnzfzF43Sd14P7tAw==:,sig2=:ZDWu2x+6APQG0Ioj10uNzTBv+5JbFBYnjhqcpL66oGFtwznROAUouXkx80ekzUY5h0HoJWE/ecqxRK2OVeHTiQ==:", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\":\"a valid message payload\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROTOCOL}}{{BASE_URL}}", + "host": [ + "{{PROTOCOL}}{{BASE_URL}}" + ] + } + }, + "response": [] + }, + { + "name": "Failed with invalid content digest", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Expected HTTP Status\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Digest", + "value": "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskyXXXXX=:", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-url", + "value": "{{expectedOriginalURL}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-method", + "value": "{{expectedOriginalMethod}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-public-key", + "value": "{{validPublicKey}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-ref", + "value": "{{validAssertionRef}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-type", + "value": "{{validAssertionType}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-auth-jwt", + "value": "{{validAuthJWT}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-user-id", + "value": "{{validUserId}}", + "type": "text" + }, + { + "key": "Signature-Input", + "value": "{{validSignatureInput}}", + "type": "text" + }, + { + "key": "Signature", + "value": "{{validSignature}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\":\"a valid message payload\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROTOCOL}}{{BASE_URL}}", + "host": [ + "{{PROTOCOL}}{{BASE_URL}}" + ] + } + }, + "response": [] + }, + { + "name": "Failed with invalid body", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Expected HTTP Status\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Digest", + "value": "{{validContentDigest}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-url", + "value": "{{expectedOriginalURL}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-method", + "value": "{{expectedOriginalMethod}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-public-key", + "value": "{{validPublicKey}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-ref", + "value": "{{validAssertionRef}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-type", + "value": "{{validAssertionType}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-auth-jwt", + "value": "{{validAuthJWT}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-user-id", + "value": "{{validUserId}}", + "type": "text" + }, + { + "key": "Signature-Input", + "value": "{{validSignatureInput}}", + "type": "text" + }, + { + "key": "Signature", + "value": "{{validSignature}}", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\":\"an invalid message payload\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROTOCOL}}{{BASE_URL}}", + "host": [ + "{{PROTOCOL}}{{BASE_URL}}" + ] + } + }, + "response": [] + }, + { + "name": "Failed with invalid encoding", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Expected HTTP Status\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Digest", + "value": "{{validContentDigest}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-url", + "value": "{{expectedOriginalURL}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-method", + "value": "{{expectedOriginalMethod}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-public-key", + "value": "{{validPublicKey}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-ref", + "value": "{{validAssertionRef}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-type", + "value": "{{validAssertionType}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-auth-jwt", + "value": "{{validAuthJWT}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-user-id", + "value": "{{validUserId}}", + "type": "text" + }, + { + "key": "Signature-Input", + "value": "{{validSignatureInput}}", + "type": "text" + }, + { + "key": "Signature", + "value": "{{validSignature}}", + "type": "text" + }, + { + "key": "content-encoding", + "value": "UTF-326", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\":\"a valid message payload\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROTOCOL}}{{BASE_URL}}", + "host": [ + "{{PROTOCOL}}{{BASE_URL}}" + ] + } + }, + "response": [] + }, + { + "name": "Failed with invalid signature", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Expected HTTP Status\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Digest", + "value": "{{validContentDigest}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-url", + "value": "{{expectedOriginalURL}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-method", + "value": "{{expectedOriginalMethod}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-public-key", + "value": "{{validPublicKey}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-ref", + "value": "{{validAssertionRef}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-type", + "value": "{{validAssertionType}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-auth-jwt", + "value": "{{validAuthJWT}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-user-id", + "value": "{{validUserId}}", + "type": "text" + }, + { + "key": "Signature-Input", + "value": "{{validSignatureInput}}", + "type": "text" + }, + { + "key": "Signature", + "value": "sig123=:lTTTRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\":\"a valid message payload\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROTOCOL}}{{BASE_URL}}", + "host": [ + "{{PROTOCOL}}{{BASE_URL}}" + ] + } + }, + "response": [] + }, + { + "name": "Failed on thumprint validation", + "event": [ + { + "listen": "test", + "script": { + "exec": [ + "pm.test(\"Expected HTTP Status\", function () {\r", + " pm.response.to.have.status(401);\r", + "});" + ], + "type": "text/javascript" + } + } + ], + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Digest", + "value": "{{validContentDigest}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-url", + "value": "{{expectedOriginalURL}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-original-method", + "value": "{{expectedOriginalMethod}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-public-key", + "value": "eyJrdHkiOiJSU0EiLCJlIjoiQVFBQiIsImtpZCI6InRlc3Qta2V5LXJzYS1wc3MiLCJuIjoicjR0bW0zcjIwV2RfUGJxdlAxczItUUV0dnB1UmFWOFlxNDBnalVSOHkyUmp4YTZkcEcyR1hIYlBmdk0gIHM4Y3QtTGgxR0g0NXgyOFJ3M1J5NTNtbS1vQVhqeVE4Nk9uRGtaNU44bFliZ2dENE8zdzZNNnBBdkxraGs5NUFuICBkVHJpZmJJRlBOVThQUE1PN095ckZBSHFnRHN6bmpQRm1UT3RDRWNOMloxRnBXZ2Nod3VZTFBMLVdva3FsdGQxMSAgbnFxemktYko5Y3ZTS0FEWWRVQUFONVdVdHpkcGl5NkxiVGdTeFA3b2NpVTRUbjBnNUk2YURaSjdBOEx6bzBLU3kgIFpZb0E0ODVtcWNPMEdWQWRWdzlscTRhT1Q5djZkLW5iNGJuTmtRVmtsTFEzZlZBdkptLXhkRE9wOUxDTkNONDhWICAycG5ET2tGVjYtVTluVjVveWM2WEkydyJ9", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-ref", + "value": "{{validAssertionRef}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-assertion-type", + "value": "{{validAssertionType}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-auth-jwt", + "value": "{{validAuthJWT}}", + "type": "text" + }, + { + "key": "x-pagopa-lollipop-user-id", + "value": "{{validUserId}}", + "type": "text" + }, + { + "key": "Signature-Input", + "value": "sig1=(\\\"content-digest\\\" \\\"x-pagopa-lollipop-original-method\\\" \\\"x-pagopa-lollipop-original-url\\\");created=1678814391;nonce=\\\"aNonce\\\";alg=\\\"rsa-pss-sha256\\\";keyid=\\\"sha256-A3OhKGLYwSvdJ2txHi_SGQ3G-sHLh2Ibu91ErqFx_58\\\"", + "type": "text" + }, + { + "key": "Signature", + "value": "sig1=:q3Og7m8yL18HkrY+zgV92Gj05lrWaFMIEFSPg2PEnO5a46+2Tt/2n7kjqVaGjI1ZXtys+Wyh3cVXCddadNARizt0BpCRdT9S4r48xsGO79Ucq4IFwZyyHNudKu5WSH4/55j5yX/YmeCtH+Nt6Nun02OZynn3iQwgLJB+CGe3h6X02iSvl4wJjKaMGE64RFHa5osE4MctoPD1j0tRkcOtgwrGmFMr282Kqrkabbx1vUpmO9T1khjouxIryfUln9zIaZ+wWmukpAZv7TKO3CltNWgfx1XT9m/iwzHiGmtvcHbWVExdAyey8lH23MgLY43AM7ytLQNSlk1s/bPNbGmPwg==:", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"message\":\"a valid message payload\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{PROTOCOL}}{{BASE_URL}}", + "host": [ + "{{PROTOCOL}}{{BASE_URL}}" + ] + } + }, + "response": [] + } + ], + "event": [ + { + "listen": "prerequest", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + }, + { + "listen": "test", + "script": { + "type": "text/javascript", + "exec": [ + "" + ] + } + } + ], + "variable": [ + { + "key": "validContentDigest", + "value": "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:" + }, + { + "key": "expectedOriginalURL", + "value": "https://api-app.io.pagopa.it/first-lollipop/sign" + }, + { + "key": "expectedOriginalMethod", + "value": "POST" + }, + { + "key": "validPublicKey", + "value": "eyJrdHkiOiJFQyIsIngiOiJTaHlZa0ZyN1F3eE9rOE5BRXF6aklkTnc4dEVKODlZOVBlWFF1eVVOWDVjIiwieSI6InlULVJxNWc2VlVadENUd0ZnRExDM2RneGNuM2RsSmNGRjhnWGdxYWgyS0UiLCJjcnYiOiJQLTI1NiJ9" + }, + { + "key": "validAssertionRef", + "value": "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw" + }, + { + "key": "validAssertionType", + "value": "SAML" + }, + { + "key": "validAuthJWT", + "value": "aValidJWT" + }, + { + "key": "validUserId", + "value": "GDNNWA12H81Y874F" + }, + { + "key": "validSignatureInput", + "value": "sig123=(\"content-digest\" \"x-pagopa-lollipop-original-method\" \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\"" + }, + { + "key": "validSignature", + "value": "sig123=:6scl8sMzJdyG/OrnJXHRM9ajmIjrJ/zrLUDqvfOxj2h51DUKztTua3vR1kSUj/c/VT1ioDlt1QIMARABhquewg==:" + } + ] +} \ No newline at end of file diff --git a/e2e/env/lollipopEnvironmentVariables.postman_environment.json b/e2e/env/lollipopEnvironmentVariables.postman_environment.json new file mode 100644 index 00000000..8ff15b84 --- /dev/null +++ b/e2e/env/lollipopEnvironmentVariables.postman_environment.json @@ -0,0 +1,19 @@ +{ + "id": "63e9abaf-4412-47e8-8002-6ec9c1663245", + "name": "Lollipop environment variables", + "values": [ + { + "key": "PROTOCOL", + "value": "http://", + "enabled": true + }, + { + "key": "BASE_URL", + "value": "localhost:8080/api/v1/lollipop-consumer", + "enabled": true + } + ], + "_postman_variable_scope": "environment", + "_postman_exported_at": "2023-04-28T08:00:59.996Z", + "_postman_exported_using": "Postman/10.13.5" +} \ No newline at end of file diff --git a/e2e/mockoon/mockoonAssertions.json b/e2e/mockoon/mockoonAssertions.json new file mode 100644 index 00000000..55597bb5 --- /dev/null +++ b/e2e/mockoon/mockoonAssertions.json @@ -0,0 +1,117 @@ +{ + "uuid": "6d242ea2-aae5-47a5-8d9e-4ef378de066f", + "lastMigration": 27, + "name": "MockoonAssertions", + "endpointPrefix": "", + "latency": 0, + "port": 3000, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "2b4e835e-84d9-46f0-9940-ae12b0f3d97e", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "assertions/:assertion", + "responses": [ + { + "uuid": "92f402d5-0421-4e92-9218-093d655430b8", + "body": "{\n \"response_xml\": \"\\n https://spid-testenv2:8088s/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\nBAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\nBhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\nXGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\ndUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\nEKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\nIhh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\\nILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\nfhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\\nBgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\\nCSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\nwTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\\nC+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\nDa1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\njJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\\nSxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n\\n \\n \\n \\n \\n https://spid-testenv2:80884cqgG29TSKgNLy2/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU//hwnefFYe9ghDPy3rDbcNl3JetT07NR/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\nBAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\nBhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\nXGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\ndUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\nEKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\nIhh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\\nILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\nfhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\\nBgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\\nCSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\nwTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\\nC+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\nDa1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\njJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\\nSxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n\\n \\n id_48129c2a9d5e9077422591baf495747cfda668c5\\n \\n \\n \\n \\n \\n \\n https://spid.agid.gov.it/cd\\n \\n \\n \\n \\n https://www.spid.gov.it/SpidL2\\n \\n \\n \\n \\n info@agid.gov.it\\n \\n \\n Mario\\n \\n \\n Bianchi\\n \\n \\n GDNNWA12H81Y874F\\n \\n \\n 1991-12-12\\n \\n \\n \\n\"\n}", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "params", + "modifier": "assertion", + "value": "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw", + "invert": false, + "operator": "equals" + }, + { + "target": "header", + "modifier": "Accept", + "value": "application/json", + "invert": false, + "operator": "equals" + }, + { + "target": "header", + "modifier": "x-pagopa-lollipop-auth", + "value": "aValidJWT", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "AND", + "disableTemplating": false, + "fallbackTo404": true, + "default": false + }, + { + "uuid": "7813cda7-31fb-449c-9538-3b7e4b3bd827", + "body": "{}", + "latency": 0, + "statusCode": 400, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "2b4e835e-84d9-46f0-9940-ae12b0f3d97e" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [] +} \ No newline at end of file diff --git a/e2e/mockoon/mockoonIDP.json b/e2e/mockoon/mockoonIDP.json new file mode 100644 index 00000000..04742ba0 --- /dev/null +++ b/e2e/mockoon/mockoonIDP.json @@ -0,0 +1,135 @@ +{ + "uuid": "a85748d7-4867-4cf5-9166-071c9b27f67c", + "lastMigration": 27, + "name": "MockoonIDP", + "endpointPrefix": "", + "latency": 0, + "port": 3001, + "hostname": "", + "folders": [], + "routes": [ + { + "uuid": "ab9a156b-88e3-4977-aca0-a00eaf6607ec", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "idp-keys/spid", + "responses": [ + { + "uuid": "83d6e1e7-26d5-47d3-aa4b-0da9b01708b7", + "body": "[\"latest\"]", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": true, + "default": true + } + ], + "enabled": true, + "responseMode": null + }, + { + "uuid": "f462f924-74d0-4bb5-b1b7-c37f0ebd76e1", + "type": "http", + "documentation": "", + "method": "get", + "endpoint": "idp-keys/spid/:tag", + "responses": [ + { + "uuid": "7b267bd3-98c5-40dd-a6b0-87ffa42462b6", + "body": " MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08 dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+ Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7 fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2 SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ urn:oasis:names:tc:SAML:2.0:nameid-format:transient ", + "latency": 0, + "statusCode": 200, + "label": "", + "headers": [ + { + "key": "Content-Type", + "value": "application/xml" + } + ], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [ + { + "target": "params", + "modifier": "tag", + "value": "latest", + "invert": false, + "operator": "equals" + } + ], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": true, + "default": false + }, + { + "uuid": "0df14f3a-d523-4679-b0c0-f8c09f38d07d", + "body": "{}", + "latency": 0, + "statusCode": 400, + "label": "", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true + } + ], + "enabled": true, + "responseMode": null + } + ], + "rootChildren": [ + { + "type": "route", + "uuid": "ab9a156b-88e3-4977-aca0-a00eaf6607ec" + }, + { + "type": "route", + "uuid": "f462f924-74d0-4bb5-b1b7-c37f0ebd76e1" + } + ], + "proxyMode": false, + "proxyHost": "", + "proxyRemovePrefix": false, + "tlsOptions": { + "enabled": false, + "type": "CERT", + "pfxPath": "", + "certPath": "", + "keyPath": "", + "caPath": "", + "passphrase": "" + }, + "cors": true, + "headers": [], + "proxyReqHeaders": [ + { + "key": "", + "value": "" + } + ], + "proxyResHeaders": [ + { + "key": "", + "value": "" + } + ], + "data": [] +} \ No newline at end of file diff --git a/e2e/package-lock.json b/e2e/package-lock.json new file mode 100644 index 00000000..3531988e --- /dev/null +++ b/e2e/package-lock.json @@ -0,0 +1,3108 @@ +{ + "name": "lollipop-e2e-test", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "lollipop-e2e-test", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "newman": "^5.3.2", + "newman-reporter-htmlextra": "^1.22.11" + } + }, + "node_modules/@budibase/handlebars-helpers": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.8.tgz", + "integrity": "sha512-ggWJUt0GqsHFAEup5tlWlcrmYML57nKhpNGGLzVsqXVYN8eVmf3xluYmmMe7fDYhQH0leSprrdEXmsdFQF3HAQ==", + "dependencies": { + "array-sort": "^1.0.0", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "for-in": "^1.0.2", + "get-object": "^0.2.0", + "get-value": "^3.0.1", + "handlebars": "^4.7.7", + "handlebars-utils": "^1.0.6", + "has-value": "^2.0.2", + "helper-md": "^0.2.2", + "html-tag": "^2.0.0", + "is-even": "^1.0.0", + "is-glob": "^4.0.1", + "kind-of": "^6.0.3", + "micromatch": "^3.1.5", + "relative": "^3.0.2", + "striptags": "^3.1.1", + "to-gfm-code-block": "^0.1.1", + "year": "^0.2.1" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/@postman/form-data": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.1.tgz", + "integrity": "sha512-vjh8Q2a8S6UCm/KKs31XFJqEEgmbjBmpPNVV2eVav6905wyFAwaUOBGA1NPBI4ERH9MMZc6w0umFgM6WbEPMdg==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", + "dependencies": { + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort/node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-sort/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/async": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.3.tgz", + "integrity": "sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/autolinker": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/autolinker/-/autolinker-0.28.1.tgz", + "integrity": "sha512-zQAFO1Dlsn69eXaO6+7YZc+v84aquQKbwpzCE3L0stj56ERn9hutFxPopViLjo9G+rWwjozRhgS5KJ25Xy19cQ==", + "dependencies": { + "gulp-header": "^1.7.1" + } + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==" + }, + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dependencies": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha512-UfFSr22dmHPQqPP9XWHRhq+gWnHCYguQGkXQlbyPtW5qTnhFWA8/iXg765tH0cAjy7l/zPJ1aBTO0g5XgA7kvQ==" + }, + "node_modules/braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dependencies": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/braces/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dependencies": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-base/node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-base/node_modules/has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "dependencies": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-base/node_modules/has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "dependencies": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-base/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-base/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cache-base/node_modules/kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-1.4.0.tgz", + "integrity": "sha512-NpwMDdSIprbYx1CLnfbxEIarI0Z+s9MssEgggMNheGM+WD68yOhV7IEA/3r6tr0yTRgQD0HuZJDw32s99i6L+A==" + }, + "node_modules/charset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/charset/-/charset-1.0.1.tgz", + "integrity": "sha512-6dVyOOYjpfFcL1Y4qChrAoQLRHvj2ziyhcm0QJlhOcAhykL/k1kTUPbeo+87MNRTRdk2OIIsIXbuF3x2wi5EXg==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dependencies": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/class-utils/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/cli-progress": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.10.0.tgz", + "integrity": "sha512-kLORQrhYCAtUPLZxqsAt2YJGOvRdt34+O6jl5cQGb7iF3dM55FQZlTR+rQyIK9JUcO9bBMwZsTlND+3dmFU2Cw==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cli-table3": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz", + "integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==", + "dependencies": { + "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "1.4.0" + } + }, + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "dependencies": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "node_modules/csv-parse": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/csv-parse/-/csv-parse-4.16.3.tgz", + "integrity": "sha512-cO1I/zmz4w2dcKHVvpCr7JVRu8/FymG5OEpmvsZYlccYolPBLoVGKUHgNoc4ZGkFeFlWGEDmMyBM+TTqRdW/wg==" + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "dependencies": { + "kind-of": "^5.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-compare/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dependencies": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" + }, + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "dependencies": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expand-brackets/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "dependencies": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dependencies": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extglob/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/faker": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/faker/-/faker-5.5.3.tgz", + "integrity": "sha512-wLTv2a28wjUyWkbnX7u/ABZBkUkIF2fCd73V6P2oFqEGEktDfzWx4UxrSqtPRw0xPRAcjeAOIiJWqZm3pP4u3g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/file-type": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-3.9.0.tgz", + "integrity": "sha512-RLoqTXE8/vPmMuTI88DAzhMYC99I8BWv7zYP4A1puo5HIjEJ5EX48ighy4ZyKMG9EDXxBgW6e++cn7d1xuFghA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/filesize": { + "version": "8.0.7", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz", + "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fill-range/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/flatted": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==" + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "engines": { + "node": "*" + } + }, + "node_modules/fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "dependencies": { + "map-cache": "^0.2.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fs-exists-sync": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", + "integrity": "sha512-cR/vflFyPZtrN6b38ZyWxpWdhlXrzZEBawlpBQMq7033xVY7/kg0GDMBK5jg8lDYQckdJ5x/YC88lM3C7VMsLg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-object": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/get-object/-/get-object-0.2.0.tgz", + "integrity": "sha512-7P6y6k6EzEFmO/XyUyFlXm1YLJy9xeA1x/grNV8276abX5GuwUtYgKFkRFkLixw4hf4Pz9q2vgv/8Ar42R0HuQ==", + "dependencies": { + "is-number": "^2.0.2", + "isobject": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-object/node_modules/isobject": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-0.2.0.tgz", + "integrity": "sha512-VaWq6XYAsbvM0wf4dyBO7WH9D7GosB7ZZlqrawI9BBiTMINBeCyqSKBa35m870MY3O4aM31pYyZi9DfGrYMJrQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/get-value": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-3.0.1.tgz", + "integrity": "sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/gulp-header": { + "version": "1.8.12", + "resolved": "https://registry.npmjs.org/gulp-header/-/gulp-header-1.8.12.tgz", + "integrity": "sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ==", + "deprecated": "Removed event-stream from gulp-header", + "dependencies": { + "concat-with-sourcemaps": "*", + "lodash.template": "^4.4.0", + "through2": "^2.0.0" + } + }, + "node_modules/handlebars": { + "version": "4.7.7", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", + "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.0", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/handlebars-utils": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/handlebars-utils/-/handlebars-utils-1.0.6.tgz", + "integrity": "sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw==", + "dependencies": { + "kind-of": "^6.0.0", + "typeof-article": "^0.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-value": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-2.0.2.tgz", + "integrity": "sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA==", + "dependencies": { + "get-value": "^3.0.0", + "has-values": "^2.0.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-values": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-2.0.1.tgz", + "integrity": "sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w==", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/helper-md": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/helper-md/-/helper-md-0.2.2.tgz", + "integrity": "sha512-49TaQzK+Ic7ZVTq4i1UZxRUJEmAilTk8hz7q4I0WNUaTclLR8ArJV5B3A1fe1xF2HtsDTr2gYKLaVTof/Lt84Q==", + "dependencies": { + "ent": "^2.2.0", + "extend-shallow": "^2.0.1", + "fs-exists-sync": "^0.1.0", + "remarkable": "^1.6.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/helper-md/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/helper-md/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-tag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/html-tag/-/html-tag-2.0.0.tgz", + "integrity": "sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g==", + "dependencies": { + "is-self-closing": "^1.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/http-reasons": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/http-reasons/-/http-reasons-0.1.0.tgz", + "integrity": "sha512-P6kYh0lKZ+y29T2Gqz+RlC9WBLhKe8kDmcJ+A+611jFfxdPsbMRQ5aNmFRM3lENqFkK+HTTL+tlQviAiv0AbLQ==" + }, + "node_modules/http-signature": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", + "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^2.0.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/httpntlm": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.7.7.tgz", + "integrity": "sha512-Pv2Rvrz8H0qv1Dne5mAdZ9JegG1uc6Vu5lwLflIY6s8RKHdZQbW39L4dYswSgqMDT0pkJILUTKjeyU0VPNRZjA==", + "dependencies": { + "httpreq": ">=0.4.22", + "underscore": "~1.12.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/httpreq": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.5.2.tgz", + "integrity": "sha512-2Jm+x9WkExDOeFRrdBCBSpLPT5SokTcRHkunV3pjKmX/cx6av8zQ0WtHUMDrYb6O4hBFzNU6sxJEypvRUVYKnw==", + "engines": { + "node": ">= 6.15.1" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + }, + "node_modules/is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dependencies": { + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dependencies": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-even": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-even/-/is-even-1.0.0.tgz", + "integrity": "sha512-LEhnkAdJqic4Dbqn58A0y52IXoHWlsueqQkKfMfdEnIYG8A1sm/GHidKkS6yvXlMoRrkM34csHnXQtOqcb+Jzg==", + "dependencies": { + "is-odd": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dependencies": { + "is-plain-object": "^2.0.4" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-odd": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-0.1.2.tgz", + "integrity": "sha512-Ri7C2K7o5IrUU9UEI8losXJCCD/UtsaIrkR5sxIcFg4xQ9cRJXlWA5DQvTE0yDc0krvSNLsRGXN11UPS6KyfBw==", + "dependencies": { + "is-number": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-odd/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-odd/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-self-closing": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-self-closing/-/is-self-closing-1.0.1.tgz", + "integrity": "sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg==", + "dependencies": { + "self-closing-tags": "^1.0.1" + }, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "node_modules/js-sha512": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha512/-/js-sha512-0.8.0.tgz", + "integrity": "sha512-PWsmefG6Jkodqt+ePTvBZCSMFgN7Clckjd0O7su3I0+BW2QWUTJNzjktHsztGLhncP2h8mcF9V9Y2Ha59pAViQ==" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, + "node_modules/jsprim": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-2.0.2.tgz", + "integrity": "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/liquid-json": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/liquid-json/-/liquid-json-0.3.1.tgz", + "integrity": "sha512-wUayTU8MS827Dam6MxgD72Ui+KOSF+u/eIqpatOtjnvgJ0+mnDq33uC2M7J0tPK+upe/DpUAuK4JUU89iBoNKQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash._reinterpolate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", + "integrity": "sha512-xYHt68QRoYGjeeM/XOE1uJtvXQAgvszfBhjV4yvsQH0u2i9I6cI6c6/eG4Hh3UAOVn0y/xAXwmTzEay49Q//HA==" + }, + "node_modules/lodash.template": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", + "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0", + "lodash.templatesettings": "^4.0.0" + } + }, + "node_modules/lodash.templatesettings": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz", + "integrity": "sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ==", + "dependencies": { + "lodash._reinterpolate": "^3.0.0" + } + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "dependencies": { + "object-visit": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mime-db": { + "version": "1.51.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", + "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-format": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mime-format/-/mime-format-2.0.1.tgz", + "integrity": "sha512-XxU3ngPbEnrYnNbIX+lYSaYg0M01v6p2ntd2YaFksTu0vayaw5OJvbdRyWs07EYRlLED5qadUZ+xo+XhOvFhwg==", + "dependencies": { + "charset": "^1.0.0" + } + }, + "node_modules/mime-types": { + "version": "2.1.34", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", + "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", + "dependencies": { + "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "dependencies": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.35", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.35.tgz", + "integrity": "sha512-cY/pBOEXepQvlgli06ttCTKcIf8cD1nmNwOKQQAdHBqYApQSpAqotBMX0RJZNgMp6i0PlZuf1mFtnlyEkwyvFw==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dependencies": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==" + }, + "node_modules/newman": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/newman/-/newman-5.3.2.tgz", + "integrity": "sha512-cWy8pV0iwvMOZLTw3hkAHcwo2ZA0GKkXm8oUMn1Ltii3ZI2nKpnrg9QGdIT0hGHChRkX6prY5e3Aar7uykMGNg==", + "dependencies": { + "async": "3.2.3", + "chardet": "1.4.0", + "cli-progress": "3.10.0", + "cli-table3": "0.6.1", + "colors": "1.4.0", + "commander": "7.2.0", + "csv-parse": "4.16.3", + "eventemitter3": "4.0.7", + "filesize": "8.0.7", + "lodash": "4.17.21", + "mkdirp": "1.0.4", + "postman-collection": "4.1.1", + "postman-collection-transformer": "4.1.6", + "postman-request": "2.88.1-postman.31", + "postman-runtime": "7.29.0", + "pretty-ms": "7.0.1", + "semver": "7.3.5", + "serialised-error": "1.1.3", + "tough-cookie": "3.0.1", + "word-wrap": "1.2.3", + "xmlbuilder": "15.1.1" + }, + "bin": { + "newman": "bin/newman.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/newman-reporter-htmlextra": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/newman-reporter-htmlextra/-/newman-reporter-htmlextra-1.22.11.tgz", + "integrity": "sha512-7Kp2y8U9kUy14dVVX3AxRPsI/d1RiFt3AG0dw8EW+INSCRV9AWEp4AY0j/nE6m3cCMylUKJVKBYYenReo/qssA==", + "dependencies": { + "@budibase/handlebars-helpers": "0.11.8", + "chalk": "4.1.2", + "cli-progress": "3.10.0", + "commander": "6.2.1", + "filesize": "6.4.0", + "handlebars": "4.7.7", + "lodash": "4.17.21", + "moment-timezone": "0.5.35", + "pretty-ms": "7.0.1" + }, + "bin": { + "newman-reporter-htmlextra": "bin/htmlextra.js" + }, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "newman": "^5.1.2" + } + }, + "node_modules/newman-reporter-htmlextra/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/newman-reporter-htmlextra/node_modules/filesize": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-6.4.0.tgz", + "integrity": "sha512-mjFIpOHC4jbfcTfoh4rkWpI31mF7viw9ikj/JyLoKzqlwG/YsefKfvYlYhdYdg/9mtK2z1AzgN/0LvVQ3zdlSQ==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/node-oauth1": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/node-oauth1/-/node-oauth1-1.3.0.tgz", + "integrity": "sha512-0yggixNfrA1KcBwvh/Hy2xAS1Wfs9dcg6TdFf2zN7gilcAigMdrtZ4ybrBSXBgLvGDw9V1p2MRnGBMq7XjTWLg==" + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "node_modules/object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "dependencies": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-copy/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.1.tgz", + "integrity": "sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA==", + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "dependencies": { + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + }, + "node_modules/posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postman-collection": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/postman-collection/-/postman-collection-4.1.1.tgz", + "integrity": "sha512-ODpJtlf8r99DMcTU7gFmi/yvQYckFzcuE6zL/fWnyrFT34ugdCBFlX+DN7M+AnP6lmR822fv5s60H4DnL4+fAg==", + "dependencies": { + "faker": "5.5.3", + "file-type": "3.9.0", + "http-reasons": "0.1.0", + "iconv-lite": "0.6.3", + "liquid-json": "0.3.1", + "lodash": "4.17.21", + "mime-format": "2.0.1", + "mime-types": "2.1.34", + "postman-url-encoder": "3.0.5", + "semver": "7.3.5", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-collection-transformer": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/postman-collection-transformer/-/postman-collection-transformer-4.1.6.tgz", + "integrity": "sha512-xvdQb6sZoWcG9xZXUPSuxocjcd6WCZlINlGGiuHdSfxhgiwQhj9qhF0JRFbagZ8xB0+pYUairD5MiCENc6DEVA==", + "dependencies": { + "commander": "8.3.0", + "inherits": "2.0.4", + "lodash": "4.17.21", + "semver": "7.3.5", + "strip-json-comments": "3.1.1" + }, + "bin": { + "postman-collection-transformer": "bin/transform-collection.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-collection-transformer/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/postman-request": { + "version": "2.88.1-postman.31", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.31.tgz", + "integrity": "sha512-OJbYqP7ItxQ84yHyuNpDywCZB0HYbpHJisMQ9lb1cSL3N5H3Td6a2+3l/a74UMd3u82BiGC5yQyYmdOIETP/nQ==", + "dependencies": { + "@postman/form-data": "~3.1.1", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "brotli": "~1.3.2", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "tough-cookie": "~2.5.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postman-request/node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/postman-request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/postman-runtime": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/postman-runtime/-/postman-runtime-7.29.0.tgz", + "integrity": "sha512-eXxHREE/fUpohkGPRgBY1YccSGx9cyW3mtGiPyIE4zD5fYzasgBHqW6kbEND3Xrd3yf/uht/YI1H8O7J1+A1+w==", + "dependencies": { + "async": "3.2.3", + "aws4": "1.11.0", + "handlebars": "4.7.7", + "httpntlm": "1.7.7", + "js-sha512": "0.8.0", + "lodash": "4.17.21", + "mime-types": "2.1.34", + "node-oauth1": "1.3.0", + "performance-now": "2.1.0", + "postman-collection": "4.1.1", + "postman-request": "2.88.1-postman.31", + "postman-sandbox": "4.0.6", + "postman-url-encoder": "3.0.5", + "serialised-error": "1.1.3", + "tough-cookie": "3.0.1", + "uuid": "8.3.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-runtime/node_modules/aws4": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" + }, + "node_modules/postman-sandbox": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/postman-sandbox/-/postman-sandbox-4.0.6.tgz", + "integrity": "sha512-PPRanSNEE4zy3kO7CeSBHmAfJnGdD9ecHY/Mjh26CQuZZarGkNO8c0U/n+xX3+5M1BRNc82UYq6YCtdsSDqcng==", + "dependencies": { + "lodash": "4.17.21", + "teleport-javascript": "1.0.0", + "uvm": "2.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/postman-url-encoder": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/postman-url-encoder/-/postman-url-encoder-3.0.5.tgz", + "integrity": "sha512-jOrdVvzUXBC7C+9gkIkpDJ3HIxOHTIqjpQ4C1EMt1ZGeMvSEpbFCKq23DEfgsj46vMnDgyQf+1ZLp2Wm+bKSsA==", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dependencies": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/relative": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/relative/-/relative-3.0.2.tgz", + "integrity": "sha512-Q5W2qeYtY9GbiR8z1yHNZ1DGhyjb4AnLEjt8iE6XfcC1QIu+FAtj3HQaO0wH28H1mX6cqNLvAqWhP402dxJGyA==", + "dependencies": { + "isobject": "^2.0.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/relative/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/remarkable": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/remarkable/-/remarkable-1.7.4.tgz", + "integrity": "sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg==", + "dependencies": { + "argparse": "^1.0.10", + "autolinker": "~0.28.0" + }, + "bin": { + "remarkable": "bin/remarkable.js" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/repeat-element": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", + "deprecated": "https://github.com/lydell/resolve-url#deprecated" + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "dependencies": { + "ret": "~0.1.10" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/self-closing-tags": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/self-closing-tags/-/self-closing-tags-1.0.1.tgz", + "integrity": "sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/serialised-error": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/serialised-error/-/serialised-error-1.1.3.tgz", + "integrity": "sha512-vybp3GItaR1ZtO2nxZZo8eOo7fnVaNtP3XE2vJKgzkKR2bagCkdJ1EpYYhEMd3qu/80DwQk9KjsNSxE3fXWq0g==", + "dependencies": { + "object-hash": "^1.1.2", + "stack-trace": "0.0.9", + "uuid": "^3.0.0" + } + }, + "node_modules/serialised-error/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "dependencies": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/set-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dependencies": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dependencies": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-node/node_modules/define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "dependencies": { + "is-descriptor": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dependencies": { + "kind-of": "^3.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon-util/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/snapdragon/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "node_modules/source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "deprecated": "See https://github.com/lydell/source-map-url#deprecated" + }, + "node_modules/split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dependencies": { + "extend-shallow": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/sshpk": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", + "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stack-trace": { + "version": "0.0.9", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", + "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", + "engines": { + "node": "*" + } + }, + "node_modules/static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", + "dependencies": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "dependencies": { + "is-descriptor": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dependencies": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/static-extend/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha512-aI+qKFiwoDV4rsXiS7WRoCt+v2RX1nUj17+KJC5r2gfh5xoSJIfP6Y3Do/HtvesFcTSWthIuJ3l1cvKQY/+nZg==", + "dependencies": { + "bluebird": "^2.6.2" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/striptags": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/striptags/-/striptags-3.2.0.tgz", + "integrity": "sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw==" + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/teleport-javascript": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/teleport-javascript/-/teleport-javascript-1.0.0.tgz", + "integrity": "sha512-j1llvWVFyEn/6XIFDfX5LAU43DXe0GCt3NfXDwJ8XpRRMkS+i50SAkonAONBy+vxwPFBd50MFU8a2uj8R/ccLg==" + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + }, + "node_modules/to-gfm-code-block": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz", + "integrity": "sha512-LQRZWyn8d5amUKnfR9A9Uu7x9ss7Re8peuWR2gkh1E+ildOfv2aF26JpuDg8JtvCduu5+hOrMIH+XstZtnagqg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-object-path/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dependencies": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", + "dependencies": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range/node_modules/is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "dependencies": { + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/to-regex-range/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tough-cookie": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", + "dependencies": { + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "node_modules/typeof-article": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/typeof-article/-/typeof-article-0.1.1.tgz", + "integrity": "sha512-Vn42zdX3FhmUrzEmitX3iYyLb+Umwpmv8fkZRIknYh84lmdrwqZA5xYaoKiIj2Rc5i/5wcDrpUmZcbk1U51vTw==", + "dependencies": { + "kind-of": "^3.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/typeof-article/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/underscore": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz", + "integrity": "sha512-hEQt0+ZLDVUMhebKxL4x1BTtDY7bavVofhZ9KZ4aI26X9SRaE+Y3m83XUL1UP2jn8ynjndwCCpEHdUG+9pP1Tw==" + }, + "node_modules/union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "dependencies": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/union-value/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", + "dependencies": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "dependencies": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "dependencies": { + "isarray": "1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/unset-value/node_modules/has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", + "deprecated": "Please see https://github.com/lydell/urix#deprecated" + }, + "node_modules/use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/uvm": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/uvm/-/uvm-2.0.2.tgz", + "integrity": "sha512-Ra+aPiS5GXAbwXmyNExqdS42sTqmmx4XWEDF8uJlsTfOkKf9Rd9xNgav1Yckv4HfVEZg4iOFODWHFYuJ+9Fzfg==", + "dependencies": { + "flatted": "3.1.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==" + }, + "node_modules/xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", + "engines": { + "node": ">=8.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/year": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/year/-/year-0.2.1.tgz", + "integrity": "sha512-9GnJUZ0QM4OgXuOzsKNzTJ5EOkums1Xc+3YQXp+Q+UxFjf7zLucp9dQ8QMIft0Szs1E1hUiXFim1OYfEKFq97w==", + "engines": { + "node": ">=0.8" + } + } + } +} diff --git a/e2e/package.json b/e2e/package.json new file mode 100644 index 00000000..d06cf4db --- /dev/null +++ b/e2e/package.json @@ -0,0 +1,15 @@ +{ + "name": "lollipop-e2e-test", + "version": "1.0.0", + "description": "Project for Lollipop SDK end-to-end automatic tests", + "main": "automatic-test.js", + "scripts": { + "execute-test": "node automatic-test.js" + }, + "author": "", + "license": "ISC", + "dependencies": { + "newman": "^5.3.2", + "newman-reporter-htmlextra": "^1.22.11" + } +} diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml new file mode 100644 index 00000000..00812df9 --- /dev/null +++ b/gradle/verification-metadata.xml @@ -0,0 +1,6584 @@ + + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..41d9927a Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..41dfb879 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 00000000..1b6c7873 --- /dev/null +++ b/gradlew @@ -0,0 +1,234 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/http-verifier/build.gradle b/http-verifier/build.gradle new file mode 100644 index 00000000..505068b6 --- /dev/null +++ b/http-verifier/build.gradle @@ -0,0 +1,66 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'java-library' + id("io.freefair.lombok") version "8.0.0" +} + +group 'it.pagopa.tech.lollipop.sdk.impls' + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } +} + +configurations { + implementation { + attributes { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-core-jar')) + } + } +} + +abstract class InstrumentedJarsRule implements AttributeCompatibilityRule { + + @Override + void execute(CompatibilityCheckDetails details) { + if (details.consumerValue.name == 'instrumented-core-jar' && details.producerValue.name == 'jar') { + details.compatible() + } + } +} + +dependencies { + attributesSchema { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) { + compatibilityRules.add(InstrumentedJarsRule) + } + } + implementation 'it.pagopa.tech:http-signatures:1.1.4' + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.assertj:assertj-core:3.24.2' + implementation 'com.nimbusds:nimbus-jose-jwt:9.31' + implementation 'org.slf4j:slf4j-api:2.0.5' + implementation project(':core') +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/ErrorCodeConverter.java b/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/ErrorCodeConverter.java new file mode 100644 index 00000000..592d21c4 --- /dev/null +++ b/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/ErrorCodeConverter.java @@ -0,0 +1,37 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.http_verifier.visma; + +import it.pagopa.tech.lollipop.consumer.exception.LollipopDigestException; +import net.visma.autopay.http.digest.DigestException; + +/** + * Manages the conversion between {@link DigestException.ErrorCode} and {@link + * LollipopDigestException.ErrorCode} + */ +public class ErrorCodeConverter { + + private ErrorCodeConverter() { + throw new IllegalStateException("Utility class"); + } + + /** + * @param errorCode {@link DigestException.ErrorCode} to convert + * @return converted {@link LollipopDigestException.ErrorCode} + */ + public static LollipopDigestException.ErrorCode convertErrorCode( + DigestException.ErrorCode errorCode) { + + if (errorCode != null) { + switch (errorCode) { + case INVALID_HEADER: + return LollipopDigestException.ErrorCode.INVALID_HEADER; + case INCORRECT_DIGEST: + return LollipopDigestException.ErrorCode.INCORRECT_DIGEST; + case UNSUPPORTED_ALGORITHM: + return LollipopDigestException.ErrorCode.UNSUPPORTED_ALGORITHM; + } + } + + return null; + } +} diff --git a/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifier.java b/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifier.java new file mode 100644 index 00000000..9dc66bc9 --- /dev/null +++ b/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifier.java @@ -0,0 +1,213 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.http_verifier.visma; + +import com.nimbusds.jose.JOSEException; +import com.nimbusds.jose.JWSAlgorithm; +import com.nimbusds.jose.crypto.impl.ECDSA; +import com.nimbusds.jose.jwk.JWK; +import com.nimbusds.jose.jwk.KeyType; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.exception.LollipopDigestException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopSignatureException; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifier; +import java.io.UnsupportedEncodingException; +import java.security.PublicKey; +import java.text.ParseException; +import java.util.Base64; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.visma.autopay.http.digest.DigestException; +import net.visma.autopay.http.signature.*; +import net.visma.autopay.http.structured.StructuredBytes; + +/** + * Implementation of the @HttpMessageVerifier using Visma-AutoPay http-signature of the + * http-signature draft + */ +@AllArgsConstructor +@Slf4j +public class VismaHttpMessageVerifier implements HttpMessageVerifier { + + String defaultEncoding; + LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + + /** + * Validates digest using Visma DigestVerifier method. Applies contentEncoding if present, + * otherwise defaults to UTF-8 + * + * @param digest Request digest + * @param requestBody Request body + * @param encoding Content encoding, if missing uses defaultEncoding + * @return boolean with value true if digest validated + * @throws LollipopDigestException if error from DigestVerifier + * @throws UnsupportedEncodingException if attempted to encode with an unsupported format + */ + @Override + public boolean verifyDigest(String digest, String requestBody, String encoding) + throws LollipopDigestException, UnsupportedEncodingException { + try { + net.visma.autopay.http.digest.DigestVerifier.verifyDigestHeader( + digest, requestBody.getBytes(encoding != null ? encoding : defaultEncoding)); + return true; + } catch (DigestException e) { + throw new LollipopDigestException( + ErrorCodeConverter.convertErrorCode(e.getErrorCode()), e.getMessage(), e); + } catch (UnsupportedEncodingException e) { + throw e; + } + } + + /** + * @param signature Input parameter containing the expected signature + * @param signatureInput Input parameter containing the expected signature base for validation + * @param parameters Header parameters to be used in signature validation + * @return boolean to determine if validated + * @throws LollipopSignatureException exception containing all the error codes related to + * signature validation + */ + @Override + public boolean verifyHttpSignature( + String signature, String signatureInput, Map parameters) + throws LollipopSignatureException { + + /* Removed to enable multiple signature validation (if necessary) */ + parameters.remove(lollipopConsumerRequestConfig.getSignatureInputHeader()); + parameters.remove(lollipopConsumerRequestConfig.getSignatureHeader()); + + String lollipopKey = parameters.get(lollipopConsumerRequestConfig.getPublicKeyHeader()); + isLollipopKeyNotNull(lollipopKey); + + String[] signatures = signature.split(","); + String[] signatureInputs = signatureInput.split(","); + + verifySignatureLength(signatures, signatureInputs); + + /* cicle through all signatures to validate */ + for (int i = 0; i < signatures.length; i++) { + String signatureToProcess = signatures[i]; + String signatureInputToProcess = signatureInputs[i]; + + String label = signatureToProcess.split("=")[0]; + SignatureAlgorithm signatureAlgorithm = null; + + /* Extract algorithm from signature input*/ + Pattern pattern = Pattern.compile("alg=\"(.*?)\""); + Matcher matcher = pattern.matcher(signatureInputToProcess); + if (matcher.find()) { + try { + + String algToUse = matcher.group(0); + signatureAlgorithm = + SignatureAlgorithm.fromIdentifier( + algToUse.replace("\"", "").split("=")[1]); + + isSignatureAlgorithmNotNull(signatureAlgorithm); + + } catch (IndexOutOfBoundsException | IllegalStateException e) { + throw new LollipopSignatureException( + LollipopSignatureException.ErrorCode.INVALID_SIGNATURE_ALG, + "Algorithm required not available", + e); + } + } + + /* Attempt to recover a valid key from the provided jwt */ + PublicKey publicKey = null; + try { + JWK jwk = JWK.parse(new String(Base64.getDecoder().decode(lollipopKey))); + KeyType keyType = jwk.getKeyType(); + if (KeyType.EC.equals(keyType)) { + publicKey = jwk.toECKey().toECPublicKey(); + signatureToProcess = transcodeSignature(signatureToProcess); + } else if (KeyType.RSA.equals(keyType)) { + publicKey = jwk.toRSAKey().toRSAPublicKey(); + } + } catch (ParseException | JOSEException e) { + throw new LollipopSignatureException( + LollipopSignatureException.ErrorCode.INVALID_SIGNATURE_ALG, + "Missing Signature Algorithm"); + } + + var signatureContext = + SignatureContext.builder() + .headers(parameters) + .header("Signature-Input", signatureInputToProcess) + .header("Signature", signatureToProcess) + .build(); + + /* Populate Visma Sign Validator*/ + SignatureAlgorithm finalSignatureAlgorithm = signatureAlgorithm; + PublicKey finalPublicKey = publicKey; + VerificationSpec verificationSpec = + VerificationSpec.builder() + .signatureLabel(label.trim()) + .context(signatureContext) + .publicKeyGetter( + keyId -> + PublicKeyInfo.builder() + .publicKey(finalPublicKey) + .algorithm(finalSignatureAlgorithm) + .build()) + .build(); + + try { + verificationSpec.verify(); + } catch (SignatureException e) { + throw new LollipopSignatureException( + LollipopSignatureException.ErrorCode.INVALID_SIGNATURE, + "The provided signature is invalid", + e); + } + } + + return true; + } + + private String transcodeSignature(String signatureToProcess) { + try { + String[] signatureParts = signatureToProcess.split("=", 2); + String signatureValue = + StructuredBytes.of( + ECDSA.transcodeSignatureToConcat( + Base64.getMimeDecoder() + .decode(signatureParts[1].getBytes()), + ECDSA.getSignatureByteArrayLength(JWSAlgorithm.ES256))) + .toString(); + ECDSA.ensureLegalSignature( + Base64.getMimeDecoder().decode(signatureValue.getBytes()), JWSAlgorithm.ES256); + signatureToProcess = signatureParts[0].concat("=").concat(signatureValue); + } catch (Exception e) { + log.debug("Could not convert EC signature to valid format"); + } + return signatureToProcess; + } + + private static void isSignatureAlgorithmNotNull(SignatureAlgorithm signatureAlgorithm) + throws LollipopSignatureException { + if (signatureAlgorithm == null) { + throw new LollipopSignatureException( + LollipopSignatureException.ErrorCode.INVALID_SIGNATURE_ALG, + "Missing Signature Algorithm"); + } + } + + private static void isLollipopKeyNotNull(String lollipopKey) throws LollipopSignatureException { + if (lollipopKey == null) { + throw new LollipopSignatureException( + LollipopSignatureException.ErrorCode.MISSING_PUBLIC_KEY, + "Could not find the public key within the expected header"); + } + } + + private static void verifySignatureLength(String[] signatures, String[] signatureInputs) + throws LollipopSignatureException { + if (signatures.length != signatureInputs.length) { + throw new LollipopSignatureException( + LollipopSignatureException.ErrorCode.INVALID_SIGNATURE_NUMBER, + "Available signatures and signature-inputs differ in number"); + } + } +} diff --git a/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierFactory.java b/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierFactory.java new file mode 100644 index 00000000..c4362a84 --- /dev/null +++ b/http-verifier/src/main/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierFactory.java @@ -0,0 +1,38 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.http_verifier.visma; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.exception.LollipopVerifierException; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifier; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import java.nio.charset.Charset; + +/** Implements {@link HttpMessageVerifierFactory} with Visma-AutoPay http-signature library */ +public class VismaHttpMessageVerifierFactory implements HttpMessageVerifierFactory { + + private final String defaultEncoding; + private final LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + + public VismaHttpMessageVerifierFactory( + String defaultEncoding, LollipopConsumerRequestConfig lollipopConsumerRequestConfig) + throws LollipopVerifierException { + if (Charset.availableCharsets().get(defaultEncoding) == null) { + throw new LollipopVerifierException( + LollipopVerifierException.ErrorCode.UNAVAILABLE_ENCODING, + "Unavailable Encoding: " + defaultEncoding); + } + this.defaultEncoding = defaultEncoding; + this.lollipopConsumerRequestConfig = lollipopConsumerRequestConfig; + } + + /** + * {@inheritDoc} + * + * @return instance of {@link VismaHttpMessageVerifierFactory}, passing the configured default + * encoding + */ + @Override + public HttpMessageVerifier create() { + return new VismaHttpMessageVerifier(defaultEncoding, lollipopConsumerRequestConfig); + } +} diff --git a/http-verifier/src/test/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierFactoryTest.java b/http-verifier/src/test/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierFactoryTest.java new file mode 100644 index 00000000..9525b1a9 --- /dev/null +++ b/http-verifier/src/test/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierFactoryTest.java @@ -0,0 +1,50 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.http_verifier.visma; + +import static org.assertj.core.api.Assertions.*; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class VismaHttpMessageVerifierFactoryTest { + + LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + + @BeforeAll + public void init() { + lollipopConsumerRequestConfig = LollipopConsumerRequestConfig.builder().build(); + } + + @Test + void encodingInCostructorIsInvalid() { + assertThatThrownBy( + () -> + new VismaHttpMessageVerifierFactory( + "UTF-326", lollipopConsumerRequestConfig)) + .isInstanceOfSatisfying( + Exception.class, + e -> assertThat(e).hasMessageContaining("Unavailable Encoding: UTF-326")); + } + + @Test + void encodingInCostructorIsValid() { + assertThatNoException() + .isThrownBy( + () -> + new VismaHttpMessageVerifierFactory( + "UTF-8", lollipopConsumerRequestConfig)); + } + + @SneakyThrows + @Test + void instanceIsCreated() { + assertThat( + new VismaHttpMessageVerifierFactory("UTF-8", lollipopConsumerRequestConfig) + .create()) + .isInstanceOf(VismaHttpMessageVerifier.class); + } +} diff --git a/http-verifier/src/test/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierTest.java b/http-verifier/src/test/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierTest.java new file mode 100644 index 00000000..7106b14c --- /dev/null +++ b/http-verifier/src/test/java/it/pagopa/tech/lollipop/consumer/http_verifier/visma/VismaHttpMessageVerifierTest.java @@ -0,0 +1,372 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.http_verifier.visma; + +import static org.assertj.core.api.Assertions.*; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.exception.LollipopDigestException; +import it.pagopa.tech.lollipop.consumer.exception.LollipopSignatureException; +import java.io.UnsupportedEncodingException; +import java.util.HashMap; +import java.util.Map; +import net.visma.autopay.http.digest.DigestException; +import org.junit.jupiter.api.Test; + +class VismaHttpMessageVerifierTest { + + public VismaHttpMessageVerifier vismaDigestVerifier = + new VismaHttpMessageVerifier("UTF-8", LollipopConsumerRequestConfig.builder().build()); + + @Test + void correctDigestIsVerified() { + // setup + var content = new String(new byte[] {1, 2, 4}); + var header = + "md5=:V9tg6T+1JldSH4+Zy8c5jw==:,sha-256=:1LKaloxAFzY43tjRdMhpV6+iEb5HnO4CDbpd/hJ9kco=:"; + + // execute & verify + assertThatNoException() + .isThrownBy(() -> vismaDigestVerifier.verifyDigest(header, content, null)); + } + + @Test + void invalidDigestIsDetected() { + // setup + var content = new String(new byte[] {1, 2, 4}); + var header = "sha-256=:A5BYxvLAy0ksUzsKTRTvd8wPeKvMztUofYShogEc+4E=:"; + + // execute & verify + assertThatThrownBy(() -> vismaDigestVerifier.verifyDigest(header, content, null)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e.getCause()).hasMessageContaining("different"); + assertThat(((DigestException) e.getCause()).getErrorCode()) + .isEqualTo(DigestException.ErrorCode.INCORRECT_DIGEST); + }); + } + + @Test + void malformedDigestIsDetected() { + // setup + var content = new String(new byte[] {1, 2, 4}); + var header = "sha-256=1LKaloxAFzY43tjRdMhpV6+iEb5HnO4CDbpd/hJ9kco="; + + // execute & verify + assertThatThrownBy(() -> vismaDigestVerifier.verifyDigest(header, content, null)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e.getCause()).hasMessageContaining("parsing"); + assertThat(((DigestException) e.getCause()).getErrorCode()) + .isEqualTo(DigestException.ErrorCode.INVALID_HEADER); + }); + } + + @Test + void unsupportedAlgorithmsAreDetected() { + // setup + var content = new String(new byte[] {1, 2, 4}); + var header = "md5=:V9tg6T+1JldSH4+Zy8c5jw==: ,sha=:q3kRUT3rxwFa1QQpqBWXcUWLJM4=:"; + + // execute & verify + assertThatThrownBy(() -> vismaDigestVerifier.verifyDigest(header, content, null)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e.getCause()).hasMessageContaining("Unsupported"); + assertThat(((DigestException) e.getCause()).getErrorCode()) + .isEqualTo(DigestException.ErrorCode.UNSUPPORTED_ALGORITHM); + }); + } + + @Test + void emptyHeaderIsDetected() { + // setup + var content = new String(new byte[] {1, 2, 4}); + var header = ""; + + // execute & verify + assertThatThrownBy(() -> vismaDigestVerifier.verifyDigest(header, content, null)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e).hasMessageContaining("Empty"); + assertThat(((DigestException) e.getCause()).getErrorCode()) + .isEqualTo(DigestException.ErrorCode.INVALID_HEADER); + }); + } + + @Test + void invalidDictionaryValuesAreDetected() { + // setup + var content = new String(new byte[] {1, 2, 4}); + var header = "sha-256=ok"; + + // execute & verify + assertThatThrownBy(() -> vismaDigestVerifier.verifyDigest(header, content, null)) + .isInstanceOfSatisfying( + LollipopDigestException.class, + e -> { + assertThat(e.getCause()).hasMessageContaining("Invalid"); + assertThat(((DigestException) e.getCause()).getErrorCode()) + .isEqualTo(DigestException.ErrorCode.INVALID_HEADER); + }); + } + + @Test + void invalidContentEncoding() { + // setup + var content = new String(new byte[] {1, 2, 4}); + var header = "sha-256=ok"; + + // execute & verify + assertThatThrownBy(() -> vismaDigestVerifier.verifyDigest(header, content, "UTF-326")) + .isInstanceOf(UnsupportedEncodingException.class); + } + + @Test + void validLollipopSignatureCheckSingleEcdaSha256() { + + String signatureInput = + "sig123=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + var signature = + "sig123=:lTuoRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:"; + + Map requestHeaders = + new HashMap<>( + Map.of( + "Content-Digest", + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:", + "x-pagopa-lollipop-original-url", + "https://api-app.io.pagopa.it/first-lollipop/sign", + "x-pagopa-lollipop-original-method", + "POST", + "x-pagopa-lollipop-public-key", + "eyJrdHkiOiJFQyIsIngiOiJGcUZEd" + + "XdFZ3U0TVVYRVJQTVZMLTg1cEd2MkQzWW1MNEoxZ2ZNa2RiYzI0IiwieSI6Im" + + "hkVjBveG1XRlN4TW9KVURwZGlocjc2clM4VlJCRXFNRmViWXlBZks5LWsiLCJjcnYiO" + + "iJQLTI1NiJ9", + "Signature-Input", + signatureInput, + "Signature", + signature)); + + // execute & verify + assertThatNoException() + .isThrownBy( + () -> + vismaDigestVerifier.verifyHttpSignature( + signature, signatureInput, requestHeaders)); + } + + @Test + void invalidLollipopSignatureCheck() { + + String signatureInput = + "sig123=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + var signature = + "sig123=:lTuoRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:"; + + Map requestHeaders = + new HashMap<>( + Map.of( + "Content-Digest", + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Za=:", + "x-pagopa-lollipop-original-url", + "https://api-app.io.pagopa.it/first-lollipop/sign", + "x-pagopa-lollipop-original-method", + "POST", + "x-pagopa-lollipop-public-key", + "eyJrdHkiOiJFQyIsIngiOiJGcUZEd" + + "XdFZ3U0TVVYRVJQTVZMLTg1cEd2MkQzWW1MNEoxZ2ZNa2RiYzI0IiwieSI6Im" + + "hkVjBveG1XRlN4TW9KVURwZGlocjc2clM4VlJCRXFNRmViWXlBZks5LWsiLCJjcnYiO" + + "iJQLTI1NiJ9", + "Signature-Input", + signatureInput, + "Signature", + signature)); + + // execute & verify + assertThatThrownBy( + () -> + vismaDigestVerifier.verifyHttpSignature( + signature, signatureInput, requestHeaders)) + .isInstanceOfSatisfying( + LollipopSignatureException.class, + e -> { + assertThat(e).hasMessageContaining("The provided signature is invalid"); + assertThat(e.getErrorCode()) + .isEqualTo( + LollipopSignatureException.ErrorCode.INVALID_SIGNATURE); + }); + } + + @Test + void validLollipopSignatureCheckSingleRsaSha256() { + + String signatureInput = + "sig1=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678814391;nonce=\"aNonce\";" + + "alg=\"rsa-pss-sha256\";keyid=\"sha256-A3OhKGLYwSvdJ2txHi_SGQ3G-sHLh2Ibu91ErqFx_58\""; + var signature = + "sig1=:Jf7v1wqk4bWDZzS0aqbA8VIYxBD07KkrhVmf8ncqsCCpgtggKzVpuwzsxJGDaxqw1sQ/4/9q3JviW7cV0Iq1EbFPiX" + + "kW9j9F+JPNt+pPZCjTrcHzKSZ+Yz+MYttSS/umR0YdCPdkObu28HyZ1hcTgt2xSqyYpjxX9CPcjHn42tVJBF6Kfmxn" + + "AdcYH3vjFj30QPRyMUjQEH9FEQItcxP7H4P9vXsHsKi2o3NFwgl8Lq5zCOMURbM4BtgxJwVh97MJzqPVJEq3isEa60h" + + "quPIdIjPoL9tgMEZkbERHZzqg3KivS9cjdQ7VsWWdwu8S2mPbRVK7SAyhEpk+hnmpxg24Uw==:"; + + Map requestHeaders = + new HashMap<>( + Map.of( + "Content-Digest", + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Za=:", + "x-pagopa-lollipop-original-url", + "https://api-app.io.pagopa.it/first-lollipop/sign", + "x-pagopa-lollipop-original-method", + "POST", + "x-pagopa-lollipop-public-key", + "eyJrdHkiOiJSU0EiLCJlIjoiQVFBQiIsImtpZCI6InRlc3Qta2V5LXJzYS1wc3MiLCJuIjoicjR0bW0zc" + + "jIwV2RfUGJxdlAxczItUUV0dnB1UmFWOFlxNDBnalVSOHkyUmp4YTZkcEcyR1hIYlBmdk0gIHM4Y3Q" + + "tTGgxR0g0NXgyOFJ3M1J5NTNtbS1vQVhqeVE4Nk9uRGtaNU44bFliZ2dENE8zdzZNNnBBdkxraGs5NU" + + "FuICBkVHJpZmJJRlBOVThQUE1PN095ckZBSHFnRHN6bmpQRm1UT3RDRWNOMloxRnBXZ2Nod3VZTFBMLV" + + "dva3FsdGQxMSAgbnFxemktYko5Y3ZTS0FEWWRVQUFONVdVdHpkcGl5NkxiVGdTeFA3b2NpVTRUbjBnNU" + + "k2YURaSjdBOEx6bzBLU3kgIFpZb0E0ODVtcWNPMEdWQWRWdzlscTRhT1Q5djZkLW5iNGJuTmtRVmtsTFE" + + "zZlZBdkptLXhkRE9wOUxDTkNONDhWICAycG5ET2tGVjYtVTluVjVveWM2WEkydyJ9", + "Signature-Input", + signatureInput, + "Signature", + signature)); + + // execute & verify + assertThatNoException() + .isThrownBy( + () -> + vismaDigestVerifier.verifyHttpSignature( + signature, signatureInput, requestHeaders)); + } + + @Test + void validLollipopMultipleSignatureCheckEcdaSha256() { + + String signatureInput = + "sig1=(\"x-io-sign-qtspclauses\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\"," + + " sig2=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + var signature = + "sig1=:dncsEeKERA9wzxBO0vbPIueMK7Izk4zZNX4D0jI+t17XQJ5YrhumR3MGvMiyarb+B8MPqn+rbOJwZt6dV+oXFA==:," + + " sig2=:nbmFduqX8AdhXzqkFX+UIvicn3ZV5yZXqUO+3bceOT8WFPXRTVRcoOcjF+0+W5KLihAZjSW5GXSgCxVVEW8pqQ==:"; + + Map requestHeaders = + new HashMap<>( + Map.of( + "X-io-sign-qtspclauses", + "anIoSignClauses", + "Content-Digest", + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:", + "x-pagopa-lollipop-original-url", + "https://api-app.io.pagopa.it/first-lollipop/sign", + "x-pagopa-lollipop-original-method", + "POST", + "x-pagopa-lollipop-public-key", + "eyJrdHkiOiJFQyIsIngiOiJGcUZEd" + + "XdFZ3U0TVVYRVJQTVZMLTg1cEd2MkQzWW1MNEoxZ2ZNa2RiYzI0IiwieSI6Im" + + "hkVjBveG1XRlN4TW9KVURwZGlocjc2clM4VlJCRXFNRmViWXlBZks5LWsiLCJjcnYiO" + + "iJQLTI1NiJ9")); + + // execute & verify + assertThatNoException() + .isThrownBy( + () -> + vismaDigestVerifier.verifyHttpSignature( + signature, signatureInput, requestHeaders)); + } + + @Test + void invalidLollipopMultipleSignatureWithLessInput() { + + String signatureInput = + "sig1=(\"x-io-sign-qtspclauses\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\"," + + " sig2=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + var signature = + "sig1=:dncsEeKERA9wzxBO0vbPIueMK7Izk4zZNX4D0jI+t17XQJ5YrhumR3MGvMiyarb+B8MPqn+rbOJwZt6dV+oXFA==:"; + + Map requestHeaders = + new HashMap<>( + Map.of( + "X-io-sign-qtspclauses", + "anIoSignClauses", + "Content-Digest", + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:", + "x-pagopa-lollipop-original-url", + "https://api-app.io.pagopa.it/first-lollipop/sign", + "x-pagopa-lollipop-original-method", + "POST", + "x-pagopa-lollipop-public-key", + "eyJrdHkiOiJFQyIsIngiOiJGcUZEd" + + "XdFZ3U0TVVYRVJQTVZMLTg1cEd2MkQzWW1MNEoxZ2ZNa2RiYzI0IiwieSI6Im" + + "hkVjBveG1XRlN4TW9KVURwZGlocjc2clM4VlJCRXFNRmViWXlBZks5LWsiLCJjcnYiO" + + "iJQLTI1NiJ9")); + + // execute & verify + assertThatThrownBy( + () -> + vismaDigestVerifier.verifyHttpSignature( + signature, signatureInput, requestHeaders)) + .isInstanceOfSatisfying( + LollipopSignatureException.class, + e -> { + assertThat(e) + .hasMessageContaining( + "Available signatures and signature-inputs differ in" + + " number"); + assertThat(e.getErrorCode()) + .isEqualTo( + LollipopSignatureException.ErrorCode + .INVALID_SIGNATURE_NUMBER); + }); + } + + @Test + void validLollipopSignatureCheckSingleEcdaSha256WithDer() { + + String signatureInput = + "sig1=(\"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1681473980;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-HiNolL87UYKQfaKISwIzyWY4swKPUzpaOWJCxaHy89M\""; + var signature = + "sig1=:MEUCIFiZHxuLhk2Jlt46E5kbB8hCx7fN7QeeAj2gaSK3Y+WzAiEAtggj3Jwu8RbTGdNmsDix2zymh0gKwKxoPlolL7j6VTg=:"; + + Map requestHeaders = + new HashMap<>( + Map.of( + "x-pagopa-lollipop-assertion-ref", + "sha256-HiNolL87UYKQfaKISwIzyWY4swKPUzpaOWJCxaHy89M", + "x-pagopa-lollipop-assertion-type", + "SAML", + "x-pagopa-lollipop-user-id", + "aFiscalCode", + "x-pagopa-lollipop-public-key", + "eyJrdHkiOiJFQyIsInkiOiJNdkVCMENsUHFnTlhrNVhIYm9xN1hZUnE2TnJTQkFTVmZhT2wzWnAxQmJzPSIsImNydiI6IlAtMjU2IiwieCI6InF6YTQzdGtLTnIrYWlTZFdNL0Q1cTdxMElmV3lZVUFIVEhSNng3dFByZEU9In0", + "x-pagopa-lollipop-auth-jwt", + "aValidJWT", + "x-pagopa-lollipop-original-method", + "POST", + "x-pagopa-lollipop-original-url", + "https://api-app.io.pagopa.it/first-lollipop/sign", + "content-digest", + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:", + "Signature-Input", + signatureInput, + "Signature", + signature)); + + // execute & verify + assertThatNoException() + .isThrownBy( + () -> + vismaDigestVerifier.verifyHttpSignature( + signature, signatureInput, requestHeaders)); + } +} diff --git a/identity-service-rest-client-native/README.md b/identity-service-rest-client-native/README.md new file mode 100644 index 00000000..0795983b --- /dev/null +++ b/identity-service-rest-client-native/README.md @@ -0,0 +1,38 @@ +# Identity service rest client +This module is used to obtain the identity provider certification data. + +The parameters needed to find the right certificate are the entity id and the assertion's issue instant, +both retrieved from the assertion. + +First we fetch the certificates' tag list (a list of certification by their issue instant) and we find the two tags +that could be used for the assertion verification; we compare the tags with the assertion's instant as timestamp in seconds and +the two eligible tags are the ones right before and after the provided assertion's instant. + +Then for each of the found tags the corresponding certification as xml is fetched, +the xml contains various elements called EntityDescriptor for each entity id, +so we will filter these elements by the entity id provided in first instance. + +Finally, from the EntityDescriptor of the right identity provider we will extract the signature that will be used to +verify the assertion. + +## Configuration +The client uri, endpoints and the entity id of the CIE identity provider are configurable and are configured by default as follows: + +| VARIABLE | DEFAULT VALUE | USAGE | +|---------------------|-----------------------------------------------------------------------|-------------------------------------------------------| +| cieEntityId | https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO | entity id of the CIE identity provider | +| baseUri | https://api.is.eng.pagopa.it | base uri of the api for retrieving the certifications | +| idpKeysCieEndpoint | /idp-keys/cie | endpoint for CIE certifications | +| idpKeysSpidEndpoint | /idp-keys/spid | endpoint for SPID certifications | + +## Example + +In order to create a new instance of the client using the provider and an instance of the configuration class. Note that +in order to use instances of a IdpCertStorageProvider and related configs (as described in the root Readme.md) should be +available for this implementation: + +``` +AssertionClientConfig config = AssertionSimpleClientConfig.builder().build(); +IdpCertSimpleClientProvider idpCertSimpleClientProvider = + new IdpCertSimpleClientProvider(config, idpCertStorageProvider, idpCertStorageConfig); +``` \ No newline at end of file diff --git a/identity-service-rest-client-native/build.gradle b/identity-service-rest-client-native/build.gradle new file mode 100644 index 00000000..773ce75a --- /dev/null +++ b/identity-service-rest-client-native/build.gradle @@ -0,0 +1,91 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'java-library' + id("io.freefair.lombok") version "8.0.0-rc4" + /*id("org.openapi.generator") version "6.5.0"*/ +} + +group 'it.pagopa.tech.lollipop.sdk.impls' + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } +} + +configurations { + implementation { + attributes { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-core-jar')) + } + } +} + +abstract class InstrumentedJarsRule implements AttributeCompatibilityRule { + + @Override + void execute(CompatibilityCheckDetails details) { + if (details.consumerValue.name == 'instrumented-core-jar' && details.producerValue.name == 'jar') { + details.compatible() + } + } +} + +dependencies { + attributesSchema { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) { + compatibilityRules.add(InstrumentedJarsRule) + } + } + implementation project(path: ':core') + + implementation 'javax.annotation:javax.annotation-api:1.3.2' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2' + implementation 'org.openapitools:jackson-databind-nullable:0.2.6' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2' + implementation 'com.google.code.findbugs:jsr305:3.0.2' + + testImplementation 'org.mock-server:mockserver-client-java:5.15.0' + implementation 'javax.inject:javax.inject:1' + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.assertj:assertj-core:3.24.2' + + testImplementation 'org.mockito:mockito-core:5.2.0' + testImplementation 'org.mockito:mockito-junit-jupiter:5.2.0' + //Mockserver for testing api + testImplementation 'org.mock-server:mockserver-netty:5.15.0' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} + +/*compileJava.dependsOn tasks.openApiGenerate + +openApiGenerate { + generatorName = "java" + inputSpec = "$projectDir/openapi/openapi-spec.yml" + outputDir = "$projectDir/generated" + configOptions = [ + dataLibrary : "java8", + library : "native", + useRuntimeException: "true", + sourceFolder : "build/generated/sources/" + ] +}*/ diff --git a/identity-service-rest-client-native/openapi/openapi-spec.yml b/identity-service-rest-client-native/openapi/openapi-spec.yml new file mode 100644 index 00000000..86a07ede --- /dev/null +++ b/identity-service-rest-client-native/openapi/openapi-spec.yml @@ -0,0 +1,250 @@ +openapi: "3.0.1" +info: + title: "identity-services" + version: "2022-09-06T20:08:39Z" + x-logo: + url: https://io.italia.it/assets/img/io-logo-blue.svg + description: |- + Client used to retrieve the public keys from the identity provider +servers: + - url: "https://api.is.eng.pagopa.it" + x-amazon-apigateway-endpoint-configuration: + disableExecuteApiEndpoint: true +paths: + /idp-keys/spid: + get: + responses: + "200": + description: "200 response" + content: + application/json: + schema: + $ref: '#/components/schemas/TagList' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '410': + description: Assertion gone + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + /idp-keys/cie: + get: + responses: + "200": + description: "200 response" + content: + application/json: + schema: + $ref: '#/components/schemas/TagList' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '410': + description: Assertion gone + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + /idp-keys/spid/{tag}: + get: + parameters: + - name: "tag" + in: "path" + required: true + schema: + type: "string" + responses: + "200": + description: "200 response" + content: + application/xml: + schema: + $ref: '#/components/schemas/CertData' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '410': + description: Assertion gone + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + /idp-keys/cie/{tag}: + get: + parameters: + - name: "tag" + in: "path" + required: true + schema: + type: "string" + responses: + "200": + description: "200 response" + content: + application/xml: + schema: + $ref: '#/components/schemas/CertData' + '400': + description: Bad Request + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '401': + description: Unauthorized + '403': + description: Forbidden + '404': + description: Not Found + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' + '410': + description: Assertion gone + '500': + description: Internal Server Error + content: + application/json: + schema: + $ref: '#/components/schemas/ProblemJson' +components: + schemas: + ProblemJson: + type: object + properties: + type: + type: string + format: uri + description: |- + An absolute URI that identifies the problem type. When dereferenced, + it SHOULD provide human-readable documentation for the problem type + (e.g., using HTML). + default: about:blank + example: https://example.com/problem/constraint-violation + title: + type: string + description: >- + A short, summary of the problem type. Written in english and + readable + + for engineers (usually not suited for non technical stakeholders and + + not localized); example: Service Unavailable + status: + type: integer + format: int32 + description: >- + The HTTP status code generated by the origin server for this + occurrence + + of the problem. + minimum: 100 + maximum: 600 + exclusiveMaximum: true + example: 200 + detail: + type: string + description: |- + A human readable explanation specific to this occurrence of the + problem. + example: There was an error processing the request + instance: + type: string + format: uri + description: >- + An absolute URI that identifies the specific occurrence of the + problem. + + It may or may not yield further information if dereferenced. + TagList: + type: array + items: + type: string + EntityDescriptor: + type: object + properties: + entityID: + type: string + xml: + attribute: true + required: + - entityID + SPIDCertData: + type: object + properties: + entitiesDescriptor: + type: object + properties: + entityDescriptor: + type: array + items: + $ref: '#/components/schemas/EntityDescriptor' + xml: + name: "EntityDescriptor" + prefix: "md" + namespace: "urn:oasis:names:tc:SAML:2.0:metadata" + xml: + name: "EntitiesDescriptor" + prefix: "md" + namespace: "urn:oasis:names:tc:SAML:2.0:metadata" + CIECertData: + type: object + properties: + entityDescriptor: + type: array + items: + $ref: '#/components/schemas/EntityDescriptor' + xml: + name: "EntityDescriptor" + CertData: + oneOf: + - $ref: '#/components/schemas/CIECertData' + - $ref: '#/components/schemas/SPIDCertData' \ No newline at end of file diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClient.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClient.java new file mode 100644 index 00000000..84625578 --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClient.java @@ -0,0 +1,263 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple; + +import it.pagopa.tech.lollipop.consumer.exception.*; +import it.pagopa.tech.lollipop.consumer.idp.client.IdpCertClient; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.exception.TagListSearchOutOfBoundException; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.ApiClient; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.ApiException; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.api.DefaultApi; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model.CertData; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model.EntitiesDescriptor; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model.EntityDescriptor; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorage; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import javax.inject.Inject; + +public class IdpCertSimpleClient implements IdpCertClient { + + private final DefaultApi defaultApi; + + private final IdpCertSimpleClientConfig entityConfig; + private final IdpCertStorage storage; + + @Inject + public IdpCertSimpleClient( + ApiClient client, IdpCertSimpleClientConfig entityConfig, IdpCertStorage storage) { + this.defaultApi = new DefaultApi(client); + this.entityConfig = entityConfig; + this.storage = storage; + } + + /** + * Retrieve the certification data of the given entityId issued in the same timeframe as the + * issue instant of the SAML assertion, first looking in the storage if enabled ({@link + * IdpCertStorageConfig}) and then, if not found, through the client {@link IdpCertClient}. If + * the storage is enabled ({@link IdpCertStorageConfig}) the IdpCertData will be stored, after + * being retrieved by the client. + * + * @param entityId Identity Provider ID + * @param instant Assertion Issue Instant + * @return the certifications issued before and after the timestamp instant + * @throws CertDataNotFoundException if an error occurred retrieving the certification XML or if + * data for the given entityId were not found + */ + @Override + public List getCertData(String entityId, String instant) + throws CertDataNotFoundException { + List listCertData = new ArrayList<>(); + + if (entityId == null || instant == null || entityId.isBlank() || instant.isBlank()) { + throw new IllegalArgumentException("EntityID or Assertion Issue Instant missing"); + } + + if (entityConfig.getCieEntityId().contains(entityId)) { + getCieCerts(entityId, instant, listCertData); + } else { + getSpidCerts(entityId, instant, listCertData); + } + return listCertData; + } + + private void getCieCerts(String entityId, String instant, List listCertData) + throws CertDataNotFoundException { + List tagList; + try { + tagList = getCIETagList(instant); + } catch (ApiException + | TagListSearchOutOfBoundException + | InvalidInstantFormatException e) { + throw new CertDataNotFoundException( + "Error retrieving certificate's tag list: " + e.getMessage(), e); + } + + for (String tag : tagList) { + try { + String storageTag = codifyStorageTag(tag, entityId); + IdpCertData certData = storage.getIdpCertData(storageTag); + + if (certData == null) { + certData = getCIECertData(tag, entityId); + } else { + storage.saveIdpCertData(storageTag, certData); + } + + listCertData.add(certData); + } catch (ApiException | EntityIdNotFoundException e) { + throw new CertDataNotFoundException( + "Error retrieving certificate data for tag " + tag + ": " + e.getMessage(), + e); + } + } + } + + private void getSpidCerts(String entityId, String instant, List listCertData) + throws CertDataNotFoundException { + List tagList; + try { + tagList = getSPIDTagList(instant); + } catch (ApiException + | TagListSearchOutOfBoundException + | InvalidInstantFormatException e) { + throw new CertDataNotFoundException( + "Error retrieving certificate's tag list: " + e.getMessage(), e); + } + + for (String tag : tagList) { + try { + String storageTag = codifyStorageTag(tag, entityId); + IdpCertData certData = storage.getIdpCertData(codifyStorageTag(tag, entityId)); + + if (certData == null) { + certData = getSPIDCertData(tag, entityId); + } else { + storage.saveIdpCertData(storageTag, certData); + } + listCertData.add(certData); + } catch (ApiException | EntityIdNotFoundException e) { + throw new CertDataNotFoundException( + "Error retrieving certificate data for tag " + tag + ": " + e.getMessage(), + e); + } + } + } + + private List getSPIDTagList(String instant) + throws TagListSearchOutOfBoundException, InvalidInstantFormatException { + List responseAssertion; + + responseAssertion = this.defaultApi.idpKeysSpidGet(); + + return getTagsFromInstant(responseAssertion, instant); + } + + private IdpCertData getSPIDCertData(String tag, String entityId) + throws EntityIdNotFoundException { + CertData responseAssertion = null; + + responseAssertion = this.defaultApi.idpKeysSpidTagGet(tag); + + return getEntityData( + ((EntitiesDescriptor) responseAssertion.getActualInstance()), tag, entityId); + } + + private List getCIETagList(String instant) + throws TagListSearchOutOfBoundException, InvalidInstantFormatException { + List responseAssertion; + + responseAssertion = this.defaultApi.idpKeysCieGet(); + + return getTagsFromInstant(responseAssertion, instant); + } + + private IdpCertData getCIECertData(String tag, String entityId) + throws EntityIdNotFoundException { + CertData responseAssertion; + + responseAssertion = this.defaultApi.idpKeysCieTagGet(tag); + + return getEntityData( + ((EntitiesDescriptor) responseAssertion.getActualInstance()), tag, entityId); + } + + private IdpCertData getEntityData(EntitiesDescriptor data, String tag, String entityId) + throws EntityIdNotFoundException { + IdpCertData newData = new IdpCertData(); + + for (EntityDescriptor entity : data.getEntityList()) { + if (entity.getEntityID().equals(entityId)) { + newData.setEntityId(entityId); + newData.setTag(tag); + newData.setCertData(entity.getSignatureList()); + + return newData; + } + } + + throw new EntityIdNotFoundException("Cert for entityID " + entityId + " not found"); + } + + private List getTagsFromInstant(List tagList, String instant) + throws TagListSearchOutOfBoundException, InvalidInstantFormatException { + List newTagList = new ArrayList<>(); + String latest = "latest"; + + long longInstant = getLongInstant(instant); + + boolean latestRemoved = tagList.remove(latest); + + Collections.sort(tagList); + + ifLatestRemovedAddLatest(tagList, latest, latestRemoved); + + int index = tagList.size() / 2; + + boolean notFound = true; + while (notFound) { + try { + if (isTagListAlreadyFiltered(tagList, latest, longInstant)) { + return tagList; + } + + String upperTag = tagList.get(index); + String lowerTag = tagList.get(index - 1); + if (upperTagIsHigherOrLatest(latest, longInstant, upperTag)) { + if (longInstant >= Long.parseLong(lowerTag)) { + notFound = false; + newTagList.add(upperTag); + newTagList.add(lowerTag); + } else { + index -= 1; + } + } else { + index += 1; + } + } catch (Exception e) { + throw new TagListSearchOutOfBoundException( + "Error finding the tags relative to assertion instant " + instant); + } + } + + return newTagList; + } + + private static boolean isTagListAlreadyFiltered( + List tagList, String latest, long longInstant) { + if (tagList.size() <= 2) { + String firstTimestamp = tagList.get(0); + return firstTimestamp.equals(latest) || Long.parseLong(firstTimestamp) <= longInstant; + } + return false; + } + + private static long getLongInstant(String instant) throws InvalidInstantFormatException { + long longInstant; + try { + longInstant = Long.parseLong(instant); + } catch (Exception e) { + throw new InvalidInstantFormatException( + "The given instant " + instant + " is not a valid timestamp"); + } + return longInstant; + } + + private static boolean upperTagIsHigherOrLatest( + String latest, long longInstant, String upperTag) { + return upperTag.equals(latest) || longInstant <= Long.parseLong(upperTag); + } + + private static void ifLatestRemovedAddLatest( + List tagList, String latest, boolean latestRemoved) { + if (latestRemoved) { + tagList.add(latest); + } + } + + private String codifyStorageTag(String tag, String entityId) { + return tag + entityId; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientConfig.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientConfig.java new file mode 100644 index 00000000..122984ef --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientConfig.java @@ -0,0 +1,25 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Builder +@Data +@NoArgsConstructor +@AllArgsConstructor +public class IdpCertSimpleClientConfig { + + @Builder.Default + private List cieEntityId = + List.of("https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO"); + + @Builder.Default private String baseUri = "https://api.is.eng.pagopa.it"; + + @Builder.Default private String idpKeysCieEndpoint = "/idp-keys/cie"; + + @Builder.Default private String idpKeysSpidEndpoint = "/idp-keys/spid"; +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientProvider.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientProvider.java new file mode 100644 index 00000000..161acd40 --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientProvider.java @@ -0,0 +1,39 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple; + +import it.pagopa.tech.lollipop.consumer.idp.client.IdpCertClient; +import it.pagopa.tech.lollipop.consumer.idp.client.IdpCertClientProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.ApiClient; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageProvider; +import javax.inject.Inject; + +/** Provider class for retrieving an instance of {@link IdpCertSimpleClient} */ +public class IdpCertSimpleClientProvider implements IdpCertClientProvider { + + private final IdpCertSimpleClientConfig idpClientConfig; + private final IdpCertStorageProvider idpCertStorageProvider; + private final IdpCertStorageConfig idpCertStorageConfig; + + @Inject + public IdpCertSimpleClientProvider( + IdpCertSimpleClientConfig config, + IdpCertStorageProvider idpCertStorageProvider, + IdpCertStorageConfig idpCertStorageConfig) { + this.idpClientConfig = config; + this.idpCertStorageProvider = idpCertStorageProvider; + this.idpCertStorageConfig = idpCertStorageConfig; + } + /** + * Provide an instance of {@link IdpCertSimpleClient} + * + * @return {@link IdpCertSimpleClient} + */ + @Override + public IdpCertClient provideClient() { + return new IdpCertSimpleClient( + new ApiClient(this.idpClientConfig), + this.idpClientConfig, + idpCertStorageProvider.provideStorage(idpCertStorageConfig)); + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/exception/TagListSearchOutOfBoundException.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/exception/TagListSearchOutOfBoundException.java new file mode 100644 index 00000000..1561bf7e --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/exception/TagListSearchOutOfBoundException.java @@ -0,0 +1,14 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.exception; + +public class TagListSearchOutOfBoundException extends Exception { + + /** + * Constructs new exception with provided message + * + * @param message Detail message + */ + public TagListSearchOutOfBoundException(String message) { + super(message); + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiClient.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiClient.java new file mode 100644 index 00000000..b1c57be6 --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiClient.java @@ -0,0 +1,185 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal; + +import static java.nio.charset.StandardCharsets.UTF_8; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import java.io.InputStream; +import java.net.URI; +import java.net.URLEncoder; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.function.Consumer; +import lombok.Getter; +import org.openapitools.jackson.nullable.JsonNullableModule; + +/** + * Configuration and utility class for API clients. + * + *

This class can be constructed and modified, then used to instantiate the various API classes. + * The API classes use the settings in this class to configure themselves, but otherwise do not + * store a link to this class. + * + *

This class is mutable and not synchronized, so it is not thread-safe. The API classes + * generated from this are immutable and thread-safe. + * + *

The setter methods of this class return the current object to facilitate a fluent style of + * configuration. + */ +@Getter +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-11T16:21:49.277208500+02:00[Europe/Paris]") +public class ApiClient { + + private HttpClient.Builder builder; + private ObjectMapper mapper; + private XmlMapper xmlMapper; + private String scheme; + private String host; + private int port; + private String basePath; + private Consumer interceptor; + private Consumer> responseInterceptor; + private Consumer> asyncResponseInterceptor; + private Duration readTimeout; + private Duration connectTimeout; + private String idpKeysCieEndpoint; + private String idpKeysSpidEndpoint; + + /** + * URL encode a string in the UTF-8 encoding. + * + * @param s String to encode. + * @return URL-encoded representation of the input string. + */ + public static String urlEncode(String s) { + return URLEncoder.encode(s, UTF_8).replaceAll("\\+", "%20"); + } + + /** Create an instance of ApiClient. */ + public ApiClient(IdpCertSimpleClientConfig config) { + this.builder = createDefaultHttpClientBuilder(); + this.mapper = createDefaultObjectMapper(); + this.xmlMapper = createDefaultXmlMapper(); + updateBaseUri(config.getBaseUri()); + interceptor = null; + readTimeout = null; + connectTimeout = null; + responseInterceptor = null; + asyncResponseInterceptor = null; + this.idpKeysCieEndpoint = config.getIdpKeysCieEndpoint(); + this.idpKeysSpidEndpoint = config.getIdpKeysSpidEndpoint(); + } + + protected ObjectMapper createDefaultObjectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + mapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + mapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + mapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + mapper.registerModule(new JavaTimeModule()); + mapper.registerModule(new JsonNullableModule()); + return mapper; + } + + protected XmlMapper createDefaultXmlMapper() { + XmlMapper mapper = new XmlMapper(); + return mapper; + } + + protected HttpClient.Builder createDefaultHttpClientBuilder() { + return HttpClient.newBuilder(); + } + + public void updateBaseUri(String baseUri) { + URI uri = URI.create(baseUri); + scheme = uri.getScheme(); + host = uri.getHost(); + port = uri.getPort(); + basePath = uri.getRawPath(); + } + + /** + * Get an {@link HttpClient} based on the current {@link HttpClient.Builder}. + * + *

The returned object is immutable and thread-safe. + * + * @return The HTTP client. + */ + public HttpClient getHttpClient() { + return builder.build(); + } + + /** + * Get a copy of the current {@link ObjectMapper}. + * + * @return A copy of the current object mapper. + */ + public ObjectMapper getObjectMapper() { + return mapper.copy(); + } + + public ApiClient setXmlMapper(XmlMapper mapper) { + this.xmlMapper = mapper; + return this; + } + + /** + * Get the base URI to resolve the endpoint paths against. + * + * @return The complete base URI that the rest of the API parameters are resolved against. + */ + public String getBaseUri() { + return scheme + "://" + host + (port == -1 ? "" : ":" + port) + basePath; + } + + /** + * Get the custom interceptor. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer getRequestInterceptor() { + return interceptor; + } + + /** + * Get the custom response interceptor. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer> getResponseInterceptor() { + return responseInterceptor; + } + + /** + * Get the custom async response interceptor. Use this interceptor when asyncNative is set to + * 'true'. + * + * @return The custom interceptor that was set, or null if there isn't any. + */ + public Consumer> getAsyncResponseInterceptor() { + return asyncResponseInterceptor; + } + + /** + * Get the read timeout that was set. + * + * @return The read timeout, or null if no timeout was set. Null represents an infinite wait + * time. + */ + public Duration getReadTimeout() { + return readTimeout; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiException.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiException.java new file mode 100644 index 00000000..701b2acf --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiException.java @@ -0,0 +1,88 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal; + +import java.net.http.HttpHeaders; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-11T16:21:49.277208500+02:00[Europe/Paris]") +public class ApiException extends RuntimeException { + private int code = 0; + private HttpHeaders responseHeaders = null; + private String responseBody = null; + + public ApiException() {} + + public ApiException(Throwable throwable) { + super(throwable); + } + + public ApiException(String message) { + super(message); + } + + public ApiException( + String message, + Throwable throwable, + int code, + HttpHeaders responseHeaders, + String responseBody) { + super(message, throwable); + this.code = code; + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + public ApiException( + String message, int code, HttpHeaders responseHeaders, String responseBody) { + this(message, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException( + String message, Throwable throwable, int code, HttpHeaders responseHeaders) { + this(message, throwable, code, responseHeaders, null); + } + + public ApiException(int code, HttpHeaders responseHeaders, String responseBody) { + this((String) null, (Throwable) null, code, responseHeaders, responseBody); + } + + public ApiException(int code, String message) { + super(message); + this.code = code; + } + + public ApiException( + int code, String message, HttpHeaders responseHeaders, String responseBody) { + this(code, message); + this.responseHeaders = responseHeaders; + this.responseBody = responseBody; + } + + /** + * Get the HTTP status code. + * + * @return HTTP status code + */ + public int getCode() { + return code; + } + + /** + * Get the HTTP response headers. + * + * @return Headers as an HttpHeaders object + */ + public HttpHeaders getResponseHeaders() { + return responseHeaders; + } + + /** + * Get the HTTP response body. + * + * @return Response body in the form of string + */ + public String getResponseBody() { + return responseBody; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiResponse.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiResponse.java new file mode 100644 index 00000000..582e0a4d --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/ApiResponse.java @@ -0,0 +1,47 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal; + +import java.util.List; +import java.util.Map; + +/** + * API response returned by API call. + * + * @param The type of data that is deserialized from response body + */ +public class ApiResponse { + private final int statusCode; + private final Map> headers; + private final T data; + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + */ + public ApiResponse(int statusCode, Map> headers) { + this(statusCode, headers, null); + } + + /** + * @param statusCode The status code of HTTP response + * @param headers The headers of HTTP response + * @param data The object deserialized from response bod + */ + public ApiResponse(int statusCode, Map> headers, T data) { + this.statusCode = statusCode; + this.headers = headers; + this.data = data; + } + + public int getStatusCode() { + return statusCode; + } + + public Map> getHeaders() { + return headers; + } + + public T getData() { + return data; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/api/DefaultApi.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/api/DefaultApi.java new file mode 100644 index 00000000..6bf55bac --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/api/DefaultApi.java @@ -0,0 +1,342 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.api; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.ApiClient; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.ApiException; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.ApiResponse; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model.CertData; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.List; +import java.util.function.Consumer; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-11T16:21:49.277208500+02:00[Europe/Paris]") +public class DefaultApi { + private final HttpClient memberVarHttpClient; + private final ObjectMapper memberVarObjectMapper; + private final XmlMapper memberVarXMLMapper; + private final String memberVarBaseUri; + private final Consumer memberVarInterceptor; + private final Duration memberVarReadTimeout; + private final Consumer> memberVarResponseInterceptor; + private final Consumer> memberVarAsyncResponseInterceptor; + private final String memberIdpKeysCieEndpoint; + private final String memberIdpKeysSpidEndpoint; + + public DefaultApi(ApiClient apiClient) { + memberVarHttpClient = apiClient.getHttpClient(); + memberVarObjectMapper = apiClient.getObjectMapper(); + memberVarXMLMapper = apiClient.getXmlMapper(); + memberVarBaseUri = apiClient.getBaseUri(); + memberVarInterceptor = apiClient.getRequestInterceptor(); + memberVarReadTimeout = apiClient.getReadTimeout(); + memberVarResponseInterceptor = apiClient.getResponseInterceptor(); + memberVarAsyncResponseInterceptor = apiClient.getAsyncResponseInterceptor(); + memberIdpKeysCieEndpoint = apiClient.getIdpKeysCieEndpoint(); + memberIdpKeysSpidEndpoint = apiClient.getIdpKeysSpidEndpoint(); + } + + protected ApiException getApiException(String operationId, HttpResponse response) + throws IOException { + String body = response.body() == null ? null : new String(response.body().readAllBytes()); + String message = formatExceptionMessage(operationId, response.statusCode(), body); + return new ApiException(response.statusCode(), message, response.headers(), body); + } + + private String formatExceptionMessage(String operationId, int statusCode, String body) { + if (body == null || body.isEmpty()) { + body = "[no body]"; + } + return operationId + " call failed with: " + statusCode + " - " + body; + } + + /** + * @return List<String> + * @throws ApiException if fails to make API call + */ + public List idpKeysCieGet() throws ApiException { + ApiResponse> localVarResponse = idpKeysCieGetWithHttpInfo(); + return localVarResponse.getData(); + } + + /** + * @return ApiResponse<List<String>> + * @throws ApiException if fails to make API call + */ + public ApiResponse> idpKeysCieGetWithHttpInfo() throws ApiException { + HttpRequest.Builder localVarRequestBuilder = idpKeysCieGetRequestBuilder(); + try { + HttpResponse localVarResponse = + memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("idpKeysCieGet", localVarResponse); + } + return new ApiResponse>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null + ? null + : memberVarObjectMapper.readValue( + localVarResponse.body(), + new TypeReference< + List>() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder idpKeysCieGetRequestBuilder() throws ApiException { + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + memberIdpKeysCieEndpoint)); + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + /** + * @param tag (required) + * @return CertData + * @throws ApiException if fails to make API call + */ + public CertData idpKeysCieTagGet(String tag) throws ApiException { + ApiResponse localVarResponse = idpKeysCieTagGetWithHttpInfo(tag); + return localVarResponse.getData(); + } + + /** + * @param tag (required) + * @return ApiResponse<CertData> + * @throws ApiException if fails to make API call + */ + public ApiResponse idpKeysCieTagGetWithHttpInfo(String tag) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = idpKeysCieTagGetRequestBuilder(tag); + try { + HttpResponse localVarResponse = + memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("idpKeysCieTagGet", localVarResponse); + } + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null + ? null + : memberVarXMLMapper.readValue( + localVarResponse.body(), + new TypeReference() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder idpKeysCieTagGetRequestBuilder(String tag) throws ApiException { + // verify the required parameter 'tag' is set + if (tag == null) { + throw new ApiException( + 400, "Missing the required parameter 'tag' when calling idpKeysCieTagGet"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = + memberIdpKeysCieEndpoint + + "/{tag}".replace("{tag}", ApiClient.urlEncode(tag.toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/xml, application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + /** + * @return List<String> + * @throws ApiException if fails to make API call + */ + public List idpKeysSpidGet() throws ApiException { + ApiResponse> localVarResponse = idpKeysSpidGetWithHttpInfo(); + return localVarResponse.getData(); + } + + /** + * @return ApiResponse<List<String>> + * @throws ApiException if fails to make API call + */ + public ApiResponse> idpKeysSpidGetWithHttpInfo() throws ApiException { + HttpRequest.Builder localVarRequestBuilder = idpKeysSpidGetRequestBuilder(); + try { + HttpResponse localVarResponse = + memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("idpKeysSpidGet", localVarResponse); + } + return new ApiResponse>( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null + ? null + : memberVarObjectMapper.readValue( + localVarResponse.body(), + new TypeReference< + List>() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder idpKeysSpidGetRequestBuilder() throws ApiException { + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + memberIdpKeysSpidEndpoint)); + + localVarRequestBuilder.header("Accept", "application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } + + /** + * @param tag (required) + * @return CertData + * @throws ApiException if fails to make API call + */ + public CertData idpKeysSpidTagGet(String tag) throws ApiException { + ApiResponse localVarResponse = idpKeysSpidTagGetWithHttpInfo(tag); + return localVarResponse.getData(); + } + + /** + * @param tag (required) + * @return ApiResponse<CertData> + * @throws ApiException if fails to make API call + */ + public ApiResponse idpKeysSpidTagGetWithHttpInfo(String tag) throws ApiException { + HttpRequest.Builder localVarRequestBuilder = idpKeysSpidTagGetRequestBuilder(tag); + try { + HttpResponse localVarResponse = + memberVarHttpClient.send( + localVarRequestBuilder.build(), + HttpResponse.BodyHandlers.ofInputStream()); + if (memberVarResponseInterceptor != null) { + memberVarResponseInterceptor.accept(localVarResponse); + } + try { + if (localVarResponse.statusCode() / 100 != 2) { + throw getApiException("idpKeysSpidTagGet", localVarResponse); + } + return new ApiResponse( + localVarResponse.statusCode(), + localVarResponse.headers().map(), + localVarResponse.body() == null + ? null + : memberVarXMLMapper.readValue( + localVarResponse.body(), + new TypeReference() {}) // closes the InputStream + ); + } finally { + } + } catch (IOException e) { + throw new ApiException(e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new ApiException(e); + } + } + + private HttpRequest.Builder idpKeysSpidTagGetRequestBuilder(String tag) throws ApiException { + // verify the required parameter 'tag' is set + if (tag == null) { + throw new ApiException( + 400, "Missing the required parameter 'tag' when calling idpKeysSpidTagGet"); + } + + HttpRequest.Builder localVarRequestBuilder = HttpRequest.newBuilder(); + + String localVarPath = + memberIdpKeysSpidEndpoint + + "/{tag}".replace("{tag}", ApiClient.urlEncode(tag.toString())); + + localVarRequestBuilder.uri(URI.create(memberVarBaseUri + localVarPath)); + + localVarRequestBuilder.header("Accept", "application/xml, application/json"); + + localVarRequestBuilder.method("GET", HttpRequest.BodyPublishers.noBody()); + if (memberVarReadTimeout != null) { + localVarRequestBuilder.timeout(memberVarReadTimeout); + } + if (memberVarInterceptor != null) { + memberVarInterceptor.accept(localVarRequestBuilder); + } + return localVarRequestBuilder; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/AbstractOpenApiSchema.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/AbstractOpenApiSchema.java new file mode 100644 index 00000000..698fe790 --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/AbstractOpenApiSchema.java @@ -0,0 +1,135 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Map; +import java.util.Objects; + +/** Abstract class for oneOf,anyOf schemas defined in OpenAPI spec */ +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-11T16:21:49.277208500+02:00[Europe/Paris]") +public abstract class AbstractOpenApiSchema { + + // store the actual instance of the schema/object + private Object instance; + + // is nullable + private Boolean isNullable; + + // schema type (e.g. oneOf, anyOf) + private final String schemaType; + + public AbstractOpenApiSchema(String schemaType, Boolean isNullable) { + this.schemaType = schemaType; + this.isNullable = isNullable; + } + + /** + * Get the list of oneOf/anyOf composed schemas allowed to be stored in this object + * + * @return an instance of the actual schema/object + */ + public abstract Map> getSchemas(); + + /** + * Get the actual instance + * + * @return an instance of the actual schema/object + */ + @JsonValue + public Object getActualInstance() { + return instance; + } + + /** + * Set the actual instance + * + * @param instance the actual instance of the schema/object + */ + public void setActualInstance(Object instance) { + this.instance = instance; + } + + /** + * Get the instant recursively when the schemas defined in oneOf/anyof happen to be oneOf/anyOf + * schema as well + * + * @return an instance of the actual schema/object + */ + public Object getActualInstanceRecursively() { + return getActualInstanceRecursively(this); + } + + private Object getActualInstanceRecursively(AbstractOpenApiSchema object) { + if (object.getActualInstance() == null) { + return null; + } else if (object.getActualInstance() instanceof AbstractOpenApiSchema) { + return getActualInstanceRecursively((AbstractOpenApiSchema) object.getActualInstance()); + } else { + return object.getActualInstance(); + } + } + + /** + * Get the schema type (e.g. anyOf, oneOf) + * + * @return the schema type + */ + public String getSchemaType() { + return schemaType; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ").append(getClass()).append(" {\n"); + sb.append(" instance: ").append(toIndentedString(instance)).append("\n"); + sb.append(" isNullable: ").append(toIndentedString(isNullable)).append("\n"); + sb.append(" schemaType: ").append(toIndentedString(schemaType)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first + * line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractOpenApiSchema a = (AbstractOpenApiSchema) o; + return Objects.equals(this.instance, a.instance) + && Objects.equals(this.isNullable, a.isNullable) + && Objects.equals(this.schemaType, a.schemaType); + } + + @Override + public int hashCode() { + return Objects.hash(instance, isNullable, schemaType); + } + + /** + * Is nullable + * + * @return true if it's nullable + */ + public Boolean isNullable() { + if (Boolean.TRUE.equals(isNullable)) { + return Boolean.TRUE; + } else { + return Boolean.FALSE; + } + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/CertData.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/CertData.java new file mode 100644 index 00000000..3ff33c7c --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/CertData.java @@ -0,0 +1,135 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import lombok.Getter; +import lombok.Setter; + +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-11T16:21:49.277208500+02:00[Europe/Paris]") +@JsonDeserialize(using = CertData.CertDataDeserializer.class) +@JsonSerialize(using = CertData.CertDataSerializer.class) +@Getter +@Setter +public class CertData extends AbstractOpenApiSchema { + private static final Logger log = Logger.getLogger(CertData.class.getName()); + + public static class CertDataSerializer extends StdSerializer { + public CertDataSerializer(Class t) { + super(t); + } + + public CertDataSerializer() { + this(null); + } + + @Override + public void serialize(CertData value, JsonGenerator jgen, SerializerProvider provider) + throws IOException, JsonProcessingException { + jgen.writeObject(value.getActualInstance()); + } + } + + public static class CertDataDeserializer extends StdDeserializer { + public CertDataDeserializer() { + this(CertData.class); + } + + public CertDataDeserializer(Class vc) { + super(vc); + } + + @Override + public CertData deserialize(JsonParser jp, DeserializationContext ctxt) + throws IOException, JsonProcessingException { + JsonNode tree = jp.readValueAsTree(); + Object deserialized = null; + boolean typeCoercion = ctxt.isEnabled(MapperFeature.ALLOW_COERCION_OF_SCALARS); + int match = 0; + JsonToken token = tree.traverse(jp.getCodec()).nextToken(); + EntitiesDescriptor entitiesDescriptor = new EntitiesDescriptor(); + + try { + + deserialized = tree.traverse(jp.getCodec()).readValueAs(EntityDescriptor.class); + + if (((EntityDescriptor) deserialized).getEntityID() != null) { + List entityList = + Arrays.asList(((EntityDescriptor) deserialized)); + entitiesDescriptor.setEntityList(entityList); + log.log(Level.FINER, "Input data matches schema 'EntityDescriptor'"); + + match++; + } else { + try { + deserialized = + tree.traverse(jp.getCodec()).readValueAs(EntitiesDescriptor.class); + + if (((EntitiesDescriptor) deserialized).getEntityList() != null) { + entitiesDescriptor.setEntityList( + ((EntitiesDescriptor) deserialized).getEntityList()); + log.log(Level.FINER, "Input data matches schema 'Entities Descriptor'"); + + match++; + } + } catch (Exception e) { + // deserialization failed, continue + log.log( + Level.FINER, + "Input data does not match schema 'Entities Descriptor'", + e); + } + } + } catch (Exception e) { + // deserialization failed, continue + log.log(Level.FINER, "Input data does not match schema 'EntityDescriptor'", e); + } + + if (match == 1) { + CertData ret = new CertData(); + ret.setActualInstance(entitiesDescriptor); + return ret; + } + throw new IOException( + String.format( + "Failed deserialization for CertData: %d classes match result, expected" + + " 1", + match)); + } + + /** Handle deserialization of the 'null' value. */ + @Override + public CertData getNullValue(DeserializationContext ctxt) throws JsonMappingException { + throw new JsonMappingException(ctxt.getParser(), "CertData cannot be null"); + } + } + + // store a list of schema names defined in oneOf + public static final Map> schemas = new HashMap<>(); + + public CertData() { + super("oneOf", Boolean.FALSE); + } + + @Override + public Map> getSchemas() { + return CertData.schemas; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/EntitiesDescriptor.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/EntitiesDescriptor.java new file mode 100644 index 00000000..54e95d5b --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/EntitiesDescriptor.java @@ -0,0 +1,18 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Getter; +import lombok.Setter; + +/** EntitiesDescriptor */ +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +@Setter +public class EntitiesDescriptor { + + @JsonProperty("EntityDescriptor") + private List entityList; +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/EntityDescriptor.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/EntityDescriptor.java new file mode 100644 index 00000000..0fb4f418 --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/EntityDescriptor.java @@ -0,0 +1,102 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.*; +import java.util.stream.Collectors; +import lombok.Getter; + +/** EntityDescriptor */ +@JsonIgnoreProperties(ignoreUnknown = true) +@Getter +public class EntityDescriptor { + private static final String KEY_DESCRIPTOR = "KeyDescriptor"; + private static final String KEY_INFO = "KeyInfo"; + private static final String X_509_DATA = "X509Data"; + private static final String X_509_CERTIFICATE = "X509Certificate"; + + @JsonProperty("entityID") + private String entityID; + + private List signatureList; + + /** + * Methods that obtains the signature used for verify the user's assertion from a series of + * nested objects contained in the idp certification xml + * + * @param signature HashMap pulled from the idp certification xml + */ + @SuppressWarnings("unchecked") + @JsonProperty("IDPSSODescriptor") + private void unpackNestedSignature(Map signature) { + List> keyDescriptorsList = getKeyDescriptorsList(signature); + + List> keyInfosList = getKeyInfosList(keyDescriptorsList); + + List> listX509Data = getListX509Data(keyInfosList); + + List extractedSignatureList = getExtractedSignatureList(listX509Data); + + this.signatureList = extractedSignatureList; + } + + private List> getKeyDescriptorsList(Map signature) { + List> keyDescriptorsList = new ArrayList<>(); + if (signature.get(KEY_DESCRIPTOR) instanceof List) { + + keyDescriptorsList = + ((List>) signature.get(KEY_DESCRIPTOR)) + .stream() + .filter(el -> el.get("use").equals("signing")) + .collect(Collectors.toList()); + } else { + Map keyDescriptorFound = + (Map) signature.get(KEY_DESCRIPTOR); + + if (keyDescriptorFound.get("use").equals("signing")) { + keyDescriptorsList.add(keyDescriptorFound); + } + } + return keyDescriptorsList; + } + + private List getExtractedSignatureList(List> listX509Data) { + List extractedSignatureList = new ArrayList<>(); + for (Map x509Data : listX509Data) { + if (x509Data.get(X_509_CERTIFICATE) instanceof List) { + extractedSignatureList = (List) x509Data.get(X_509_CERTIFICATE); + } else { + extractedSignatureList.add((String) x509Data.get(X_509_CERTIFICATE)); + } + } + return extractedSignatureList; + } + + private List> getListX509Data(List> keyInfosList) { + List> listX509Data = new ArrayList<>(); + for (Map keyInfo : keyInfosList) { + if (keyInfo.get(X_509_DATA) instanceof List) { + listX509Data = (List>) keyInfo.get(X_509_DATA); + } else { + listX509Data.add((Map) keyInfo.get(X_509_DATA)); + } + } + return listX509Data; + } + + private List> getKeyInfosList( + List> keyDescriptorsList) { + List> keyInfosList = new ArrayList<>(); + for (Map keyDescriptor : keyDescriptorsList) { + if (keyDescriptor.get(KEY_INFO) instanceof List) { + keyInfosList = (List>) keyDescriptor.get(KEY_INFO); + } else { + Map keyInfo = (Map) keyDescriptor.get(KEY_INFO); + + keyInfosList.add(keyInfo); + } + } + return keyInfosList; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/ProblemJson.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/ProblemJson.java new file mode 100644 index 00000000..6ca6fd6b --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/internal/model/ProblemJson.java @@ -0,0 +1,291 @@ +/* (C)2022-2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.model; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.StringJoiner; + +/** ProblemJson */ +@JsonPropertyOrder({ + ProblemJson.JSON_PROPERTY_TYPE, + ProblemJson.JSON_PROPERTY_TITLE, + ProblemJson.JSON_PROPERTY_STATUS, + ProblemJson.JSON_PROPERTY_DETAIL, + ProblemJson.JSON_PROPERTY_INSTANCE +}) +@javax.annotation.Generated( + value = "org.openapitools.codegen.languages.JavaClientCodegen", + date = "2023-04-11T16:21:49.277208500+02:00[Europe/Paris]") +public class ProblemJson { + public static final String JSON_PROPERTY_TYPE = "type"; + private URI type = URI.create("about:blank"); + + public static final String JSON_PROPERTY_TITLE = "title"; + private String title; + + public static final String JSON_PROPERTY_STATUS = "status"; + private Integer status; + + public static final String JSON_PROPERTY_DETAIL = "detail"; + private String detail; + + public static final String JSON_PROPERTY_INSTANCE = "instance"; + private URI instance; + + public ProblemJson() {} + + public ProblemJson type(URI type) { + this.type = type; + return this; + } + + /** + * An absolute URI that identifies the problem type. When dereferenced, it SHOULD provide + * human-readable documentation for the problem type (e.g., using HTML). + * + * @return type + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public URI getType() { + return type; + } + + @JsonProperty(JSON_PROPERTY_TYPE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setType(URI type) { + this.type = type; + } + + public ProblemJson title(String title) { + this.title = title; + return this; + } + + /** + * A short, summary of the problem type. Written in english and readable for engineers (usually + * not suited for non technical stakeholders and not localized); example: Service Unavailable + * + * @return title + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getTitle() { + return title; + } + + @JsonProperty(JSON_PROPERTY_TITLE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setTitle(String title) { + this.title = title; + } + + public ProblemJson status(Integer status) { + this.status = status; + return this; + } + + /** + * The HTTP status code generated by the origin server for this occurrence of the problem. + * minimum: 100 maximum: 600 + * + * @return status + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public Integer getStatus() { + return status; + } + + @JsonProperty(JSON_PROPERTY_STATUS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setStatus(Integer status) { + this.status = status; + } + + public ProblemJson detail(String detail) { + this.detail = detail; + return this; + } + + /** + * A human readable explanation specific to this occurrence of the problem. + * + * @return detail + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_DETAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public String getDetail() { + return detail; + } + + @JsonProperty(JSON_PROPERTY_DETAIL) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setDetail(String detail) { + this.detail = detail; + } + + public ProblemJson instance(URI instance) { + this.instance = instance; + return this; + } + + /** + * An absolute URI that identifies the specific occurrence of the problem. It may or may not + * yield further information if dereferenced. + * + * @return instance + */ + @javax.annotation.Nullable @JsonProperty(JSON_PROPERTY_INSTANCE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public URI getInstance() { + return instance; + } + + @JsonProperty(JSON_PROPERTY_INSTANCE) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public void setInstance(URI instance) { + this.instance = instance; + } + + /** Return true if this ProblemJson object is equal to o. */ + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ProblemJson problemJson = (ProblemJson) o; + return Objects.equals(this.type, problemJson.type) + && Objects.equals(this.title, problemJson.title) + && Objects.equals(this.status, problemJson.status) + && Objects.equals(this.detail, problemJson.detail) + && Objects.equals(this.instance, problemJson.instance); + } + + @Override + public int hashCode() { + return Objects.hash(type, title, status, detail, instance); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("class ProblemJson {\n"); + sb.append(" type: ").append(toIndentedString(type)).append("\n"); + sb.append(" title: ").append(toIndentedString(title)).append("\n"); + sb.append(" status: ").append(toIndentedString(status)).append("\n"); + sb.append(" detail: ").append(toIndentedString(detail)).append("\n"); + sb.append(" instance: ").append(toIndentedString(instance)).append("\n"); + sb.append("}"); + return sb.toString(); + } + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first + * line). + */ + private String toIndentedString(Object o) { + if (o == null) { + return "null"; + } + return o.toString().replace("\n", "\n "); + } + + /** + * Convert the instance into URL query string. + * + * @return URL query string + */ + public String toUrlQueryString() { + return toUrlQueryString(null); + } + + /** + * Convert the instance into URL query string. + * + * @param prefix prefix of the query string + * @return URL query string + */ + public String toUrlQueryString(String prefix) { + String suffix = ""; + String containerSuffix = ""; + String containerPrefix = ""; + if (prefix == null) { + // style=form, explode=true, e.g. /pet?name=cat&type=manx + prefix = ""; + } else { + // deepObject style e.g. /pet?id[name]=cat&id[type]=manx + prefix = prefix + "["; + suffix = "]"; + containerSuffix = "]"; + containerPrefix = "["; + } + + StringJoiner joiner = new StringJoiner("&"); + + // add `type` to the URL query string + if (getType() != null) { + joiner.add( + String.format( + "%stype%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getType()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `title` to the URL query string + if (getTitle() != null) { + joiner.add( + String.format( + "%stitle%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getTitle()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `status` to the URL query string + if (getStatus() != null) { + joiner.add( + String.format( + "%sstatus%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getStatus()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `detail` to the URL query string + if (getDetail() != null) { + joiner.add( + String.format( + "%sdetail%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getDetail()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + // add `instance` to the URL query string + if (getInstance() != null) { + joiner.add( + String.format( + "%sinstance%s=%s", + prefix, + suffix, + URLEncoder.encode(String.valueOf(getInstance()), StandardCharsets.UTF_8) + .replaceAll("\\+", "%20"))); + } + + return joiner.toString(); + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorage.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorage.java new file mode 100644 index 00000000..69cc2826 --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorage.java @@ -0,0 +1,105 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.storage; + +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorage; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import javax.inject.Inject; + +/** + * Implementation of the {@link IdpCertStorage} interface as a simple in memory storage. + * + *

The storage can be configured via the {@link IdpCertStorageConfig} configuration class. + * + *

It store in a in memory {@link java.util.HashMap} the assertions and the associated scheduled + * eviction operations, every time an assertion is accessed the associated eviction operation is + * rescheduled. + */ +public class SimpleIdpCertStorage implements IdpCertStorage { + + private final Map idpCertDataMap; + private final Map> scheduledEvictionsMap; + private final IdpCertStorageConfig storageConfig; + + @Inject + public SimpleIdpCertStorage( + Map idpCertDataMap, + Map> scheduledEvictionsMap, + IdpCertStorageConfig storageConfig) { + this.idpCertDataMap = idpCertDataMap; + this.scheduledEvictionsMap = scheduledEvictionsMap; + this.storageConfig = storageConfig; + } + + /** + * Retrieve the idpCertData associated with the provided tag if the storage is enabled {@link + * IdpCertStorageConfig}, otherwise no operation is performed. + * + *

Before the list of idpCertData is returned the associated eviction operation is + * rescheduled with the delay configured via {@link IdpCertStorageConfig} + * + * @param tag the idpCertData issue instant + * @return the list of cert data if found, null if no cert data are present in the storage or + * the storage is disabled + */ + @Override + public IdpCertData getIdpCertData(String tag) { + if (!storageConfig.isIdpCertDataStorageEnabled()) { + return null; + } + + IdpCertData idpCertData = idpCertDataMap.get(tag); + if (idpCertData != null) { + delayEviction(tag); + } + return idpCertData; + } + + /** + * Store the idpCertData if the storage is enabled {@link IdpCertStorageConfig}, otherwise no + * operation is performed. + * + *

Once the idpCertData is stored an eviction operation is scheduled with a delay configured + * via {@link IdpCertStorageConfig} + * + * @param tag the idpCertData issue instant + * @param idpCertData + */ + @Override + public void saveIdpCertData(String tag, IdpCertData idpCertData) { + if (!storageConfig.isIdpCertDataStorageEnabled()) { + return; + } + + idpCertDataMap.put(tag, idpCertData); + scheduleEviction(tag); + } + + private void scheduleEviction(String assertionRef) { + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + ScheduledFuture schedule = + executorService.schedule( + getEvictionTask(assertionRef), + storageConfig.getStorageEvictionDelay(), + storageConfig.getStorageEvictionDelayTimeUnit()); + scheduledEvictionsMap.put(assertionRef, schedule); + } + + private void delayEviction(String tag) { + ScheduledFuture schedule = scheduledEvictionsMap.get(tag); + schedule.cancel(false); + scheduledEvictionsMap.remove(tag); + scheduleEviction(tag); + } + + private Runnable getEvictionTask(String tag) { + return () -> { + idpCertDataMap.remove(tag); + scheduledEvictionsMap.remove(tag); + }; + } +} diff --git a/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorageProvider.java b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorageProvider.java new file mode 100644 index 00000000..710f5787 --- /dev/null +++ b/identity-service-rest-client-native/src/main/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorageProvider.java @@ -0,0 +1,22 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.storage; + +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorage; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageProvider; +import java.util.HashMap; + +/** Implementation of {@link IdpCertStorageProvider} interface. It provides an instance of the */ +public class SimpleIdpCertStorageProvider implements IdpCertStorageProvider { + + /** + * {@inheritDoc} + * + * @param storageConfig the storage configuration + * @return an instance of {@link SimpleIdpCertStorage} + */ + @Override + public IdpCertStorage provideStorage(IdpCertStorageConfig storageConfig) { + return new SimpleIdpCertStorage(new HashMap<>(), new HashMap<>(), storageConfig); + } +} diff --git a/identity-service-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientTest.java b/identity-service-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientTest.java new file mode 100644 index 00000000..eb915dd6 --- /dev/null +++ b/identity-service-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/IdpCertSimpleClientTest.java @@ -0,0 +1,189 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockserver.integration.ClientAndServer.startClientAndServer; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import it.pagopa.tech.lollipop.consumer.exception.CertDataNotFoundException; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.internal.ApiClient; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.storage.SimpleIdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.io.FileInputStream; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.List; +import lombok.SneakyThrows; +import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockserver.client.MockServerClient; +import org.mockserver.integration.ClientAndServer; + +class IdpCertSimpleClientTest { + + private static IdpCertSimpleClient idpCertSimpleClient; + + private static ClientAndServer mockServer; + + private static final String INSTANT = String.valueOf(Instant.now().getEpochSecond()); + private static final String TAG1 = String.valueOf(Instant.now().getEpochSecond() - 1); + private static final String TAG2 = String.valueOf(Instant.now().getEpochSecond() + 1); + private static final String SPID_ENTITY_ID = "https://posteid.poste.it"; + private static final String SPID_ENTITY_ID_MULTIPLE_SIGNATURE = "https://loginspid.aruba.it"; + private static final String CIE_ENTITY_ID = + "https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO"; + + private static final String WRONG_ENTITY_ID = "https://wrongEntityID.it"; + private static final String WRONG_INSTANT = "xxxxxx"; + + @BeforeAll + public static void startServer() { + mockServer = startClientAndServer(3001); + IdpCertSimpleClientConfig entityConfig = spy(IdpCertSimpleClientConfig.builder().build()); + ApiClient client = new ApiClient(entityConfig); + SimpleIdpCertStorageProvider storageProvider = new SimpleIdpCertStorageProvider(); + idpCertSimpleClient = + new IdpCertSimpleClient( + client, + entityConfig, + storageProvider.provideStorage(new IdpCertStorageConfig())); + doReturn("http://localhost:3001").when(entityConfig).getBaseUri(); + } + + @AfterAll + public static void stopServer() { + mockServer.stop(); + } + + @Test + void certSPIDDataFound() throws CertDataNotFoundException { + createExpectationIdpSpidFound(); + List response = idpCertSimpleClient.getCertData(SPID_ENTITY_ID, INSTANT); + + Assertions.assertNotNull(response); + } + + @Test + void certSPIDDataFoundMultipleSignature() throws CertDataNotFoundException { + createExpectationIdpSpidFound(); + List response = + idpCertSimpleClient.getCertData(SPID_ENTITY_ID_MULTIPLE_SIGNATURE, INSTANT); + + Assertions.assertNotNull(response); + Assertions.assertTrue(response.get(0).getCertData().size() > 1); + } + + @Test + void certCIEDataFound() throws CertDataNotFoundException { + createExpectationIdpCieFound(); + List response = idpCertSimpleClient.getCertData(CIE_ENTITY_ID, INSTANT); + + Assertions.assertNotNull(response); + } + + @Test + void getCertDataWrongEntityID() { + createExpectationIdpSpidFound(); + Assertions.assertThrows( + CertDataNotFoundException.class, + () -> idpCertSimpleClient.getCertData(WRONG_ENTITY_ID, INSTANT)); + } + + @Test + void getSPIDCertDataWrongInstant() { + Assertions.assertThrows( + CertDataNotFoundException.class, + () -> idpCertSimpleClient.getCertData(SPID_ENTITY_ID, WRONG_INSTANT)); + } + + @Test + void getCIECertDataWrongInstant() { + Assertions.assertThrows( + CertDataNotFoundException.class, + () -> idpCertSimpleClient.getCertData(CIE_ENTITY_ID, WRONG_INSTANT)); + } + + @Test + void entityIdNull() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> idpCertSimpleClient.getCertData(null, WRONG_INSTANT)); + } + + @Test + void instantNull() { + Assertions.assertThrows( + IllegalArgumentException.class, + () -> idpCertSimpleClient.getCertData(CIE_ENTITY_ID, null)); + } + + public static void createExpectationIdpSpidFound() { + new MockServerClient("localhost", 3001) + .when(request().withMethod("GET").withPath("/idp-keys/spid")) + .respond( + response() + .withStatusCode(200) + .withBody("[\"" + TAG1 + "\",\"" + TAG2 + "\"]")); + new MockServerClient("localhost", 3001) + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", TAG1)) + .respond( + response() + .withStatusCode(200) + .withBody(retrieveDataFromFile("idp_spid_data_tag1.xml"))); + new MockServerClient("localhost", 3001) + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", TAG2)) + .respond( + response() + .withStatusCode(200) + .withBody(retrieveDataFromFile("idp_spid_data_tag2.xml"))); + } + + public static void createExpectationIdpCieFound() { + new MockServerClient("localhost", 3001) + .when(request().withMethod("GET").withPath("/idp-keys/cie")) + .respond( + response() + .withStatusCode(200) + .withBody("[\"" + TAG1 + "\",\"" + TAG2 + "\"]")); + new MockServerClient("localhost", 3001) + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/cie/{tag}") + .withPathParameter("tag", TAG1)) + .respond( + response() + .withStatusCode(200) + .withBody(retrieveDataFromFile("idp_cie_data_tag1.xml"))); + new MockServerClient("localhost", 3001) + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/cie/{tag}") + .withPathParameter("tag", TAG2)) + .respond( + response() + .withStatusCode(200) + .withBody(retrieveDataFromFile("idp_cie_data_tag1.xml"))); + } + + @SneakyThrows + private static String retrieveDataFromFile(String fileName) { + FileInputStream fis = new FileInputStream("src/test/resources/" + fileName); + return IOUtils.toString(fis, StandardCharsets.UTF_8); + } +} diff --git a/identity-service-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorageTest.java b/identity-service-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorageTest.java new file mode 100644 index 00000000..e189d7eb --- /dev/null +++ b/identity-service-rest-client-native/src/test/java/it/pagopa/tech/lollipop/consumer/idp/client/simple/storage/SimpleIdpCertStorageTest.java @@ -0,0 +1,135 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.idp.client.simple.storage; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorage; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.*; +import lombok.SneakyThrows; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class SimpleIdpCertStorageTest { + + private static final long EVICTION_DELAY = 5000L; + private static IdpCertStorageConfig storageConfigMock; + private IdpCertStorage sut; + private static final String IDPCERTDATA_1 = "1680691737https://posteid.poste.it"; + + @BeforeEach + void setUp() { + storageConfigMock = mock(IdpCertStorageConfig.class); + doReturn(EVICTION_DELAY).when(storageConfigMock).getStorageEvictionDelay(); + doReturn(TimeUnit.MILLISECONDS).when(storageConfigMock).getStorageEvictionDelayTimeUnit(); + } + + @Test + void getExistingAssertionAndResetScheduleEvictionWithStorageEnabled() + throws InterruptedException, ExecutionException { + doReturn(true).when(storageConfigMock).isIdpCertDataStorageEnabled(); + + Map idpCertDataMap = new HashMap<>(); + IdpCertData idpCertData = new IdpCertData(); + idpCertDataMap.put(IDPCERTDATA_1, idpCertData); + Map> scheduledEvictionsMap = new HashMap<>(); + ScheduledFuture scheduledFutureMock = mock(ScheduledFuture.class); + scheduledEvictionsMap.put(IDPCERTDATA_1, scheduledFutureMock); + + sut = new SimpleIdpCertStorage(idpCertDataMap, scheduledEvictionsMap, storageConfigMock); + + IdpCertData result = sut.getIdpCertData(IDPCERTDATA_1); + + assertNotNull(result); + assertEquals(idpCertData, result); + assertEquals(1, scheduledEvictionsMap.size()); + + CompletableFuture future = waitEvictionEnd(scheduledEvictionsMap); + assertEquals(true, future.get()); + assertEquals(0, idpCertDataMap.size()); + assertEquals(0, scheduledEvictionsMap.size()); + } + + @Test + void getNotExistingAssertionWithStorageEnabled() { + doReturn(true).when(storageConfigMock).isIdpCertDataStorageEnabled(); + + sut = new SimpleIdpCertStorage(new HashMap<>(), new HashMap<>(), storageConfigMock); + + IdpCertData result = sut.getIdpCertData(IDPCERTDATA_1); + + assertNull(result); + } + + @Test + void saveAssertionAndScheduleEvictionWithStorageEnabled() + throws InterruptedException, ExecutionException { + doReturn(true).when(storageConfigMock).isIdpCertDataStorageEnabled(); + + Map idpCertDataMap = new HashMap<>(); + Map> scheduledEvictionsMap = new HashMap<>(); + + sut = new SimpleIdpCertStorage(idpCertDataMap, scheduledEvictionsMap, storageConfigMock); + IdpCertData idpCertData = new IdpCertData(); + + sut.saveIdpCertData(IDPCERTDATA_1, idpCertData); + + assertEquals(1, idpCertDataMap.size()); + assertEquals(1, scheduledEvictionsMap.size()); + assertEquals(idpCertData, idpCertDataMap.get(IDPCERTDATA_1)); + + CompletableFuture future = waitEvictionEnd(scheduledEvictionsMap); + assertEquals(true, future.get()); + assertEquals(0, idpCertDataMap.size()); + assertEquals(0, scheduledEvictionsMap.size()); + } + + @Test + void getAssertionWithStorageDisabled() { + doReturn(false).when(storageConfigMock).isIdpCertDataStorageEnabled(); + + sut = new SimpleIdpCertStorage(new HashMap<>(), new HashMap<>(), storageConfigMock); + + IdpCertData result = sut.getIdpCertData(IDPCERTDATA_1); + + assertNull(result); + } + + @Test + void savaAssertionWithStorageDisabled() { + doReturn(false).when(storageConfigMock).isIdpCertDataStorageEnabled(); + + Map idpCertDataMap = new HashMap<>(); + Map> scheduledEvictionsMap = new HashMap<>(); + + sut = new SimpleIdpCertStorage(idpCertDataMap, scheduledEvictionsMap, storageConfigMock); + + sut.saveIdpCertData(IDPCERTDATA_1, new IdpCertData()); + + assertEquals(0, idpCertDataMap.size()); + assertEquals(0, scheduledEvictionsMap.size()); + } + + private CompletableFuture waitEvictionEnd( + Map> scheduledEvictionsMap) { + CompletableFuture future = new CompletableFuture<>(); + ExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.submit( + new Runnable() { + @SneakyThrows + @Override + public void run() { + ScheduledFuture scheduledFuture = + scheduledEvictionsMap.get(IDPCERTDATA_1); + scheduledFuture.get(); + future.complete(true); + } + }); + return future; + } +} diff --git a/identity-service-rest-client-native/src/test/resources/idp_cie_data_tag1.xml b/identity-service-rest-client-native/src/test/resources/idp_cie_data_tag1.xml new file mode 100644 index 00000000..74ca1fd9 --- /dev/null +++ b/identity-service-rest-client-native/src/test/resources/idp_cie_data_tag1.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + ssh7Qe/Sett1HNLh/vQYvNUkCjfgEhAg8Ce4f1GL+Mg= + + + UI+4D9XebmPI96WgQAgSo+IrNzAObjecitjsR6l8gSYmtNDLNLYPeobF4kpFY34Y5bTm+IL1K9VN hhnBeJBeuL9oSBee9PaDzCSt+hmrcQdKvAr05UWDsg96ZYkgyuDugcmbRl3+PBpHzheK0qnVGZne BTSOrFk9vpYxrd2cHv/C6/DV6vNHJFe7uf2LE8yZ+qJqT/UKUgdS0qtW6FjdTOq44BxujJsi/1Yo DiIMvDjKxNrWKjxgpra35i1D1iS6jAEG68nVHjFROQ0ciUS8+9JyoUvQJ3YkVdDAhnsrMtIE8w2A RXL19GhWAw2wR8SKVEZeSNTkf34AQIHLx0vjiA== + + + + x62o94jkwiCC05Ts4nEhLhbdN5Cr0A6hlkXeaO7NVu0j9hLXE5oN8a6J/7G6yxC/3jFEFfwYs+ie KRBqBaTGUBxsTlcqZjuzXPKZBaLe8lEwKa+iJLsuHFLW8dIOX5ECzW97qSINFYNY0p0VxL1AsoK/ /RHiglDov9qbZjlUi2nfnU+04kbGU8GNxb0VnJXg38mMHCDIM+XS0jSzGasM0GStQ871ng+mhrQS gmD0X7WnB6BEg/um4bpB2esPeX6ETCSzmgaZKfl37oBUIqGL6zNAAdWEGeQwkEYYXPI3o8HWPmzg d3mdZSWOzmp537ulz1tn3JJ3pcj7ezxn9tqzXw== + AQAB + + + + MIIDdTCCAl2gAwIBAgIUU79XEfveueyClDtLkqUlSPZ2o8owDQYJKoZIhvcNAQELBQAwLTErMCkG A1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdDAeFw0xODEwMTkwODM1MDVa Fw0zODEwMTkwODM1MDVaMC0xKzApBgNVBAMMImlkc2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5n b3YuaXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHraj3iOTCIILTlOzicSEuFt03 kKvQDqGWRd5o7s1W7SP2EtcTmg3xron/sbrLEL/eMUQV/Biz6J4pEGoFpMZQHGxOVypmO7Nc8pkF ot7yUTApr6Ikuy4cUtbx0g5fkQLNb3upIg0Vg1jSnRXEvUCygr/9EeKCUOi/2ptmOVSLad+dT7Ti RsZTwY3FvRWcleDfyYwcIMgz5dLSNLMZqwzQZK1DzvWeD6aGtBKCYPRftacHoESD+6bhukHZ6w95 foRMJLOaBpkp+XfugFQioYvrM0AB1YQZ5DCQRhhc8jejwdY+bOB3eZ1lJY7Oannfu6XPW2fcknel yPt7PGf22rNfAgMBAAGjgYwwgYkwHQYDVR0OBBYEFK3Ah+Do3/zB9XjZ66i4biDpUEbAMGgGA1Ud EQRhMF+CImlkc2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2 ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0BAQsF AAOCAQEAVtpn/s+lYVf42pAtdgJnGTaSIy8KxHeZobKNYNFEY/XTaZEt9QeV5efUMBVVhxKTTHN0 046DR96WFYXs4PJ9Fpyq6Hmy3k/oUdmHJ1c2bwWF/nZ82CwOO081Yg0GBcfPEmKLUGOBK8T55ncW +RSZadvWTyhTtQhLUtLKcWyzKB5aS3kEE5LSzR8sw3owln9P41Mz+QtL3WeNESRHW0qoQkFotYXX W6Rvh69+GyzJLxvq2qd7D1qoJgOMrarshBKKPk+ABaLYoEf/cru4e0RDIp2mD0jkGOGDkn9XUl+3 ddALq/osTki6CEawkhiZEo6ABEAjEWNkH9W3/ZzvJnWo6Q== + + + + + + gov.it + + + + + MIIDdTCCAl2gAwIBAgIUU79XEfveueyClDtLkqUlSPZ2o8owDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDVaFw0zODEwMTkwODM1MDVaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDHraj3iOTCIILTlOzicSEuFt03kKvQDqGWRd5o7s1W 7SP2EtcTmg3xron/sbrLEL/eMUQV/Biz6J4pEGoFpMZQHGxOVypmO7Nc8pkFot7y UTApr6Ikuy4cUtbx0g5fkQLNb3upIg0Vg1jSnRXEvUCygr/9EeKCUOi/2ptmOVSL ad+dT7TiRsZTwY3FvRWcleDfyYwcIMgz5dLSNLMZqwzQZK1DzvWeD6aGtBKCYPRf tacHoESD+6bhukHZ6w95foRMJLOaBpkp+XfugFQioYvrM0AB1YQZ5DCQRhhc8jej wdY+bOB3eZ1lJY7Oannfu6XPW2fcknelyPt7PGf22rNfAgMBAAGjgYwwgYkwHQYD VR0OBBYEFK3Ah+Do3/zB9XjZ66i4biDpUEbAMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAVtpn/s+lYVf42pAtdgJnGTaSIy8KxHeZobKNYNFEY/XTaZEt9QeV 5efUMBVVhxKTTHN0046DR96WFYXs4PJ9Fpyq6Hmy3k/oUdmHJ1c2bwWF/nZ82CwO O081Yg0GBcfPEmKLUGOBK8T55ncW+RSZadvWTyhTtQhLUtLKcWyzKB5aS3kEE5LS zR8sw3owln9P41Mz+QtL3WeNESRHW0qoQkFotYXXW6Rvh69+GyzJLxvq2qd7D1qo JgOMrarshBKKPk+ABaLYoEf/cru4e0RDIp2mD0jkGOGDkn9XUl+3ddALq/osTki6 CEawkhiZEo6ABEAjEWNkH9W3/ZzvJnWo6Q== + + + + + + + MIIDdTCCAl2gAwIBAgIUegfFpjtEsLaV0IL3qBEa0u81rGkwDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDZaFw0zODEwMTkwODM1MDZaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQCe9W63GohPUaNbsoluWsVWfmtIyAIufqpmzYS4TiBv E6l9LlDITsmShVBpiLPU4IDdvoPPBlDqgotofCnSjQxRhGky7tiy+pBObo13lN6d 03GgXNPZqZ+vKJinf8AmNe2UZ1ZbuvUtgS6+vx6P52/KNKx6YuDNmR3lLDhKZVDb 2wwR5qfsdnJIAORbJVWd8kI6GGhmrsmha7zARd0W+ueDtd/WLuAg3G7QWRocHPlP TN/dPUbKS4O0cnJx0M5UERQ12PIdy641ps6P1v2OatpfSmZp/IlDLKJj9O9V49LM nxF3VBJkTep2UQsQUc3rlelN2rYAlhURQQzRwpWO5WJvAgMBAAGjgYwwgYkwHQYD VR0OBBYEFAQDr+o8YMapC4lje9upfeiwmFdtMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAb7gRYzTPEMQjQKiwI4/NdhzzaoKQjp2tu3UPZwsUHruyCbI+B/0k C2SaSBaAKGT66yN9bPY2Vj4FuxtYmLSZZnatydF19hSu+lExCySKt16GBJ+D5HN7 OmVizRvJNE4+RF0bajpeXnMottLrcL5Ry/BivpxdnIQ9th2sMc7ev0IZtIGYCxGg c5SAJCz4zuCcNiPANHDPdoxYEQ9EV9PNAUx8q9tjAhoRRiT2ovqT+Dowqax0AVOP hRY5rA8WMccWAedO8iSSO8DTWomtoOKS9vjWrQxnsHaT8GXohC2OYgSdKsBchvjS i1RIVkrqHoSHIK2XQapkl8YmD75JjrGNNA== + + + + + + + + + + + + + + + + + gov.it + + + + + MIIDdTCCAl2gAwIBAgIUU79XEfveueyClDtLkqUlSPZ2o8owDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDVaFw0zODEwMTkwODM1MDVaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDHraj3iOTCIILTlOzicSEuFt03kKvQDqGWRd5o7s1W 7SP2EtcTmg3xron/sbrLEL/eMUQV/Biz6J4pEGoFpMZQHGxOVypmO7Nc8pkFot7y UTApr6Ikuy4cUtbx0g5fkQLNb3upIg0Vg1jSnRXEvUCygr/9EeKCUOi/2ptmOVSL ad+dT7TiRsZTwY3FvRWcleDfyYwcIMgz5dLSNLMZqwzQZK1DzvWeD6aGtBKCYPRf tacHoESD+6bhukHZ6w95foRMJLOaBpkp+XfugFQioYvrM0AB1YQZ5DCQRhhc8jej wdY+bOB3eZ1lJY7Oannfu6XPW2fcknelyPt7PGf22rNfAgMBAAGjgYwwgYkwHQYD VR0OBBYEFK3Ah+Do3/zB9XjZ66i4biDpUEbAMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAVtpn/s+lYVf42pAtdgJnGTaSIy8KxHeZobKNYNFEY/XTaZEt9QeV 5efUMBVVhxKTTHN0046DR96WFYXs4PJ9Fpyq6Hmy3k/oUdmHJ1c2bwWF/nZ82CwO O081Yg0GBcfPEmKLUGOBK8T55ncW+RSZadvWTyhTtQhLUtLKcWyzKB5aS3kEE5LS zR8sw3owln9P41Mz+QtL3WeNESRHW0qoQkFotYXXW6Rvh69+GyzJLxvq2qd7D1qo JgOMrarshBKKPk+ABaLYoEf/cru4e0RDIp2mD0jkGOGDkn9XUl+3ddALq/osTki6 CEawkhiZEo6ABEAjEWNkH9W3/ZzvJnWo6Q== + + + + + + + MIIDdTCCAl2gAwIBAgIUegfFpjtEsLaV0IL3qBEa0u81rGkwDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDZaFw0zODEwMTkwODM1MDZaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQCe9W63GohPUaNbsoluWsVWfmtIyAIufqpmzYS4TiBv E6l9LlDITsmShVBpiLPU4IDdvoPPBlDqgotofCnSjQxRhGky7tiy+pBObo13lN6d 03GgXNPZqZ+vKJinf8AmNe2UZ1ZbuvUtgS6+vx6P52/KNKx6YuDNmR3lLDhKZVDb 2wwR5qfsdnJIAORbJVWd8kI6GGhmrsmha7zARd0W+ueDtd/WLuAg3G7QWRocHPlP TN/dPUbKS4O0cnJx0M5UERQ12PIdy641ps6P1v2OatpfSmZp/IlDLKJj9O9V49LM nxF3VBJkTep2UQsQUc3rlelN2rYAlhURQQzRwpWO5WJvAgMBAAGjgYwwgYkwHQYD VR0OBBYEFAQDr+o8YMapC4lje9upfeiwmFdtMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAb7gRYzTPEMQjQKiwI4/NdhzzaoKQjp2tu3UPZwsUHruyCbI+B/0k C2SaSBaAKGT66yN9bPY2Vj4FuxtYmLSZZnatydF19hSu+lExCySKt16GBJ+D5HN7 OmVizRvJNE4+RF0bajpeXnMottLrcL5Ry/BivpxdnIQ9th2sMc7ev0IZtIGYCxGg c5SAJCz4zuCcNiPANHDPdoxYEQ9EV9PNAUx8q9tjAhoRRiT2ovqT+Dowqax0AVOP hRY5rA8WMccWAedO8iSSO8DTWomtoOKS9vjWrQxnsHaT8GXohC2OYgSdKsBchvjS i1RIVkrqHoSHIK2XQapkl8YmD75JjrGNNA== + + + + + + \ No newline at end of file diff --git a/identity-service-rest-client-native/src/test/resources/idp_cie_data_tag2.xml b/identity-service-rest-client-native/src/test/resources/idp_cie_data_tag2.xml new file mode 100644 index 00000000..74ca1fd9 --- /dev/null +++ b/identity-service-rest-client-native/src/test/resources/idp_cie_data_tag2.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + ssh7Qe/Sett1HNLh/vQYvNUkCjfgEhAg8Ce4f1GL+Mg= + + + UI+4D9XebmPI96WgQAgSo+IrNzAObjecitjsR6l8gSYmtNDLNLYPeobF4kpFY34Y5bTm+IL1K9VN hhnBeJBeuL9oSBee9PaDzCSt+hmrcQdKvAr05UWDsg96ZYkgyuDugcmbRl3+PBpHzheK0qnVGZne BTSOrFk9vpYxrd2cHv/C6/DV6vNHJFe7uf2LE8yZ+qJqT/UKUgdS0qtW6FjdTOq44BxujJsi/1Yo DiIMvDjKxNrWKjxgpra35i1D1iS6jAEG68nVHjFROQ0ciUS8+9JyoUvQJ3YkVdDAhnsrMtIE8w2A RXL19GhWAw2wR8SKVEZeSNTkf34AQIHLx0vjiA== + + + + x62o94jkwiCC05Ts4nEhLhbdN5Cr0A6hlkXeaO7NVu0j9hLXE5oN8a6J/7G6yxC/3jFEFfwYs+ie KRBqBaTGUBxsTlcqZjuzXPKZBaLe8lEwKa+iJLsuHFLW8dIOX5ECzW97qSINFYNY0p0VxL1AsoK/ /RHiglDov9qbZjlUi2nfnU+04kbGU8GNxb0VnJXg38mMHCDIM+XS0jSzGasM0GStQ871ng+mhrQS gmD0X7WnB6BEg/um4bpB2esPeX6ETCSzmgaZKfl37oBUIqGL6zNAAdWEGeQwkEYYXPI3o8HWPmzg d3mdZSWOzmp537ulz1tn3JJ3pcj7ezxn9tqzXw== + AQAB + + + + MIIDdTCCAl2gAwIBAgIUU79XEfveueyClDtLkqUlSPZ2o8owDQYJKoZIhvcNAQELBQAwLTErMCkG A1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdDAeFw0xODEwMTkwODM1MDVa Fw0zODEwMTkwODM1MDVaMC0xKzApBgNVBAMMImlkc2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5n b3YuaXQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDHraj3iOTCIILTlOzicSEuFt03 kKvQDqGWRd5o7s1W7SP2EtcTmg3xron/sbrLEL/eMUQV/Biz6J4pEGoFpMZQHGxOVypmO7Nc8pkF ot7yUTApr6Ikuy4cUtbx0g5fkQLNb3upIg0Vg1jSnRXEvUCygr/9EeKCUOi/2ptmOVSLad+dT7Ti RsZTwY3FvRWcleDfyYwcIMgz5dLSNLMZqwzQZK1DzvWeD6aGtBKCYPRftacHoESD+6bhukHZ6w95 foRMJLOaBpkp+XfugFQioYvrM0AB1YQZ5DCQRhhc8jejwdY+bOB3eZ1lJY7Oannfu6XPW2fcknel yPt7PGf22rNfAgMBAAGjgYwwgYkwHQYDVR0OBBYEFK3Ah+Do3/zB9XjZ66i4biDpUEbAMGgGA1Ud EQRhMF+CImlkc2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2 ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0BAQsF AAOCAQEAVtpn/s+lYVf42pAtdgJnGTaSIy8KxHeZobKNYNFEY/XTaZEt9QeV5efUMBVVhxKTTHN0 046DR96WFYXs4PJ9Fpyq6Hmy3k/oUdmHJ1c2bwWF/nZ82CwOO081Yg0GBcfPEmKLUGOBK8T55ncW +RSZadvWTyhTtQhLUtLKcWyzKB5aS3kEE5LSzR8sw3owln9P41Mz+QtL3WeNESRHW0qoQkFotYXX W6Rvh69+GyzJLxvq2qd7D1qoJgOMrarshBKKPk+ABaLYoEf/cru4e0RDIp2mD0jkGOGDkn9XUl+3 ddALq/osTki6CEawkhiZEo6ABEAjEWNkH9W3/ZzvJnWo6Q== + + + + + + gov.it + + + + + MIIDdTCCAl2gAwIBAgIUU79XEfveueyClDtLkqUlSPZ2o8owDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDVaFw0zODEwMTkwODM1MDVaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDHraj3iOTCIILTlOzicSEuFt03kKvQDqGWRd5o7s1W 7SP2EtcTmg3xron/sbrLEL/eMUQV/Biz6J4pEGoFpMZQHGxOVypmO7Nc8pkFot7y UTApr6Ikuy4cUtbx0g5fkQLNb3upIg0Vg1jSnRXEvUCygr/9EeKCUOi/2ptmOVSL ad+dT7TiRsZTwY3FvRWcleDfyYwcIMgz5dLSNLMZqwzQZK1DzvWeD6aGtBKCYPRf tacHoESD+6bhukHZ6w95foRMJLOaBpkp+XfugFQioYvrM0AB1YQZ5DCQRhhc8jej wdY+bOB3eZ1lJY7Oannfu6XPW2fcknelyPt7PGf22rNfAgMBAAGjgYwwgYkwHQYD VR0OBBYEFK3Ah+Do3/zB9XjZ66i4biDpUEbAMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAVtpn/s+lYVf42pAtdgJnGTaSIy8KxHeZobKNYNFEY/XTaZEt9QeV 5efUMBVVhxKTTHN0046DR96WFYXs4PJ9Fpyq6Hmy3k/oUdmHJ1c2bwWF/nZ82CwO O081Yg0GBcfPEmKLUGOBK8T55ncW+RSZadvWTyhTtQhLUtLKcWyzKB5aS3kEE5LS zR8sw3owln9P41Mz+QtL3WeNESRHW0qoQkFotYXXW6Rvh69+GyzJLxvq2qd7D1qo JgOMrarshBKKPk+ABaLYoEf/cru4e0RDIp2mD0jkGOGDkn9XUl+3ddALq/osTki6 CEawkhiZEo6ABEAjEWNkH9W3/ZzvJnWo6Q== + + + + + + + MIIDdTCCAl2gAwIBAgIUegfFpjtEsLaV0IL3qBEa0u81rGkwDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDZaFw0zODEwMTkwODM1MDZaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQCe9W63GohPUaNbsoluWsVWfmtIyAIufqpmzYS4TiBv E6l9LlDITsmShVBpiLPU4IDdvoPPBlDqgotofCnSjQxRhGky7tiy+pBObo13lN6d 03GgXNPZqZ+vKJinf8AmNe2UZ1ZbuvUtgS6+vx6P52/KNKx6YuDNmR3lLDhKZVDb 2wwR5qfsdnJIAORbJVWd8kI6GGhmrsmha7zARd0W+ueDtd/WLuAg3G7QWRocHPlP TN/dPUbKS4O0cnJx0M5UERQ12PIdy641ps6P1v2OatpfSmZp/IlDLKJj9O9V49LM nxF3VBJkTep2UQsQUc3rlelN2rYAlhURQQzRwpWO5WJvAgMBAAGjgYwwgYkwHQYD VR0OBBYEFAQDr+o8YMapC4lje9upfeiwmFdtMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAb7gRYzTPEMQjQKiwI4/NdhzzaoKQjp2tu3UPZwsUHruyCbI+B/0k C2SaSBaAKGT66yN9bPY2Vj4FuxtYmLSZZnatydF19hSu+lExCySKt16GBJ+D5HN7 OmVizRvJNE4+RF0bajpeXnMottLrcL5Ry/BivpxdnIQ9th2sMc7ev0IZtIGYCxGg c5SAJCz4zuCcNiPANHDPdoxYEQ9EV9PNAUx8q9tjAhoRRiT2ovqT+Dowqax0AVOP hRY5rA8WMccWAedO8iSSO8DTWomtoOKS9vjWrQxnsHaT8GXohC2OYgSdKsBchvjS i1RIVkrqHoSHIK2XQapkl8YmD75JjrGNNA== + + + + + + + + + + + + + + + + + gov.it + + + + + MIIDdTCCAl2gAwIBAgIUU79XEfveueyClDtLkqUlSPZ2o8owDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDVaFw0zODEwMTkwODM1MDVaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQDHraj3iOTCIILTlOzicSEuFt03kKvQDqGWRd5o7s1W 7SP2EtcTmg3xron/sbrLEL/eMUQV/Biz6J4pEGoFpMZQHGxOVypmO7Nc8pkFot7y UTApr6Ikuy4cUtbx0g5fkQLNb3upIg0Vg1jSnRXEvUCygr/9EeKCUOi/2ptmOVSL ad+dT7TiRsZTwY3FvRWcleDfyYwcIMgz5dLSNLMZqwzQZK1DzvWeD6aGtBKCYPRf tacHoESD+6bhukHZ6w95foRMJLOaBpkp+XfugFQioYvrM0AB1YQZ5DCQRhhc8jej wdY+bOB3eZ1lJY7Oannfu6XPW2fcknelyPt7PGf22rNfAgMBAAGjgYwwgYkwHQYD VR0OBBYEFK3Ah+Do3/zB9XjZ66i4biDpUEbAMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAVtpn/s+lYVf42pAtdgJnGTaSIy8KxHeZobKNYNFEY/XTaZEt9QeV 5efUMBVVhxKTTHN0046DR96WFYXs4PJ9Fpyq6Hmy3k/oUdmHJ1c2bwWF/nZ82CwO O081Yg0GBcfPEmKLUGOBK8T55ncW+RSZadvWTyhTtQhLUtLKcWyzKB5aS3kEE5LS zR8sw3owln9P41Mz+QtL3WeNESRHW0qoQkFotYXXW6Rvh69+GyzJLxvq2qd7D1qo JgOMrarshBKKPk+ABaLYoEf/cru4e0RDIp2mD0jkGOGDkn9XUl+3ddALq/osTki6 CEawkhiZEo6ABEAjEWNkH9W3/ZzvJnWo6Q== + + + + + + + MIIDdTCCAl2gAwIBAgIUegfFpjtEsLaV0IL3qBEa0u81rGkwDQYJKoZIhvcNAQEL BQAwLTErMCkGA1UEAwwiaWRzZXJ2ZXIuc2Vydml6aWNpZS5pbnRlcm5vLmdvdi5p dDAeFw0xODEwMTkwODM1MDZaFw0zODEwMTkwODM1MDZaMC0xKzApBgNVBAMMImlk c2VydmVyLnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXQwggEiMA0GCSqGSIb3DQEB AQUAA4IBDwAwggEKAoIBAQCe9W63GohPUaNbsoluWsVWfmtIyAIufqpmzYS4TiBv E6l9LlDITsmShVBpiLPU4IDdvoPPBlDqgotofCnSjQxRhGky7tiy+pBObo13lN6d 03GgXNPZqZ+vKJinf8AmNe2UZ1ZbuvUtgS6+vx6P52/KNKx6YuDNmR3lLDhKZVDb 2wwR5qfsdnJIAORbJVWd8kI6GGhmrsmha7zARd0W+ueDtd/WLuAg3G7QWRocHPlP TN/dPUbKS4O0cnJx0M5UERQ12PIdy641ps6P1v2OatpfSmZp/IlDLKJj9O9V49LM nxF3VBJkTep2UQsQUc3rlelN2rYAlhURQQzRwpWO5WJvAgMBAAGjgYwwgYkwHQYD VR0OBBYEFAQDr+o8YMapC4lje9upfeiwmFdtMGgGA1UdEQRhMF+CImlkc2VydmVy LnNlcnZpemljaWUuaW50ZXJuby5nb3YuaXSGOWh0dHBzOi8vaWRzZXJ2ZXIuc2Vy dml6aWNpZS5pbnRlcm5vLmdvdi5pdC9pZHAvc2hpYmJvbGV0aDANBgkqhkiG9w0B AQsFAAOCAQEAb7gRYzTPEMQjQKiwI4/NdhzzaoKQjp2tu3UPZwsUHruyCbI+B/0k C2SaSBaAKGT66yN9bPY2Vj4FuxtYmLSZZnatydF19hSu+lExCySKt16GBJ+D5HN7 OmVizRvJNE4+RF0bajpeXnMottLrcL5Ry/BivpxdnIQ9th2sMc7ev0IZtIGYCxGg c5SAJCz4zuCcNiPANHDPdoxYEQ9EV9PNAUx8q9tjAhoRRiT2ovqT+Dowqax0AVOP hRY5rA8WMccWAedO8iSSO8DTWomtoOKS9vjWrQxnsHaT8GXohC2OYgSdKsBchvjS i1RIVkrqHoSHIK2XQapkl8YmD75JjrGNNA== + + + + + + \ No newline at end of file diff --git a/identity-service-rest-client-native/src/test/resources/idp_spid_data_tag1.xml b/identity-service-rest-client-native/src/test/resources/idp_spid_data_tag1.xml new file mode 100644 index 00000000..5bf14905 --- /dev/null +++ b/identity-service-rest-client-native/src/test/resources/idp_spid_data_tag1.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + zvyH0X70+ePPeUwZOtgzFOPQJGor0Xzx20B6bo8rhE8= + + + RS1FS7vwNEaXcfbfhU5tG8l/jByeaDAfDtC7YozGYb5PViGAfl0R09VSvb9s9FwdHhWSNF7jnoVH9X3ZQ73Dd153KfII3TYmSEqkqIHNtttq9K7CQdoXwgmXz18xWxeXzT6EglJ66TpXUKcGYpiSBktCaHEgFw74W5n7Md2uDl2fQEM8PQG8RdLmUdNC3+6LPFrnbsF6SjDNkSpoKxQEzVegQYZ1QvsQhsRAB368KGgseJ4NVVLovqZ7qoTiGqrVg2bWTcM4O+AzX0Y+uUsX8s2HnBGPFvkHG1ro+wPs8BJ4IpxyXzUKVgY2r653MVpTwjC4+RieXd/4dbUczltrOQ== + + + MIIEGDCCAwCgAwIBAgIJAOrYj9oLEJCwMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDAeFw0xOTA0MTExMDAyMDhaFw0yNTAzMDgxMDAyMDhaMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8kJVo+ugRrbbv9xhXCuVrqi4B7/MQzQc62ocwlFFujJNd4m1mXkUHFbgvwhRkQqo2DAmFeHiwCkJT3K1eeXIFhNFFroEzGPzONyekLpjNvmYIs1CFvirGOj0bkEiGaKEs+/umzGjxIhy5JQlqXE96y1+Izp2QhJimDK0/KNij8I1bzxseP0Ygc4SFveKS+7QO+PrLzWklEWGMs4DM5Zc3VRK7g4LWPWZhKdImC1rnS+/lEmHSvHisdVp/DJtbSrZwSYTRvTTz5IZDSq4kAzrDfpj16h7b3t3nFGc8UoY2Ro4tRZ3ahJ2r3b79yK6C5phY7CAANuW3gDdhVjiBNYs0CAwEAAaOByjCBxzAdBgNVHQ4EFgQU3/7kV2tbdFtphbSA4LH7+w8SkcwwgZcGA1UdIwSBjzCBjIAU3/7kV2tbdFtphbSA4LH7+w8SkcyhaaRnMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdIIJAOrYj9oLEJCwMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJNFqXg/V3aimJKUmUaqmQEEoSc3qvXFITvT5f5bKw9yk/NVhR6wndL+z/24h1OdRqs76blgH8k116qWNkkDtt0AlSjQOx5qvFYh1UviOjNdRI4WkYONSw+vuavcx+fB6O5JDHNmMhMySKTnmRqTkyhjrch7zaFIWUSV7hsBuxpqmrWDoLWdXbV3eFH3mINA5AoIY/m0bZtzZ7YNgiFWzxQgekpxd0vcTseMnCcXnsAlctdir0FoCZztxMuZjlBjwLTtM6Ry3/48LMM8Z+lw7NMciKLLTGQyU8XmKKSSOh0dGh5Lrlt5GxIIJkH81C0YimWebz8464QPL3RbLnTKg+c= + + + + + + + + + + + + + + + JYGJryCuw9bp9PBqoxl1ogs1BX6rIdxN2Cld6uEDcMY= + + + CIEQ0HLKuPpklXui6C9d1pd1syuB9RcgoOI4yBU4QnAEoLvWAJoyTOUvjXfNN9pKehdvcPyW6LPmlonb6Mf5sKswUXdAbimSeYwgAO0U3mVAzGxGK543RjHGamGtz3G4IDW7FTqkG0QmQDAWfeq+CYJksdHFfKwvzY9l6PWCvmPIsaIjwJcFvWMWlwCBwABL3QmUqHkLmifk3/zcN1kmHEjlwMNpCwH32A2jgyFPho96BWQo+iMRjIaLUHfrnPNqMS49nYW5rUQM1nWiRrTMY74dxfd+xTUVZKVGSgL9ACQviTPwHOm4YYkhA+zjUTT22kEFb0fdMxrC0QzBM/FnLA== + + + MIID0jCCArqgAwIBAgIUXDUOKL3WuolxDw96Fk9es8rIt6kwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMB4XDTIxMTExMTE3MDMyMFoXDTI1MTExMDE3MDMyMFowgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6sIS3+3iZSaAIyVywahlbpua2uJ/XmpV68P1e1STJpHoaj32STdHhqZnnb4Y/FshP1NUolzNolPXAYDmDduW1OnGndJZ+G9Hjh1PCkdiRw+p0FjhQAsGJkn8NdgTIHLJjqN1qQwtOsVGab8ScyA3mtmj3xKYuBhUoweuATzC7f5r7FfIoc3cy6N5lgrpZpfeAChxLwoHVjoAVgIBuemi6HAzmd4/BI06KzOcR7+dBVi4+uiseldxrJ5bhnjZKIwgkX14y9UA84Y+e+rMtyT8cT3XXi9NazZl5Ej5/bQPqqVsbg6tXzQSfEJD6JEjuYeC0RUKMS/EJn3hL5VLzTJ1NwIDAQABoywwKjAdBgNVHQ4EFgQUfctFZ8bRtmEvXPRlqgVDuggY/ZwwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEA0lszHadknPfE17IWGWsgvlXOdKMnWcl9H5rEYmsWwDB9FJG9XAZvPMcVv1kkWi6XZI/8N2Twhu1BdZkdvntDRscuck8wxxIpkRV7CwlcqNFZ/IwjDBxOBa8Q1J850p+qP8A9apsLLPUlu/oLygNDWIXzcOjMqnPkEP+XXUNYPto5iV+OyDzLLacCYqDDHcvDewWLmEjt35X967KcM+m7K2zGRLWfqcZPIjJJOkpNjgcs+MaisMrGDyOKiD16v0LpwVyIpTqXvDk7KHo8CUNXDxyLxZzB6WffgnOgjXTfU3vluweOx0qQy/VxIupDlNBKiZB4gnt1oAfnaMbqla9wcw== + + + + + + + + MIID0jCCArqgAwIBAgIUXDUOKL3WuolxDw96Fk9es8rIt6kwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMB4XDTIxMTExMTE3MDMyMFoXDTI1MTExMDE3MDMyMFowgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6sIS3+3iZSaAIyVywahlbpua2uJ/XmpV68P1e1STJpHoaj32STdHhqZnnb4Y/FshP1NUolzNolPXAYDmDduW1OnGndJZ+G9Hjh1PCkdiRw+p0FjhQAsGJkn8NdgTIHLJjqN1qQwtOsVGab8ScyA3mtmj3xKYuBhUoweuATzC7f5r7FfIoc3cy6N5lgrpZpfeAChxLwoHVjoAVgIBuemi6HAzmd4/BI06KzOcR7+dBVi4+uiseldxrJ5bhnjZKIwgkX14y9UA84Y+e+rMtyT8cT3XXi9NazZl5Ej5/bQPqqVsbg6tXzQSfEJD6JEjuYeC0RUKMS/EJn3hL5VLzTJ1NwIDAQABoywwKjAdBgNVHQ4EFgQUfctFZ8bRtmEvXPRlqgVDuggY/ZwwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEA0lszHadknPfE17IWGWsgvlXOdKMnWcl9H5rEYmsWwDB9FJG9XAZvPMcVv1kkWi6XZI/8N2Twhu1BdZkdvntDRscuck8wxxIpkRV7CwlcqNFZ/IwjDBxOBa8Q1J850p+qP8A9apsLLPUlu/oLygNDWIXzcOjMqnPkEP+XXUNYPto5iV+OyDzLLacCYqDDHcvDewWLmEjt35X967KcM+m7K2zGRLWfqcZPIjJJOkpNjgcs+MaisMrGDyOKiD16v0LpwVyIpTqXvDk7KHo8CUNXDxyLxZzB6WffgnOgjXTfU3vluweOx0qQy/VxIupDlNBKiZB4gnt1oAfnaMbqla9wcw== + CN=TI Trust Technologies srl,OU=Servizi per l'identita digitale,O=Telecom Italia Trust Technologies srl,C=IT + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + TI Trust Technologies srl + Trust Technologies srl + https://www.trusttechnologies.it + + + + + + + + + + + + + + zanMEv9e3IoYmz27wv5RQCYhp7IuxdvUwb/VCjOjosA= + + + EyJLBOIVDVK2UM0VYzm+ukfwm34rO2a+AmXnyem+FpLF8mHUdGe2vBafE2YiV6sr7H6/zg0ozeRgPVos9E5xc0LWZwPFK8KWaMiQwrdFVwxAVp3SL0DMXs8msj9+zMnrFb9zGNq9/SoSgJm9BNcjxud+9Ky4XlS30pk7deHy/KgdGpO0cnWOoaYbWfPhHmQ40y7lMF9WZnHibDNTbYPGFMhUgGjGauTH5x+HvGEbreLSpTMEt07Hc0KNV/TSCsUCKpbv7z2YOFbQ5yt6IO2MrpgOQIqr8JF1oC5t/C+5SltkpLUxvYwqh+gF91u1METuqURTzNe1Iz+qb0WFNuyMxw== + + + MIIExTCCA62gAwIBAgIQH32A70kY92tuXB8AGi2DdDANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJJVDEYMBYGA1UECgwPQXJ1YmFQRUMgUy5wLkEuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eUIxIDAeBgNVBAMMF0FydWJhUEVDIFMucC5BLiBORyBDQSAyMB4XDTIwMDEyMjAwMDAw MFoXDTI1MDEyMTIzNTk1OVowgaAxCzAJBgNVBAYTAklUMRYwFAYDVQQKDA1BcnViYSBQRUMgc3Bh MREwDwYDVQQLDAhQcm9kb3R0bzEWMBQGA1UEAwwNcGVjLml0IHBlYy5pdDEZMBcGA1UEBRMQWFhY WFhYMDBYMDBYMDAwWDEPMA0GA1UEKgwGcGVjLml0MQ8wDQYDVQQEDAZwZWMuaXQxETAPBgNVBC4T CDIwODc2Mzc5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqt2oHJhcp03l73p+QYpE J+f3jYYj0W0gos0RItZx/w4vpsiKBygaqDNVWSwfo1aPdVDIX13f62O+lBki29KTt+QWv5K6SGHD UXYPntRdEQlicIBh2Z0HfrM7fDl+xeJrMp1s4dsSQAuB5TJOlFZq7xCQuukytGWBTvjfcN/os5aE sEg+RbtZHJR26SbbUcIqWb27Swgj/9jwK+tvzLnP4w8FNvEOrNfR0XwTMNDFrwbOCuWgthv5jNBs VZaoqNwiA/MxYt+gTOMj/o5PWKk8Wpm6o/7/+lWAoxh0v8x9OkbIi+YaFpIxuCcUqsrJJk63x2gH Cc2nr+yclYUhsKD/AwIDAQABo4IBLDCCASgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBTKQ3+N PGcXFk8nX994vMTVpba1EzBHBgNVHSAEQDA+MDwGCysGAQQBgegtAQEBMC0wKwYIKwYBBQUHAgEW H2h0dHBzOi8vY2EuYXJ1YmFwZWMuaXQvY3BzLmh0bWwwWAYDVR0fBFEwTzBNoEugSYZHaHR0cDov L2NybC5hcnViYXBlYy5pdC9BcnViYVBFQ1NwQUNlcnRpZmljYXRpb25BdXRob3JpdHlCL0xhdGVz dENSTC5jcmwwHwYDVR0jBBgwFoAU8v9jQBwRQv3M3/FZ9m7omYcxR3kwMwYIKwYBBQUHAQEEJzAl MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5hcnViYXBlYy5pdDANBgkqhkiG9w0BAQsFAAOCAQEA ZKpor1MrrYwPw+IuPZElQAuNzXsaSWSnn/QQwJtW49c4rFM4mEud9c61p9XxIIbgQKmDmNbzC+Dm wJSZ8ILdCAyBHmY3BehVRAy3KRA2KQhS9kd4vywf5KVYd1L5hQa9DBrusxF7i1X/SEeLQgoKkov0 R8v43UncqXS/ql50ovJFxi938Rv4rVwa8o0hqqc6WUcjkidB6M9aNJLIbOZN3xNUgC28qIr8y7N8 lbxWbwVrGxqKDtpaA9J0hOOXxwuTfSd1zOtT0KSSSUQ53QGOPnxyjxYDQbJu60/lBPuUV5wb/Z2r gpeUH1/n7limHV5sVmOZgSnf18T+0STANCfkXg== + + + + + + + + + + + MIIExTCCA62gAwIBAgIQH32A70kY92tuXB8AGi2DdDANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJJVDEYMBYGA1UECgwPQXJ1YmFQRUMgUy5wLkEuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eUIxIDAeBgNVBAMMF0FydWJhUEVDIFMucC5BLiBORyBDQSAyMB4XDTIwMDEyMjAwMDAw MFoXDTI1MDEyMTIzNTk1OVowgaAxCzAJBgNVBAYTAklUMRYwFAYDVQQKDA1BcnViYSBQRUMgc3Bh MREwDwYDVQQLDAhQcm9kb3R0bzEWMBQGA1UEAwwNcGVjLml0IHBlYy5pdDEZMBcGA1UEBRMQWFhY WFhYMDBYMDBYMDAwWDEPMA0GA1UEKgwGcGVjLml0MQ8wDQYDVQQEDAZwZWMuaXQxETAPBgNVBC4T CDIwODc2Mzc5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqt2oHJhcp03l73p+QYpE J+f3jYYj0W0gos0RItZx/w4vpsiKBygaqDNVWSwfo1aPdVDIX13f62O+lBki29KTt+QWv5K6SGHD UXYPntRdEQlicIBh2Z0HfrM7fDl+xeJrMp1s4dsSQAuB5TJOlFZq7xCQuukytGWBTvjfcN/os5aE sEg+RbtZHJR26SbbUcIqWb27Swgj/9jwK+tvzLnP4w8FNvEOrNfR0XwTMNDFrwbOCuWgthv5jNBs VZaoqNwiA/MxYt+gTOMj/o5PWKk8Wpm6o/7/+lWAoxh0v8x9OkbIi+YaFpIxuCcUqsrJJk63x2gH Cc2nr+yclYUhsKD/AwIDAQABo4IBLDCCASgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBTKQ3+N PGcXFk8nX994vMTVpba1EzBHBgNVHSAEQDA+MDwGCysGAQQBgegtAQEBMC0wKwYIKwYBBQUHAgEW H2h0dHBzOi8vY2EuYXJ1YmFwZWMuaXQvY3BzLmh0bWwwWAYDVR0fBFEwTzBNoEugSYZHaHR0cDov L2NybC5hcnViYXBlYy5pdC9BcnViYVBFQ1NwQUNlcnRpZmljYXRpb25BdXRob3JpdHlCL0xhdGVz dENSTC5jcmwwHwYDVR0jBBgwFoAU8v9jQBwRQv3M3/FZ9m7omYcxR3kwMwYIKwYBBQUHAQEEJzAl MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5hcnViYXBlYy5pdDANBgkqhkiG9w0BAQsFAAOCAQEA ZKpor1MrrYwPw+IuPZElQAuNzXsaSWSnn/QQwJtW49c4rFM4mEud9c61p9XxIIbgQKmDmNbzC+Dm wJSZ8ILdCAyBHmY3BehVRAy3KRA2KQhS9kd4vywf5KVYd1L5hQa9DBrusxF7i1X/SEeLQgoKkov0 R8v43UncqXS/ql50ovJFxi938Rv4rVwa8o0hqqc6WUcjkidB6M9aNJLIbOZN3xNUgC28qIr8y7N8 lbxWbwVrGxqKDtpaA9J0hOOXxwuTfSd1zOtT0KSSSUQ53QGOPnxyjxYDQbJu60/lBPuUV5wb/Z2r gpeUH1/n7limHV5sVmOZgSnf18T+0STANCfkXg== + + + + + + + MIIExTCCA62gAwIBAgIQIHtEvEhGM77HwqsuvSbi9zANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJJVDEYMBYGA1UECgwPQXJ1YmFQRUMgUy5wLkEuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eUIxIDAeBgNVBAMMF0FydWJhUEVDIFMucC5BLiBORyBDQSAyMB4XDTE3MDEyMzAwMDAw MFoXDTIwMDEyMzIzNTk1OVowgaAxCzAJBgNVBAYTAklUMRYwFAYDVQQKDA1BcnViYSBQRUMgc3Bh MREwDwYDVQQLDAhQcm9kb3R0bzEWMBQGA1UEAwwNcGVjLml0IHBlYy5pdDEZMBcGA1UEBRMQWFhY WFhYMDBYMDBYMDAwWDEPMA0GA1UEKgwGcGVjLml0MQ8wDQYDVQQEDAZwZWMuaXQxETAPBgNVBC4T CDE2MzQ1MzgzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqt2oHJhcp03l73p+QYpE J+f3jYYj0W0gos0RItZx/w4vpsiKBygaqDNVWSwfo1aPdVDIX13f62O+lBki29KTt+QWv5K6SGHD UXYPntRdEQlicIBh2Z0HfrM7fDl+xeJrMp1s4dsSQAuB5TJOlFZq7xCQuukytGWBTvjfcN/os5aE sEg+RbtZHJR26SbbUcIqWb27Swgj/9jwK+tvzLnP4w8FNvEOrNfR0XwTMNDFrwbOCuWgthv5jNBs VZaoqNwiA/MxYt+gTOMj/o5PWKk8Wpm6o/7/+lWAoxh0v8x9OkbIi+YaFpIxuCcUqsrJJk63x2gH Cc2nr+yclYUhsKD/AwIDAQABo4IBLDCCASgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBTKQ3+N PGcXFk8nX994vMTVpba1EzBHBgNVHSAEQDA+MDwGCysGAQQBgegtAQEBMC0wKwYIKwYBBQUHAgEW H2h0dHBzOi8vY2EuYXJ1YmFwZWMuaXQvY3BzLmh0bWwwWAYDVR0fBFEwTzBNoEugSYZHaHR0cDov L2NybC5hcnViYXBlYy5pdC9BcnViYVBFQ1NwQUNlcnRpZmljYXRpb25BdXRob3JpdHlCL0xhdGVz dENSTC5jcmwwHwYDVR0jBBgwFoAU8v9jQBwRQv3M3/FZ9m7omYcxR3kwMwYIKwYBBQUHAQEEJzAl MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5hcnViYXBlYy5pdDANBgkqhkiG9w0BAQsFAAOCAQEA nEw0NuaspbpDjA5wggwFtfQydU6b3Bw2/KXPRKS2JoqGmx0SYKj+L17A2KUBa2c7gDtKXYz0FLT6 0Bv0pmBN/oYCgVMEBJKqwRwdki9YjEBwyCZwNEx1kDAyyqFEVU9vw/OQfrAdp7MTbuZGFKknVt7b 9wOYy/Op9FiUaTg6SuOy0ep+rqhihltYNAAl4L6fY45mHvqa5vvVG30OvLW/S4uvRYUXYwY6KhWv NdDf5CnFugnuEZtHJrVe4wx9aO5GvFLFZ/mQ35C5mXPQ7nIb0CDdLBJdz82nUoLSA5BUbeXAUkfa hW/hLxLdhks68/TK694xVIuiB40pvMmJwxIyDA== + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + ArubaPEC S.p.A. + ArubaPEC S.p.A. + https://www.pec.it/ + + + + + + + + + + + + + + qjmkoKCiL8y4gBEXT8DVubGLvH7N8roi/lpgTQww+dw= + + + fm30Z3Ugxcae7W+nvNm9SDeMjH+sWDDcUnobMaBemlKLSj7KiOT1bc/HVIqY9sH/AoV6LiDZ05PSnQLhL8HI1Q71CGpRUTIt3b2oL7D+4iOsirKjFCYjHq75FBR5esaejhvHWIpnTx6Dp+CigVM2eH9IrBIY3Xkw2FqD7Q9CCc/bl33OUjEGS2o4VpjWyFCVsPMWy0QxsC0Em+fTbPjlMX8n55SKvSZy4ItNfoahLy/+8DD+VsWA9u3fPbValsfLZqyziBRjTcdc0nAeQliXj+ajjCbqGmG+jLw3mNRXglF5Kqq3hcHe5a/9WqKCh2x82ReMT7KhFRwQl+lhsBoWRg== + + + MIIIRDCCBiygAwIBAgIINO3vGmIYBP0wDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNVBAYTAklUMQ0w CwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0YWxpYSBEaWdpdGFsZTEwMC4G A1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8gZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQ cm9nZXR0byBTUElEIC0gR2VzdG9yaSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkq hkiG9w0BCQEWGnByb3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05Nzcz NTAyMDU4NDAeFw0yMzAyMjIwMDAwMDBaFw0zMzAyMjEyMzU5NTlaMIG3MQswCQYDVQQGEwJJVDEO MAwGA1UECAwFSXRhbHkxDzANBgNVBAcMBlBhZG92YTEcMBoGA1UECgwTSW5mb0NhbWVyZSBTLkMu cC5BLjElMCMGA1UEAwwcSW5mb0NhbWVyZSBJZGVudGl0eSBQcm92aWRlcjEaMBgGA1UEYQwRVkFU SVQtMDIzMTM4MjEwMDcxJjAkBgNVBFMMHWh0dHBzOi8vaWRzcGlkLmluZm9jYW1lcmUuaXQvMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8Div4aLGUtoDoP5RWbRwqvEtjnDcCCUS+Sz ChAsJP+UYjWl+R4R4Y7Lz+WId3LJqey+QIyvviD6vH/QloqzVRG/JabW70NZylk1UX2isss8mRvt ceK7nYVxjTIoQpasg0OsCevgljjnFxRm8c3zUpYfjC5zzr/jZ9HjFKghGCZGjBavNNgiGIo7e7jb dmGH5N9z+uQ8KRG/p2JRxD0YeVy2+EV2o0cQO2duE383EganLKPcQ9AnxkLE1K0cpP7XQDtUgWTP qsL9+OLTl13KhVM2TMK7EkAm00WCOl1aX3E7g9Qgw+4fUm308v77OSDe77dY8hohZWPRTwjemaHA 2QIDAQABo4IDDDCCAwgwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUddqeUDWjVXqV3PSfTyzAmlFD TDMwHwYDVR0jBBgwFoAUyF8jl8Jbn9TohwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1Ud EQQKMAiCBmlkcC5pdDAWBgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5o dHRwczovL2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUFBwEB BF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2NlcnRpZmljYXRpL1N1 Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBzOi8vMIIBzgYDVR0gBIIBxTCCAcEw CQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggrBgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmlj YXRlIGNvbmZvcm1pbmcgd2l0aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlm aWNhdG8gZWxldHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAgZGlnaXRhbGkg KElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJvdmlkZXIgKElkUCkwCAYGBACP egEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMv QWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6 Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG 9w0BAQ0FAAOCAgEAoYZlSArAwFZDknzUG5Z3NQQUT3JKaOTT8TrNi/F8yL4mz0qjaJaJURMQauKZ eNQiGlGvNyGp3SlgGYFHasZ9FrtpxbxGXVkNreer61kFhY/I3ZdU4DjGW2qPs9csP+W06R4k3OFF hua7DFyyoxAWQYIFisucT3E3+N32XuLQPDqjMwnvSdT4FLE6c4QIpJl3fQYlCsyhAxrNWlrndP1Q 1f97oF6oB7tWR5Ae1/ixDN0q5QJeEnapNaDjvS2wEzVNRYW/RzbHPPZQ1Zs0jLEfXsuwD3A0iJiy D0GSgXYUibqH3VExCqQ1yjEDwjq3zF8bcSaoAQm2fRY3KIYSbI18kpPhFmNTJWbv303dQe6MzIOR LUzs0tSHfB+mtclrHgqqaKwZZmHiGUYTV3bziWjMDacG9gRJtyS04LYZdkSBcSOn3dYXSM18F58p bKifcdajFmUicUWlI/2TFArDguh5TUekLQKsTi4tMnmk5RWA4oMLjZ+q2r4jMNVuoZ0+FGFbrfdh z+Kyo3gWdyZyY+Uqr1aiL+QTnht8hVTVrgOf4RJW/3z5hgYLSyx3INT6GDtaSr5V+orYfSpbvU1X linz+iP4vfYKmpFdF1cxjTYkNQB7/DW9nXYC4PwXjI5253rha8g/BLdsIEWD73Q1GM1HieSVX+tN PBbjHpKLz2UVZEM= + + + + + + + + + + + MIIIRDCCBiygAwIBAgIINO3vGmIYBP0wDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNVBAYTAklUMQ0w CwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0YWxpYSBEaWdpdGFsZTEwMC4G A1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8gZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQ cm9nZXR0byBTUElEIC0gR2VzdG9yaSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkq hkiG9w0BCQEWGnByb3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05Nzcz NTAyMDU4NDAeFw0yMzAyMjIwMDAwMDBaFw0zMzAyMjEyMzU5NTlaMIG3MQswCQYDVQQGEwJJVDEO MAwGA1UECAwFSXRhbHkxDzANBgNVBAcMBlBhZG92YTEcMBoGA1UECgwTSW5mb0NhbWVyZSBTLkMu cC5BLjElMCMGA1UEAwwcSW5mb0NhbWVyZSBJZGVudGl0eSBQcm92aWRlcjEaMBgGA1UEYQwRVkFU SVQtMDIzMTM4MjEwMDcxJjAkBgNVBFMMHWh0dHBzOi8vaWRzcGlkLmluZm9jYW1lcmUuaXQvMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8Div4aLGUtoDoP5RWbRwqvEtjnDcCCUS+Sz ChAsJP+UYjWl+R4R4Y7Lz+WId3LJqey+QIyvviD6vH/QloqzVRG/JabW70NZylk1UX2isss8mRvt ceK7nYVxjTIoQpasg0OsCevgljjnFxRm8c3zUpYfjC5zzr/jZ9HjFKghGCZGjBavNNgiGIo7e7jb dmGH5N9z+uQ8KRG/p2JRxD0YeVy2+EV2o0cQO2duE383EganLKPcQ9AnxkLE1K0cpP7XQDtUgWTP qsL9+OLTl13KhVM2TMK7EkAm00WCOl1aX3E7g9Qgw+4fUm308v77OSDe77dY8hohZWPRTwjemaHA 2QIDAQABo4IDDDCCAwgwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUddqeUDWjVXqV3PSfTyzAmlFD TDMwHwYDVR0jBBgwFoAUyF8jl8Jbn9TohwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1Ud EQQKMAiCBmlkcC5pdDAWBgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5o dHRwczovL2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUFBwEB BF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2NlcnRpZmljYXRpL1N1 Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBzOi8vMIIBzgYDVR0gBIIBxTCCAcEw CQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggrBgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmlj YXRlIGNvbmZvcm1pbmcgd2l0aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlm aWNhdG8gZWxldHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAgZGlnaXRhbGkg KElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJvdmlkZXIgKElkUCkwCAYGBACP egEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMv QWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6 Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG 9w0BAQ0FAAOCAgEAoYZlSArAwFZDknzUG5Z3NQQUT3JKaOTT8TrNi/F8yL4mz0qjaJaJURMQauKZ eNQiGlGvNyGp3SlgGYFHasZ9FrtpxbxGXVkNreer61kFhY/I3ZdU4DjGW2qPs9csP+W06R4k3OFF hua7DFyyoxAWQYIFisucT3E3+N32XuLQPDqjMwnvSdT4FLE6c4QIpJl3fQYlCsyhAxrNWlrndP1Q 1f97oF6oB7tWR5Ae1/ixDN0q5QJeEnapNaDjvS2wEzVNRYW/RzbHPPZQ1Zs0jLEfXsuwD3A0iJiy D0GSgXYUibqH3VExCqQ1yjEDwjq3zF8bcSaoAQm2fRY3KIYSbI18kpPhFmNTJWbv303dQe6MzIOR LUzs0tSHfB+mtclrHgqqaKwZZmHiGUYTV3bziWjMDacG9gRJtyS04LYZdkSBcSOn3dYXSM18F58p bKifcdajFmUicUWlI/2TFArDguh5TUekLQKsTi4tMnmk5RWA4oMLjZ+q2r4jMNVuoZ0+FGFbrfdh z+Kyo3gWdyZyY+Uqr1aiL+QTnht8hVTVrgOf4RJW/3z5hgYLSyx3INT6GDtaSr5V+orYfSpbvU1X linz+iP4vfYKmpFdF1cxjTYkNQB7/DW9nXYC4PwXjI5253rha8g/BLdsIEWD73Q1GM1HieSVX+tN PBbjHpKLz2UVZEM= + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + InfoCamere S.C.p.A. + InfoCamere S.C.p.A. + https://www.infocamere.it/ + + + + + + + + + + + + + + ix5zJ0s5HicXBtbud2nW7dwhwEVB6jZnzhFkbFLAYVs= + + + EdhsS12CrldyKtXkWCHY7PlrD8Uc2HyKd2a40aNsEabBJxH0gsKfzO85HSRw1jLBVf0352moDNAp vqrH24ImHV9umqzxqY5SAXx7ISeGl56kmWB4CWGPK7X7Vb0iDosDzoI60vHlipVmdbaqlwOZQG79 xEoyo4bU/IxhdFhr0wl8b3SnGTWlFS3iThaz4g2dmWlzcjVcf+s5CigClhqToedKxPbY2CGl+U78 sgNTlR2cGIe9gjRlQIboXUr14SzDJgIOLkFIGyuIlgA7vk85/HDSXGhEIa5r/N9Tb+dTc+PXPxV9 Lk9Oy88WbYOqfvOKnkzVjmWj026DTT2N9uBPmg== + + + MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4 fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1 MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0 uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc= + + + + + + + + + + + MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4 fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1 MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0 uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc= + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + Poste Italiane SpA + Poste Italiane SpA + https://www.poste.it + + + + + + + + + + + + + + D/A8yDFktAAd8ZdH4txugRyfui6Yy9cYlS/ilyzB6qg= + + + YN5NOuJNo8+3p5j+vQ+l0tA8hsvgfqwHu4amDbJeV3ltCa2ev2chti18Tekswx/FjkpVo7Xu1Thi3jcxRalyeoY2XAPhhEigI+JSA6+JcJUC91Gm+b9+LO6mnKba+epGBdfoDoj66tBCeSXD1AOSid1WcCEjEoFfMwIx2TJQVhz/Vx6JhAPYjbjyiXgus7hI4JPFQla3msjrrCJ8umU635e1dyFPqxTt1jIRP5oZnSGx0moP5dGRMhU+mu2mtcJOGiz02km+TtmxIRgACJ8HB3sEP3HDwtQmVguhDCUluY94UfU42dIsaKOZB2mVEVjHDzCVxGGYPAYaY8lu1ZDO5g== + + + MIIDazCCAlOgAwIBAgIED8R+MDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJJVDELMAkGA1UECBMCRkkxETAPBgNVBAcTCGZsb3JlbmNlMREwDwYDVQQKEwhyZWdpc3RlcjERMA8GA1UECxMIcmVnaXN0ZXIxETAPBgNVBAMTCHJlZ2lzdGVyMB4XDTE3MDcxMDEwMzM0OVoXDTI3MDcwODEwMzM0OVowZjELMAkGA1UEBhMCSVQxCzAJBgNVBAgTAkZJMREwDwYDVQQHEwhmbG9yZW5jZTERMA8GA1UEChMIcmVnaXN0ZXIxETAPBgNVBAsTCHJlZ2lzdGVyMREwDwYDVQQDEwhyZWdpc3RlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANkYXHbm3q6xt3wrLAXnytswtj2JE1MM8aYmNXkTgDMCwO/+ahQOoQru6IBTbjfWH9jr+Woy54FDdX6bHl+5/mO6l/yAB/bKgwe5HmUjZJ5oakJjWucsSm+VkEwN2HquBZoN+mktju00xvLX5VAjmDHvZc/b8NhNr/FRKlYITboygkhGiUwGI3wLf3IaB76J0o7ugpW2WNLcywpX+p1VWZAMCdHBveBe/e42hh6WnWPqdwYUWHOgJ8HX4IzCHifiS1n6eUMgtoTQOmSvTQDwSjD0WWJE8tWSYt+txXg1t+3A3tbZOFu7T442wE7DtMdUL4+8gimQS+e8PxDK1uTqIPUCAwEAAaMhMB8wHQYDVR0OBBYEFMCgo1gzCIcUThQIs5g5ikfv1D7eMA0GCSqGSIb3DQEBCwUAA4IBAQBnGw3i3hQ37L8vyelkyZMeO3tLK65Cqti4oVrQZxClGV5zNA6fIMDY8Mci1UhLwjzp29POd/sez0vuHZ/Vmmygzoye4jTKr6c3jAh0u81FTzefBU+vIietm9RuV3sd7D9xq6EqOY1NDL+rkvBcTFtiwLEUm2kHYu/U67jk73pxOtmqxQvQeMU8oi42tehMZGLIGp3U5lGS8YGGl+GtkkQ2Z5/PSm67HGP81kTArG/QX+bX+ykypTJVg9hfb9zOFQidp1HkCRIez6YhDiP/ZLurd6Grt/wVfZPNBO8EOgy25AkRZlp+UD686BFg7qq5KKEbz3qmPrj8deHL3duacZcp + + + + + + + + MIIDazCCAlOgAwIBAgIED8R+MDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJJVDELMAkGA1UECBMCRkkxETAPBgNVBAcTCGZsb3JlbmNlMREwDwYDVQQKEwhyZWdpc3RlcjERMA8GA1UECxMIcmVnaXN0ZXIxETAPBgNVBAMTCHJlZ2lzdGVyMB4XDTE3MDcxMDEwMzM0OVoXDTI3MDcwODEwMzM0OVowZjELMAkGA1UEBhMCSVQxCzAJBgNVBAgTAkZJMREwDwYDVQQHEwhmbG9yZW5jZTERMA8GA1UEChMIcmVnaXN0ZXIxETAPBgNVBAsTCHJlZ2lzdGVyMREwDwYDVQQDEwhyZWdpc3RlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANkYXHbm3q6xt3wrLAXnytswtj2JE1MM8aYmNXkTgDMCwO/+ahQOoQru6IBTbjfWH9jr+Woy54FDdX6bHl+5/mO6l/yAB/bKgwe5HmUjZJ5oakJjWucsSm+VkEwN2HquBZoN+mktju00xvLX5VAjmDHvZc/b8NhNr/FRKlYITboygkhGiUwGI3wLf3IaB76J0o7ugpW2WNLcywpX+p1VWZAMCdHBveBe/e42hh6WnWPqdwYUWHOgJ8HX4IzCHifiS1n6eUMgtoTQOmSvTQDwSjD0WWJE8tWSYt+txXg1t+3A3tbZOFu7T442wE7DtMdUL4+8gimQS+e8PxDK1uTqIPUCAwEAAaMhMB8wHQYDVR0OBBYEFMCgo1gzCIcUThQIs5g5ikfv1D7eMA0GCSqGSIb3DQEBCwUAA4IBAQBnGw3i3hQ37L8vyelkyZMeO3tLK65Cqti4oVrQZxClGV5zNA6fIMDY8Mci1UhLwjzp29POd/sez0vuHZ/Vmmygzoye4jTKr6c3jAh0u81FTzefBU+vIietm9RuV3sd7D9xq6EqOY1NDL+rkvBcTFtiwLEUm2kHYu/U67jk73pxOtmqxQvQeMU8oi42tehMZGLIGp3U5lGS8YGGl+GtkkQ2Z5/PSm67HGP81kTArG/QX+bX+ykypTJVg9hfb9zOFQidp1HkCRIez6YhDiP/ZLurd6Grt/wVfZPNBO8EOgy25AkRZlp+UD686BFg7qq5KKEbz3qmPrj8deHL3duacZcp + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + Register.it S.p.A. + Register.it S.p.A. + https//www.register.it + + + + + + + + + + + + + + khZostyVOFwCtkt5rHSLJ9UxgM6rKAR+7O+yLQ++5jI= + + + rnN5Omq7oWmuqqW13KbMr+taRMdqJ85W9+WpcWLX2Wsax26kTHBgD78vVfVWKuejMKeK6F8oSTkiipMlnGLXMniQplvI9rZJO3fj3ygG3qdwPCqFqJ7e9iYPZ5gIMjLuePyoAfcvSdAQiMBxeTX7nwjpLrzB51RaxGNTmJNJv44zbtdR++I1my8nSDRycq5o6+uR+k/SEOiR65+uQbiBvL9Or/N4sEoJyFx23AbQjurWiBzqgHwHPf8tDLBbDa8mjPhReXGK7aePymQU4GlSeZBxSHcSad04gQjlcgayp4d+O43SBczmtueV6szrAqURnhdj6L1PRvyusfNNk04bZA== + + + MIIINTCCBh2gAwIBAgIIJz+ujRbSAYwwDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNV BAYTAklUMQ0wCwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0 YWxpYSBEaWdpdGFsZTEwMC4GA1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8g ZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQcm9nZXR0byBTUElEIC0gR2VzdG9y aSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkqhkiG9w0BCQEWGnBy b3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05NzczNTAy MDU4NDAeFw0yMjA1MTAwMDAwMDBaFw0zMjA1MDkyMzU5NTlaMIGrMRowGAYDVQRh DBFWQVRJVC0wMTAzNTMxMDQxNDEcMBoGA1UEAwwTc3BpZC50ZWFtc3lzdGVtLmNv bTEaMBgGA1UECgwRVGVhbVN5c3RlbSBTLnAuQS4xKDAmBgNVBFMMH2h0dHBzOi8v c3BpZC50ZWFtc3lzdGVtLmNvbS9pZHAxCzAJBgNVBAYTAklUMQ8wDQYDVQQHDAZQ ZXNhcm8xCzAJBgNVBAgMAlBVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAyNJMgyn+iquzTvLR5Z/eYBfOoyJIfI3rYcj5WOSlTzlqXXBCzdcROm/JKgrf 3MOTEzH8RAn6XkSHXtJDtMpD7GlwYB0mo8scqDNtpszbhm/UXapJTrP7gy/UI3yf n99n4hvqkGOdld7w5vaAPS0w9PdcaRxY/7X4olHKBAx2cHAwiqhKuiFEDhfACRWs bw4gaIjVM7NuUtL/jG+PJV1NHrEn10vizE7IneMxDNqiQ14IjLL7pJMEPXwbXedz ZsModKKAXIX5reNSegEU1Y386BCkmg4IMWd+DglmMJ4uuzcga1AppgjDuqb8yFDa NOKy/0Jivh2rs7u9boE4cLVBPQIDAQABo4IDCTCCAwUwCQYDVR0TBAIwADAdBgNV HQ4EFgQU/q5NWlPmylmZTsX0C2MwZkrx3b4wHwYDVR0jBBgwFoAUyF8jl8Jbn9To hwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1UdEQQKMAiCBmlkcC5pdDAW BgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5odHRwczov L2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUF BwEBBF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2Nl cnRpZmljYXRpL1N1Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBz Oi8vMIIBzgYDVR0gBIIBxTCCAcEwCQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggr BgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmljYXRlIGNvbmZvcm1pbmcgd2l0 aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlmaWNhdG8gZWxl dHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAg ZGlnaXRhbGkgKElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJv dmlkZXIgKElkUCkwCAYGBACPegEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0 cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3Bz LnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lk Lmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG9w0B AQ0FAAOCAgEAG9XZeAkIuqSmYb6bq5WrcI2FQtVrfbMH1CXGDKytZUsH5phkGfk/ 8UaIfkbHhnWakM4H9J2gnvfhKorfMt2FHyXFFJ38hlWR8MhFziqthXLUxyLZpUMn h8CcNQyFpNz7xbZk/qN5yFfJyY4Rggm1qdgCNR1LsVI3hjuaORTAzvy4kLjfuU5r nVYPcxpHF7feJKlN03d8JRKYaIi5U+QVYtYJpTcE7jeYmn4Ewfry2BDCOsnljeYl gm3fF8EEVpMfHIhvJg8evATWmKWHpXL2BRtVrl7TfhvtWqKv4tLff+Lv2YqRpmYu oApA48/MB4QxwAPUBnmQb3CxVGs6OCbE/tdUfda9HuHP5MXYLtTVbRYu8pHEPnaN jPA8y90KRw2wiedgjgOG8BxOkhVF/cYs3yH+0hbPS5Oji27t0P2g9eG/p9TOy4AI gUykFimVFk6HV9znknrFSdgsePSp+T5zy45Jdi1z4/RgJN10szJfqEBuvd8MhUu4 meVgfDqXrqavCVzGpSLuicdk41sTOviBz+PEgbQ/qP9KHQv67SHoF4US9Pp9tkyj VFUs7lBnrlFAPpOzd97XdiZfotCA5umibqlxLshy4UK7yl2LZFllpxrfiXTCDASM KlMMIcIsWx0lU/qw5KPpqvXELiya791kohJTi+9pyG7LXIOHHA0whr0= + + + + + + https://www.spid.gov.it/SpidL1 + https://www.spid.gov.it/SpidL2 + + + P + LP + PG + PF + PX + + + + + + + MIIINTCCBh2gAwIBAgIIJz+ujRbSAYwwDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNV BAYTAklUMQ0wCwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0 YWxpYSBEaWdpdGFsZTEwMC4GA1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8g ZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQcm9nZXR0byBTUElEIC0gR2VzdG9y aSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkqhkiG9w0BCQEWGnBy b3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05NzczNTAy MDU4NDAeFw0yMjA1MTAwMDAwMDBaFw0zMjA1MDkyMzU5NTlaMIGrMRowGAYDVQRh DBFWQVRJVC0wMTAzNTMxMDQxNDEcMBoGA1UEAwwTc3BpZC50ZWFtc3lzdGVtLmNv bTEaMBgGA1UECgwRVGVhbVN5c3RlbSBTLnAuQS4xKDAmBgNVBFMMH2h0dHBzOi8v c3BpZC50ZWFtc3lzdGVtLmNvbS9pZHAxCzAJBgNVBAYTAklUMQ8wDQYDVQQHDAZQ ZXNhcm8xCzAJBgNVBAgMAlBVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAyNJMgyn+iquzTvLR5Z/eYBfOoyJIfI3rYcj5WOSlTzlqXXBCzdcROm/JKgrf 3MOTEzH8RAn6XkSHXtJDtMpD7GlwYB0mo8scqDNtpszbhm/UXapJTrP7gy/UI3yf n99n4hvqkGOdld7w5vaAPS0w9PdcaRxY/7X4olHKBAx2cHAwiqhKuiFEDhfACRWs bw4gaIjVM7NuUtL/jG+PJV1NHrEn10vizE7IneMxDNqiQ14IjLL7pJMEPXwbXedz ZsModKKAXIX5reNSegEU1Y386BCkmg4IMWd+DglmMJ4uuzcga1AppgjDuqb8yFDa NOKy/0Jivh2rs7u9boE4cLVBPQIDAQABo4IDCTCCAwUwCQYDVR0TBAIwADAdBgNV HQ4EFgQU/q5NWlPmylmZTsX0C2MwZkrx3b4wHwYDVR0jBBgwFoAUyF8jl8Jbn9To hwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1UdEQQKMAiCBmlkcC5pdDAW BgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5odHRwczov L2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUF BwEBBF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2Nl cnRpZmljYXRpL1N1Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBz Oi8vMIIBzgYDVR0gBIIBxTCCAcEwCQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggr BgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmljYXRlIGNvbmZvcm1pbmcgd2l0 aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlmaWNhdG8gZWxl dHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAg ZGlnaXRhbGkgKElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJv dmlkZXIgKElkUCkwCAYGBACPegEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0 cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3Bz LnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lk Lmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG9w0B AQ0FAAOCAgEAG9XZeAkIuqSmYb6bq5WrcI2FQtVrfbMH1CXGDKytZUsH5phkGfk/ 8UaIfkbHhnWakM4H9J2gnvfhKorfMt2FHyXFFJ38hlWR8MhFziqthXLUxyLZpUMn h8CcNQyFpNz7xbZk/qN5yFfJyY4Rggm1qdgCNR1LsVI3hjuaORTAzvy4kLjfuU5r nVYPcxpHF7feJKlN03d8JRKYaIi5U+QVYtYJpTcE7jeYmn4Ewfry2BDCOsnljeYl gm3fF8EEVpMfHIhvJg8evATWmKWHpXL2BRtVrl7TfhvtWqKv4tLff+Lv2YqRpmYu oApA48/MB4QxwAPUBnmQb3CxVGs6OCbE/tdUfda9HuHP5MXYLtTVbRYu8pHEPnaN jPA8y90KRw2wiedgjgOG8BxOkhVF/cYs3yH+0hbPS5Oji27t0P2g9eG/p9TOy4AI gUykFimVFk6HV9znknrFSdgsePSp+T5zy45Jdi1z4/RgJN10szJfqEBuvd8MhUu4 meVgfDqXrqavCVzGpSLuicdk41sTOviBz+PEgbQ/qP9KHQv67SHoF4US9Pp9tkyj VFUs7lBnrlFAPpOzd97XdiZfotCA5umibqlxLshy4UK7yl2LZFllpxrfiXTCDASM KlMMIcIsWx0lU/qw5KPpqvXELiya791kohJTi+9pyG7LXIOHHA0whr0= + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + TeamSystem s.p.a. + TeamSystem s.p.a. + TeamSystem + TeamSystem + https://www.teamsystem.com + https://international.teamsystem.com/ww/ + + + \ No newline at end of file diff --git a/identity-service-rest-client-native/src/test/resources/idp_spid_data_tag2.xml b/identity-service-rest-client-native/src/test/resources/idp_spid_data_tag2.xml new file mode 100644 index 00000000..f247add0 --- /dev/null +++ b/identity-service-rest-client-native/src/test/resources/idp_spid_data_tag2.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + tOOejf44Q/QPfEY9FK8yHfEPtN0W2MsSQqhWL+9SNmE= + + + Y7vUoO0PW+Lj4eTQMvH4muBctCEnX+K+go+tO1PITSiJBRbO1ZdH1OHyCpt6ZzBcbxTkF1CTCx/WG4LUTYLG4inHdDR/8JFLTiZMFrQC90IpKpmtjF2KIUvstL6zS4TruuRbny8iZGgaybJVhNQEkjDtH7CeA296VOGYiI7w7+BapO5nbx/7nL9Rb05Gx92smo6Jno5dHV6TaT+SXZAkrF/EV+4LVJmzqMPFQKN3qXNIiHHFEgnKOyR5yBiyz26j29weBdYDao8oFx4Ml2oYpv4c2o0g5gpkCYc4SpNlqLlwU8E7hFd72W3rbmEngSgaaOpswlaKS3mI/2XxkmLG2g== + + + MIIEGDCCAwCgAwIBAgIJAOrYj9oLEJCwMA0GCSqGSIb3DQEBCwUAMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDAeFw0xOTA0MTExMDAyMDhaFw0yNTAzMDgxMDAyMDhaMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8kJVo+ugRrbbv9xhXCuVrqi4B7/MQzQc62ocwlFFujJNd4m1mXkUHFbgvwhRkQqo2DAmFeHiwCkJT3K1eeXIFhNFFroEzGPzONyekLpjNvmYIs1CFvirGOj0bkEiGaKEs+/umzGjxIhy5JQlqXE96y1+Izp2QhJimDK0/KNij8I1bzxseP0Ygc4SFveKS+7QO+PrLzWklEWGMs4DM5Zc3VRK7g4LWPWZhKdImC1rnS+/lEmHSvHisdVp/DJtbSrZwSYTRvTTz5IZDSq4kAzrDfpj16h7b3t3nFGc8UoY2Ro4tRZ3ahJ2r3b79yK6C5phY7CAANuW3gDdhVjiBNYs0CAwEAAaOByjCBxzAdBgNVHQ4EFgQU3/7kV2tbdFtphbSA4LH7+w8SkcwwgZcGA1UdIwSBjzCBjIAU3/7kV2tbdFtphbSA4LH7+w8SkcyhaaRnMGUxCzAJBgNVBAYTAklUMQ4wDAYDVQQIEwVJdGFseTENMAsGA1UEBxMEUm9tZTENMAsGA1UEChMEQWdJRDESMBAGA1UECxMJQWdJRCBURVNUMRQwEgYDVQQDEwthZ2lkLmdvdi5pdIIJAOrYj9oLEJCwMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJNFqXg/V3aimJKUmUaqmQEEoSc3qvXFITvT5f5bKw9yk/NVhR6wndL+z/24h1OdRqs76blgH8k116qWNkkDtt0AlSjQOx5qvFYh1UviOjNdRI4WkYONSw+vuavcx+fB6O5JDHNmMhMySKTnmRqTkyhjrch7zaFIWUSV7hsBuxpqmrWDoLWdXbV3eFH3mINA5AoIY/m0bZtzZ7YNgiFWzxQgekpxd0vcTseMnCcXnsAlctdir0FoCZztxMuZjlBjwLTtM6Ry3/48LMM8Z+lw7NMciKLLTGQyU8XmKKSSOh0dGh5Lrlt5GxIIJkH81C0YimWebz8464QPL3RbLnTKg+c= + + + + + + + + + + + + + + + JYGJryCuw9bp9PBqoxl1ogs1BX6rIdxN2Cld6uEDcMY= + + + CIEQ0HLKuPpklXui6C9d1pd1syuB9RcgoOI4yBU4QnAEoLvWAJoyTOUvjXfNN9pKehdvcPyW6LPmlonb6Mf5sKswUXdAbimSeYwgAO0U3mVAzGxGK543RjHGamGtz3G4IDW7FTqkG0QmQDAWfeq+CYJksdHFfKwvzY9l6PWCvmPIsaIjwJcFvWMWlwCBwABL3QmUqHkLmifk3/zcN1kmHEjlwMNpCwH32A2jgyFPho96BWQo+iMRjIaLUHfrnPNqMS49nYW5rUQM1nWiRrTMY74dxfd+xTUVZKVGSgL9ACQviTPwHOm4YYkhA+zjUTT22kEFb0fdMxrC0QzBM/FnLA== + + + MIID0jCCArqgAwIBAgIUXDUOKL3WuolxDw96Fk9es8rIt6kwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMB4XDTIxMTExMTE3MDMyMFoXDTI1MTExMDE3MDMyMFowgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6sIS3+3iZSaAIyVywahlbpua2uJ/XmpV68P1e1STJpHoaj32STdHhqZnnb4Y/FshP1NUolzNolPXAYDmDduW1OnGndJZ+G9Hjh1PCkdiRw+p0FjhQAsGJkn8NdgTIHLJjqN1qQwtOsVGab8ScyA3mtmj3xKYuBhUoweuATzC7f5r7FfIoc3cy6N5lgrpZpfeAChxLwoHVjoAVgIBuemi6HAzmd4/BI06KzOcR7+dBVi4+uiseldxrJ5bhnjZKIwgkX14y9UA84Y+e+rMtyT8cT3XXi9NazZl5Ej5/bQPqqVsbg6tXzQSfEJD6JEjuYeC0RUKMS/EJn3hL5VLzTJ1NwIDAQABoywwKjAdBgNVHQ4EFgQUfctFZ8bRtmEvXPRlqgVDuggY/ZwwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEA0lszHadknPfE17IWGWsgvlXOdKMnWcl9H5rEYmsWwDB9FJG9XAZvPMcVv1kkWi6XZI/8N2Twhu1BdZkdvntDRscuck8wxxIpkRV7CwlcqNFZ/IwjDBxOBa8Q1J850p+qP8A9apsLLPUlu/oLygNDWIXzcOjMqnPkEP+XXUNYPto5iV+OyDzLLacCYqDDHcvDewWLmEjt35X967KcM+m7K2zGRLWfqcZPIjJJOkpNjgcs+MaisMrGDyOKiD16v0LpwVyIpTqXvDk7KHo8CUNXDxyLxZzB6WffgnOgjXTfU3vluweOx0qQy/VxIupDlNBKiZB4gnt1oAfnaMbqla9wcw== + + + + + + + + MIID0jCCArqgAwIBAgIUXDUOKL3WuolxDw96Fk9es8rIt6kwDQYJKoZIhvcNAQELBQAwgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMB4XDTIxMTExMTE3MDMyMFoXDTI1MTExMDE3MDMyMFowgYsxCzAJBgNVBAYTAklUMS4wLAYDVQQKDCVUZWxlY29tIEl0YWxpYSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMSgwJgYDVQQLDB9TZXJ2aXppIHBlciBsJ2lkZW50aXRhIGRpZ2l0YWxlMSIwIAYDVQQDDBlUSSBUcnVzdCBUZWNobm9sb2dpZXMgc3JsMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6sIS3+3iZSaAIyVywahlbpua2uJ/XmpV68P1e1STJpHoaj32STdHhqZnnb4Y/FshP1NUolzNolPXAYDmDduW1OnGndJZ+G9Hjh1PCkdiRw+p0FjhQAsGJkn8NdgTIHLJjqN1qQwtOsVGab8ScyA3mtmj3xKYuBhUoweuATzC7f5r7FfIoc3cy6N5lgrpZpfeAChxLwoHVjoAVgIBuemi6HAzmd4/BI06KzOcR7+dBVi4+uiseldxrJ5bhnjZKIwgkX14y9UA84Y+e+rMtyT8cT3XXi9NazZl5Ej5/bQPqqVsbg6tXzQSfEJD6JEjuYeC0RUKMS/EJn3hL5VLzTJ1NwIDAQABoywwKjAdBgNVHQ4EFgQUfctFZ8bRtmEvXPRlqgVDuggY/ZwwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEA0lszHadknPfE17IWGWsgvlXOdKMnWcl9H5rEYmsWwDB9FJG9XAZvPMcVv1kkWi6XZI/8N2Twhu1BdZkdvntDRscuck8wxxIpkRV7CwlcqNFZ/IwjDBxOBa8Q1J850p+qP8A9apsLLPUlu/oLygNDWIXzcOjMqnPkEP+XXUNYPto5iV+OyDzLLacCYqDDHcvDewWLmEjt35X967KcM+m7K2zGRLWfqcZPIjJJOkpNjgcs+MaisMrGDyOKiD16v0LpwVyIpTqXvDk7KHo8CUNXDxyLxZzB6WffgnOgjXTfU3vluweOx0qQy/VxIupDlNBKiZB4gnt1oAfnaMbqla9wcw== + CN=TI Trust Technologies srl,OU=Servizi per l'identita digitale,O=Telecom Italia Trust Technologies srl,C=IT + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + TI Trust Technologies srl + Trust Technologies srl + https://www.trusttechnologies.it + + + + + + + + + + + + + + zanMEv9e3IoYmz27wv5RQCYhp7IuxdvUwb/VCjOjosA= + + + EyJLBOIVDVK2UM0VYzm+ukfwm34rO2a+AmXnyem+FpLF8mHUdGe2vBafE2YiV6sr7H6/zg0ozeRgPVos9E5xc0LWZwPFK8KWaMiQwrdFVwxAVp3SL0DMXs8msj9+zMnrFb9zGNq9/SoSgJm9BNcjxud+9Ky4XlS30pk7deHy/KgdGpO0cnWOoaYbWfPhHmQ40y7lMF9WZnHibDNTbYPGFMhUgGjGauTH5x+HvGEbreLSpTMEt07Hc0KNV/TSCsUCKpbv7z2YOFbQ5yt6IO2MrpgOQIqr8JF1oC5t/C+5SltkpLUxvYwqh+gF91u1METuqURTzNe1Iz+qb0WFNuyMxw== + + + MIIExTCCA62gAwIBAgIQH32A70kY92tuXB8AGi2DdDANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJJVDEYMBYGA1UECgwPQXJ1YmFQRUMgUy5wLkEuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eUIxIDAeBgNVBAMMF0FydWJhUEVDIFMucC5BLiBORyBDQSAyMB4XDTIwMDEyMjAwMDAw MFoXDTI1MDEyMTIzNTk1OVowgaAxCzAJBgNVBAYTAklUMRYwFAYDVQQKDA1BcnViYSBQRUMgc3Bh MREwDwYDVQQLDAhQcm9kb3R0bzEWMBQGA1UEAwwNcGVjLml0IHBlYy5pdDEZMBcGA1UEBRMQWFhY WFhYMDBYMDBYMDAwWDEPMA0GA1UEKgwGcGVjLml0MQ8wDQYDVQQEDAZwZWMuaXQxETAPBgNVBC4T CDIwODc2Mzc5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqt2oHJhcp03l73p+QYpE J+f3jYYj0W0gos0RItZx/w4vpsiKBygaqDNVWSwfo1aPdVDIX13f62O+lBki29KTt+QWv5K6SGHD UXYPntRdEQlicIBh2Z0HfrM7fDl+xeJrMp1s4dsSQAuB5TJOlFZq7xCQuukytGWBTvjfcN/os5aE sEg+RbtZHJR26SbbUcIqWb27Swgj/9jwK+tvzLnP4w8FNvEOrNfR0XwTMNDFrwbOCuWgthv5jNBs VZaoqNwiA/MxYt+gTOMj/o5PWKk8Wpm6o/7/+lWAoxh0v8x9OkbIi+YaFpIxuCcUqsrJJk63x2gH Cc2nr+yclYUhsKD/AwIDAQABo4IBLDCCASgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBTKQ3+N PGcXFk8nX994vMTVpba1EzBHBgNVHSAEQDA+MDwGCysGAQQBgegtAQEBMC0wKwYIKwYBBQUHAgEW H2h0dHBzOi8vY2EuYXJ1YmFwZWMuaXQvY3BzLmh0bWwwWAYDVR0fBFEwTzBNoEugSYZHaHR0cDov L2NybC5hcnViYXBlYy5pdC9BcnViYVBFQ1NwQUNlcnRpZmljYXRpb25BdXRob3JpdHlCL0xhdGVz dENSTC5jcmwwHwYDVR0jBBgwFoAU8v9jQBwRQv3M3/FZ9m7omYcxR3kwMwYIKwYBBQUHAQEEJzAl MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5hcnViYXBlYy5pdDANBgkqhkiG9w0BAQsFAAOCAQEA ZKpor1MrrYwPw+IuPZElQAuNzXsaSWSnn/QQwJtW49c4rFM4mEud9c61p9XxIIbgQKmDmNbzC+Dm wJSZ8ILdCAyBHmY3BehVRAy3KRA2KQhS9kd4vywf5KVYd1L5hQa9DBrusxF7i1X/SEeLQgoKkov0 R8v43UncqXS/ql50ovJFxi938Rv4rVwa8o0hqqc6WUcjkidB6M9aNJLIbOZN3xNUgC28qIr8y7N8 lbxWbwVrGxqKDtpaA9J0hOOXxwuTfSd1zOtT0KSSSUQ53QGOPnxyjxYDQbJu60/lBPuUV5wb/Z2r gpeUH1/n7limHV5sVmOZgSnf18T+0STANCfkXg== + + + + + + + + + + + MIIExTCCA62gAwIBAgIQH32A70kY92tuXB8AGi2DdDANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJJVDEYMBYGA1UECgwPQXJ1YmFQRUMgUy5wLkEuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eUIxIDAeBgNVBAMMF0FydWJhUEVDIFMucC5BLiBORyBDQSAyMB4XDTIwMDEyMjAwMDAw MFoXDTI1MDEyMTIzNTk1OVowgaAxCzAJBgNVBAYTAklUMRYwFAYDVQQKDA1BcnViYSBQRUMgc3Bh MREwDwYDVQQLDAhQcm9kb3R0bzEWMBQGA1UEAwwNcGVjLml0IHBlYy5pdDEZMBcGA1UEBRMQWFhY WFhYMDBYMDBYMDAwWDEPMA0GA1UEKgwGcGVjLml0MQ8wDQYDVQQEDAZwZWMuaXQxETAPBgNVBC4T CDIwODc2Mzc5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqt2oHJhcp03l73p+QYpE J+f3jYYj0W0gos0RItZx/w4vpsiKBygaqDNVWSwfo1aPdVDIX13f62O+lBki29KTt+QWv5K6SGHD UXYPntRdEQlicIBh2Z0HfrM7fDl+xeJrMp1s4dsSQAuB5TJOlFZq7xCQuukytGWBTvjfcN/os5aE sEg+RbtZHJR26SbbUcIqWb27Swgj/9jwK+tvzLnP4w8FNvEOrNfR0XwTMNDFrwbOCuWgthv5jNBs VZaoqNwiA/MxYt+gTOMj/o5PWKk8Wpm6o/7/+lWAoxh0v8x9OkbIi+YaFpIxuCcUqsrJJk63x2gH Cc2nr+yclYUhsKD/AwIDAQABo4IBLDCCASgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBTKQ3+N PGcXFk8nX994vMTVpba1EzBHBgNVHSAEQDA+MDwGCysGAQQBgegtAQEBMC0wKwYIKwYBBQUHAgEW H2h0dHBzOi8vY2EuYXJ1YmFwZWMuaXQvY3BzLmh0bWwwWAYDVR0fBFEwTzBNoEugSYZHaHR0cDov L2NybC5hcnViYXBlYy5pdC9BcnViYVBFQ1NwQUNlcnRpZmljYXRpb25BdXRob3JpdHlCL0xhdGVz dENSTC5jcmwwHwYDVR0jBBgwFoAU8v9jQBwRQv3M3/FZ9m7omYcxR3kwMwYIKwYBBQUHAQEEJzAl MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5hcnViYXBlYy5pdDANBgkqhkiG9w0BAQsFAAOCAQEA ZKpor1MrrYwPw+IuPZElQAuNzXsaSWSnn/QQwJtW49c4rFM4mEud9c61p9XxIIbgQKmDmNbzC+Dm wJSZ8ILdCAyBHmY3BehVRAy3KRA2KQhS9kd4vywf5KVYd1L5hQa9DBrusxF7i1X/SEeLQgoKkov0 R8v43UncqXS/ql50ovJFxi938Rv4rVwa8o0hqqc6WUcjkidB6M9aNJLIbOZN3xNUgC28qIr8y7N8 lbxWbwVrGxqKDtpaA9J0hOOXxwuTfSd1zOtT0KSSSUQ53QGOPnxyjxYDQbJu60/lBPuUV5wb/Z2r gpeUH1/n7limHV5sVmOZgSnf18T+0STANCfkXg== + + + + + + + MIIExTCCA62gAwIBAgIQIHtEvEhGM77HwqsuvSbi9zANBgkqhkiG9w0BAQsFADBsMQswCQYDVQQG EwJJVDEYMBYGA1UECgwPQXJ1YmFQRUMgUy5wLkEuMSEwHwYDVQQLDBhDZXJ0aWZpY2F0aW9uIEF1 dGhvcml0eUIxIDAeBgNVBAMMF0FydWJhUEVDIFMucC5BLiBORyBDQSAyMB4XDTE3MDEyMzAwMDAw MFoXDTIwMDEyMzIzNTk1OVowgaAxCzAJBgNVBAYTAklUMRYwFAYDVQQKDA1BcnViYSBQRUMgc3Bh MREwDwYDVQQLDAhQcm9kb3R0bzEWMBQGA1UEAwwNcGVjLml0IHBlYy5pdDEZMBcGA1UEBRMQWFhY WFhYMDBYMDBYMDAwWDEPMA0GA1UEKgwGcGVjLml0MQ8wDQYDVQQEDAZwZWMuaXQxETAPBgNVBC4T CDE2MzQ1MzgzMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqt2oHJhcp03l73p+QYpE J+f3jYYj0W0gos0RItZx/w4vpsiKBygaqDNVWSwfo1aPdVDIX13f62O+lBki29KTt+QWv5K6SGHD UXYPntRdEQlicIBh2Z0HfrM7fDl+xeJrMp1s4dsSQAuB5TJOlFZq7xCQuukytGWBTvjfcN/os5aE sEg+RbtZHJR26SbbUcIqWb27Swgj/9jwK+tvzLnP4w8FNvEOrNfR0XwTMNDFrwbOCuWgthv5jNBs VZaoqNwiA/MxYt+gTOMj/o5PWKk8Wpm6o/7/+lWAoxh0v8x9OkbIi+YaFpIxuCcUqsrJJk63x2gH Cc2nr+yclYUhsKD/AwIDAQABo4IBLDCCASgwDgYDVR0PAQH/BAQDAgeAMB0GA1UdDgQWBBTKQ3+N PGcXFk8nX994vMTVpba1EzBHBgNVHSAEQDA+MDwGCysGAQQBgegtAQEBMC0wKwYIKwYBBQUHAgEW H2h0dHBzOi8vY2EuYXJ1YmFwZWMuaXQvY3BzLmh0bWwwWAYDVR0fBFEwTzBNoEugSYZHaHR0cDov L2NybC5hcnViYXBlYy5pdC9BcnViYVBFQ1NwQUNlcnRpZmljYXRpb25BdXRob3JpdHlCL0xhdGVz dENSTC5jcmwwHwYDVR0jBBgwFoAU8v9jQBwRQv3M3/FZ9m7omYcxR3kwMwYIKwYBBQUHAQEEJzAl MCMGCCsGAQUFBzABhhdodHRwOi8vb2NzcC5hcnViYXBlYy5pdDANBgkqhkiG9w0BAQsFAAOCAQEA nEw0NuaspbpDjA5wggwFtfQydU6b3Bw2/KXPRKS2JoqGmx0SYKj+L17A2KUBa2c7gDtKXYz0FLT6 0Bv0pmBN/oYCgVMEBJKqwRwdki9YjEBwyCZwNEx1kDAyyqFEVU9vw/OQfrAdp7MTbuZGFKknVt7b 9wOYy/Op9FiUaTg6SuOy0ep+rqhihltYNAAl4L6fY45mHvqa5vvVG30OvLW/S4uvRYUXYwY6KhWv NdDf5CnFugnuEZtHJrVe4wx9aO5GvFLFZ/mQ35C5mXPQ7nIb0CDdLBJdz82nUoLSA5BUbeXAUkfa hW/hLxLdhks68/TK694xVIuiB40pvMmJwxIyDA== + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + ArubaPEC S.p.A. + ArubaPEC S.p.A. + https://www.pec.it/ + + + + + + + + + + + + + + qjmkoKCiL8y4gBEXT8DVubGLvH7N8roi/lpgTQww+dw= + + + fm30Z3Ugxcae7W+nvNm9SDeMjH+sWDDcUnobMaBemlKLSj7KiOT1bc/HVIqY9sH/AoV6LiDZ05PSnQLhL8HI1Q71CGpRUTIt3b2oL7D+4iOsirKjFCYjHq75FBR5esaejhvHWIpnTx6Dp+CigVM2eH9IrBIY3Xkw2FqD7Q9CCc/bl33OUjEGS2o4VpjWyFCVsPMWy0QxsC0Em+fTbPjlMX8n55SKvSZy4ItNfoahLy/+8DD+VsWA9u3fPbValsfLZqyziBRjTcdc0nAeQliXj+ajjCbqGmG+jLw3mNRXglF5Kqq3hcHe5a/9WqKCh2x82ReMT7KhFRwQl+lhsBoWRg== + + + MIIIRDCCBiygAwIBAgIINO3vGmIYBP0wDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNVBAYTAklUMQ0w CwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0YWxpYSBEaWdpdGFsZTEwMC4G A1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8gZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQ cm9nZXR0byBTUElEIC0gR2VzdG9yaSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkq hkiG9w0BCQEWGnByb3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05Nzcz NTAyMDU4NDAeFw0yMzAyMjIwMDAwMDBaFw0zMzAyMjEyMzU5NTlaMIG3MQswCQYDVQQGEwJJVDEO MAwGA1UECAwFSXRhbHkxDzANBgNVBAcMBlBhZG92YTEcMBoGA1UECgwTSW5mb0NhbWVyZSBTLkMu cC5BLjElMCMGA1UEAwwcSW5mb0NhbWVyZSBJZGVudGl0eSBQcm92aWRlcjEaMBgGA1UEYQwRVkFU SVQtMDIzMTM4MjEwMDcxJjAkBgNVBFMMHWh0dHBzOi8vaWRzcGlkLmluZm9jYW1lcmUuaXQvMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8Div4aLGUtoDoP5RWbRwqvEtjnDcCCUS+Sz ChAsJP+UYjWl+R4R4Y7Lz+WId3LJqey+QIyvviD6vH/QloqzVRG/JabW70NZylk1UX2isss8mRvt ceK7nYVxjTIoQpasg0OsCevgljjnFxRm8c3zUpYfjC5zzr/jZ9HjFKghGCZGjBavNNgiGIo7e7jb dmGH5N9z+uQ8KRG/p2JRxD0YeVy2+EV2o0cQO2duE383EganLKPcQ9AnxkLE1K0cpP7XQDtUgWTP qsL9+OLTl13KhVM2TMK7EkAm00WCOl1aX3E7g9Qgw+4fUm308v77OSDe77dY8hohZWPRTwjemaHA 2QIDAQABo4IDDDCCAwgwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUddqeUDWjVXqV3PSfTyzAmlFD TDMwHwYDVR0jBBgwFoAUyF8jl8Jbn9TohwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1Ud EQQKMAiCBmlkcC5pdDAWBgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5o dHRwczovL2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUFBwEB BF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2NlcnRpZmljYXRpL1N1 Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBzOi8vMIIBzgYDVR0gBIIBxTCCAcEw CQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggrBgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmlj YXRlIGNvbmZvcm1pbmcgd2l0aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlm aWNhdG8gZWxldHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAgZGlnaXRhbGkg KElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJvdmlkZXIgKElkUCkwCAYGBACP egEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMv QWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6 Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG 9w0BAQ0FAAOCAgEAoYZlSArAwFZDknzUG5Z3NQQUT3JKaOTT8TrNi/F8yL4mz0qjaJaJURMQauKZ eNQiGlGvNyGp3SlgGYFHasZ9FrtpxbxGXVkNreer61kFhY/I3ZdU4DjGW2qPs9csP+W06R4k3OFF hua7DFyyoxAWQYIFisucT3E3+N32XuLQPDqjMwnvSdT4FLE6c4QIpJl3fQYlCsyhAxrNWlrndP1Q 1f97oF6oB7tWR5Ae1/ixDN0q5QJeEnapNaDjvS2wEzVNRYW/RzbHPPZQ1Zs0jLEfXsuwD3A0iJiy D0GSgXYUibqH3VExCqQ1yjEDwjq3zF8bcSaoAQm2fRY3KIYSbI18kpPhFmNTJWbv303dQe6MzIOR LUzs0tSHfB+mtclrHgqqaKwZZmHiGUYTV3bziWjMDacG9gRJtyS04LYZdkSBcSOn3dYXSM18F58p bKifcdajFmUicUWlI/2TFArDguh5TUekLQKsTi4tMnmk5RWA4oMLjZ+q2r4jMNVuoZ0+FGFbrfdh z+Kyo3gWdyZyY+Uqr1aiL+QTnht8hVTVrgOf4RJW/3z5hgYLSyx3INT6GDtaSr5V+orYfSpbvU1X linz+iP4vfYKmpFdF1cxjTYkNQB7/DW9nXYC4PwXjI5253rha8g/BLdsIEWD73Q1GM1HieSVX+tN PBbjHpKLz2UVZEM= + + + + + + + + + + + MIIIRDCCBiygAwIBAgIINO3vGmIYBP0wDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNVBAYTAklUMQ0w CwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0YWxpYSBEaWdpdGFsZTEwMC4G A1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8gZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQ cm9nZXR0byBTUElEIC0gR2VzdG9yaSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkq hkiG9w0BCQEWGnByb3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05Nzcz NTAyMDU4NDAeFw0yMzAyMjIwMDAwMDBaFw0zMzAyMjEyMzU5NTlaMIG3MQswCQYDVQQGEwJJVDEO MAwGA1UECAwFSXRhbHkxDzANBgNVBAcMBlBhZG92YTEcMBoGA1UECgwTSW5mb0NhbWVyZSBTLkMu cC5BLjElMCMGA1UEAwwcSW5mb0NhbWVyZSBJZGVudGl0eSBQcm92aWRlcjEaMBgGA1UEYQwRVkFU SVQtMDIzMTM4MjEwMDcxJjAkBgNVBFMMHWh0dHBzOi8vaWRzcGlkLmluZm9jYW1lcmUuaXQvMIIB IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo8Div4aLGUtoDoP5RWbRwqvEtjnDcCCUS+Sz ChAsJP+UYjWl+R4R4Y7Lz+WId3LJqey+QIyvviD6vH/QloqzVRG/JabW70NZylk1UX2isss8mRvt ceK7nYVxjTIoQpasg0OsCevgljjnFxRm8c3zUpYfjC5zzr/jZ9HjFKghGCZGjBavNNgiGIo7e7jb dmGH5N9z+uQ8KRG/p2JRxD0YeVy2+EV2o0cQO2duE383EganLKPcQ9AnxkLE1K0cpP7XQDtUgWTP qsL9+OLTl13KhVM2TMK7EkAm00WCOl1aX3E7g9Qgw+4fUm308v77OSDe77dY8hohZWPRTwjemaHA 2QIDAQABo4IDDDCCAwgwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUddqeUDWjVXqV3PSfTyzAmlFD TDMwHwYDVR0jBBgwFoAUyF8jl8Jbn9TohwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1Ud EQQKMAiCBmlkcC5pdDAWBgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5o dHRwczovL2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUFBwEB BF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2NlcnRpZmljYXRpL1N1 Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBzOi8vMIIBzgYDVR0gBIIBxTCCAcEw CQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggrBgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmlj YXRlIGNvbmZvcm1pbmcgd2l0aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlm aWNhdG8gZWxldHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAgZGlnaXRhbGkg KElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJvdmlkZXIgKElkUCkwCAYGBACP egEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMv QWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6 Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG 9w0BAQ0FAAOCAgEAoYZlSArAwFZDknzUG5Z3NQQUT3JKaOTT8TrNi/F8yL4mz0qjaJaJURMQauKZ eNQiGlGvNyGp3SlgGYFHasZ9FrtpxbxGXVkNreer61kFhY/I3ZdU4DjGW2qPs9csP+W06R4k3OFF hua7DFyyoxAWQYIFisucT3E3+N32XuLQPDqjMwnvSdT4FLE6c4QIpJl3fQYlCsyhAxrNWlrndP1Q 1f97oF6oB7tWR5Ae1/ixDN0q5QJeEnapNaDjvS2wEzVNRYW/RzbHPPZQ1Zs0jLEfXsuwD3A0iJiy D0GSgXYUibqH3VExCqQ1yjEDwjq3zF8bcSaoAQm2fRY3KIYSbI18kpPhFmNTJWbv303dQe6MzIOR LUzs0tSHfB+mtclrHgqqaKwZZmHiGUYTV3bziWjMDacG9gRJtyS04LYZdkSBcSOn3dYXSM18F58p bKifcdajFmUicUWlI/2TFArDguh5TUekLQKsTi4tMnmk5RWA4oMLjZ+q2r4jMNVuoZ0+FGFbrfdh z+Kyo3gWdyZyY+Uqr1aiL+QTnht8hVTVrgOf4RJW/3z5hgYLSyx3INT6GDtaSr5V+orYfSpbvU1X linz+iP4vfYKmpFdF1cxjTYkNQB7/DW9nXYC4PwXjI5253rha8g/BLdsIEWD73Q1GM1HieSVX+tN PBbjHpKLz2UVZEM= + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + InfoCamere S.C.p.A. + InfoCamere S.C.p.A. + https://www.infocamere.it/ + + + + + + + + + + + + + + ix5zJ0s5HicXBtbud2nW7dwhwEVB6jZnzhFkbFLAYVs= + + + EdhsS12CrldyKtXkWCHY7PlrD8Uc2HyKd2a40aNsEabBJxH0gsKfzO85HSRw1jLBVf0352moDNAp vqrH24ImHV9umqzxqY5SAXx7ISeGl56kmWB4CWGPK7X7Vb0iDosDzoI60vHlipVmdbaqlwOZQG79 xEoyo4bU/IxhdFhr0wl8b3SnGTWlFS3iThaz4g2dmWlzcjVcf+s5CigClhqToedKxPbY2CGl+U78 sgNTlR2cGIe9gjRlQIboXUr14SzDJgIOLkFIGyuIlgA7vk85/HDSXGhEIa5r/N9Tb+dTc+PXPxV9 Lk9Oy88WbYOqfvOKnkzVjmWj026DTT2N9uBPmg== + + + MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4 fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1 MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0 uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc= + + + + + + + + + + + MIIFgzCCA2ugAwIBAgIIJSppAZKg/XQwDQYJKoZIhvcNAQELBQAwZTELMAkGA1UEBhMCSVQxHjAc BgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEaMBgGA1UEYQwRVkFUSVQtMDExMTQ2MDEwMDYx GjAYBgNVBAMMEVBvc3RlIEl0YWxpYW5lIENBMB4XDTIxMDIxODExNDYzMVoXDTI0MDIxOTExNDYz MVowQzELMAkGA1UEBhMCSVQxHjAcBgNVBAoMFVBvc3RlIEl0YWxpYW5lIFMucC5BLjEUMBIGA1UE AwwLaWRwLXBvc3RlaWQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZFEtJoEHFAjpC aZcj5DVWrRDyaLZyu31XApslbo87CyWz61OJMtw6QQU0MdCtrYbtSJ6vJwx7/6EUjsZ3u4x3EPLd lkyiGOqukPwATv4c7TVOUVs5onIqTphM9b+AHRg4ehiMGesm/9d7RIaLuN79iPUvdLn6WP3idAfE w+rhJ/wYEQ0h1Xm5osNUgtWcBGavZIjLssWNrDDfJYxXH3QZ0kI6feEvLCJwgjXLGkBuhFehNhM4 fhbX9iUCWwwkJ3JsP2++Rc/iTA0LZhiUsXNNq7gBcLAJ9UX2V1dWjTzBHevfHspzt4e0VgIIwbDR qsRtF8VUPSDYYbLoqwbLt18XAgMBAAGjggFXMIIBUzA/BggrBgEFBQcBAQQzMDEwLwYIKwYBBQUH MAGGI2h0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQvcGktb2NzcENBMB0GA1UdDgQWBBRL64pGUJHw Y7ok6cRMUgXvMBoLMjAfBgNVHSMEGDAWgBRs0025F7hHd0d+ULyAaELPZ7w/eTA+BgNVHSAENzA1 MDMGCCtMMAEFAQEEMCcwJQYIKwYBBQUHAgEWGWh0dHA6Ly9wb3N0ZWNlcnQucG9zdGUuaXQwOAYD VR0fBDEwLzAtoCugKYYnaHR0cDovL3Bvc3RlY2VydC5wb3N0ZS5pdC9waS1DQS9jcmwuY3JsMA4G A1UdDwEB/wQEAwIFoDAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwJwYDVR0RBCAwHoEc aWRwLXBvc3RlaWRAcG9zdGVpdGFsaWFuZS5pdDANBgkqhkiG9w0BAQsFAAOCAgEAp0EhITlTx+cO aoXw//nBl6Q4y82MfSGfPJIw3ROV1z3tHBctaksi/RxAzyMD5beO2s8Q6lXx0sLMCcuUQmzHj3eJ bqn+6sIUr000dSlX/iPgVUc2dvPIZZg9xu38J8NvCfrtgAGY5iMVFMd3CZLFw0ybr+Bx/1K/NhQO 7jxn0RSGA1J4mM2syVhEDUODs9kz3T4kXYUofwwvPL1a9xB9RBqbp7plYtbBBdftEORUQrWzH1mz NO4nlFkX9qgVrgFIIJJT2KadHoop1r65O9ffncK14qpNo3eTsNDq3hRlteb7ylmlJ8CoakUWZeXD DP9ZboWxZkyp+9903OrToRvOgeWSc+YrqcRZOv7r6tTALTk4U9OTKDG9/eNWSGQqD7Qd/9rssfF0 uJEGHnbsk/Hvdxn8apgWN1Zwt6tsT7f/DO0Pdlaso9g7PVy8R+B3VkWAh76uCcICIPFBluC/ljaH V8hI+VsCLpMClo83YMCEM6E6nAPD22+fDR/DF9P73P04yUvJVHx4cnHPrpxVrPbaJoKrr9mUOLFy VRekX78ZRgiFiKYDNsiq9+148oRy+VehpmBoQ+T2EPeDFQ8JJ4xT8H7qdyr1swSk/9Lu4K0kw/yC TSb9K/wCuiHiuoSB54rzJoQxz90gS868r/+JGahYwHY5dUh1RbA4g5N8H3TDThc= + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + Poste Italiane SpA + Poste Italiane SpA + https://www.poste.it + + + + + + + + + + + + + + D/A8yDFktAAd8ZdH4txugRyfui6Yy9cYlS/ilyzB6qg= + + + YN5NOuJNo8+3p5j+vQ+l0tA8hsvgfqwHu4amDbJeV3ltCa2ev2chti18Tekswx/FjkpVo7Xu1Thi3jcxRalyeoY2XAPhhEigI+JSA6+JcJUC91Gm+b9+LO6mnKba+epGBdfoDoj66tBCeSXD1AOSid1WcCEjEoFfMwIx2TJQVhz/Vx6JhAPYjbjyiXgus7hI4JPFQla3msjrrCJ8umU635e1dyFPqxTt1jIRP5oZnSGx0moP5dGRMhU+mu2mtcJOGiz02km+TtmxIRgACJ8HB3sEP3HDwtQmVguhDCUluY94UfU42dIsaKOZB2mVEVjHDzCVxGGYPAYaY8lu1ZDO5g== + + + MIIDazCCAlOgAwIBAgIED8R+MDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJJVDELMAkGA1UECBMCRkkxETAPBgNVBAcTCGZsb3JlbmNlMREwDwYDVQQKEwhyZWdpc3RlcjERMA8GA1UECxMIcmVnaXN0ZXIxETAPBgNVBAMTCHJlZ2lzdGVyMB4XDTE3MDcxMDEwMzM0OVoXDTI3MDcwODEwMzM0OVowZjELMAkGA1UEBhMCSVQxCzAJBgNVBAgTAkZJMREwDwYDVQQHEwhmbG9yZW5jZTERMA8GA1UEChMIcmVnaXN0ZXIxETAPBgNVBAsTCHJlZ2lzdGVyMREwDwYDVQQDEwhyZWdpc3RlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANkYXHbm3q6xt3wrLAXnytswtj2JE1MM8aYmNXkTgDMCwO/+ahQOoQru6IBTbjfWH9jr+Woy54FDdX6bHl+5/mO6l/yAB/bKgwe5HmUjZJ5oakJjWucsSm+VkEwN2HquBZoN+mktju00xvLX5VAjmDHvZc/b8NhNr/FRKlYITboygkhGiUwGI3wLf3IaB76J0o7ugpW2WNLcywpX+p1VWZAMCdHBveBe/e42hh6WnWPqdwYUWHOgJ8HX4IzCHifiS1n6eUMgtoTQOmSvTQDwSjD0WWJE8tWSYt+txXg1t+3A3tbZOFu7T442wE7DtMdUL4+8gimQS+e8PxDK1uTqIPUCAwEAAaMhMB8wHQYDVR0OBBYEFMCgo1gzCIcUThQIs5g5ikfv1D7eMA0GCSqGSIb3DQEBCwUAA4IBAQBnGw3i3hQ37L8vyelkyZMeO3tLK65Cqti4oVrQZxClGV5zNA6fIMDY8Mci1UhLwjzp29POd/sez0vuHZ/Vmmygzoye4jTKr6c3jAh0u81FTzefBU+vIietm9RuV3sd7D9xq6EqOY1NDL+rkvBcTFtiwLEUm2kHYu/U67jk73pxOtmqxQvQeMU8oi42tehMZGLIGp3U5lGS8YGGl+GtkkQ2Z5/PSm67HGP81kTArG/QX+bX+ykypTJVg9hfb9zOFQidp1HkCRIez6YhDiP/ZLurd6Grt/wVfZPNBO8EOgy25AkRZlp+UD686BFg7qq5KKEbz3qmPrj8deHL3duacZcp + + + + + + + + MIIDazCCAlOgAwIBAgIED8R+MDANBgkqhkiG9w0BAQsFADBmMQswCQYDVQQGEwJJVDELMAkGA1UECBMCRkkxETAPBgNVBAcTCGZsb3JlbmNlMREwDwYDVQQKEwhyZWdpc3RlcjERMA8GA1UECxMIcmVnaXN0ZXIxETAPBgNVBAMTCHJlZ2lzdGVyMB4XDTE3MDcxMDEwMzM0OVoXDTI3MDcwODEwMzM0OVowZjELMAkGA1UEBhMCSVQxCzAJBgNVBAgTAkZJMREwDwYDVQQHEwhmbG9yZW5jZTERMA8GA1UEChMIcmVnaXN0ZXIxETAPBgNVBAsTCHJlZ2lzdGVyMREwDwYDVQQDEwhyZWdpc3RlcjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANkYXHbm3q6xt3wrLAXnytswtj2JE1MM8aYmNXkTgDMCwO/+ahQOoQru6IBTbjfWH9jr+Woy54FDdX6bHl+5/mO6l/yAB/bKgwe5HmUjZJ5oakJjWucsSm+VkEwN2HquBZoN+mktju00xvLX5VAjmDHvZc/b8NhNr/FRKlYITboygkhGiUwGI3wLf3IaB76J0o7ugpW2WNLcywpX+p1VWZAMCdHBveBe/e42hh6WnWPqdwYUWHOgJ8HX4IzCHifiS1n6eUMgtoTQOmSvTQDwSjD0WWJE8tWSYt+txXg1t+3A3tbZOFu7T442wE7DtMdUL4+8gimQS+e8PxDK1uTqIPUCAwEAAaMhMB8wHQYDVR0OBBYEFMCgo1gzCIcUThQIs5g5ikfv1D7eMA0GCSqGSIb3DQEBCwUAA4IBAQBnGw3i3hQ37L8vyelkyZMeO3tLK65Cqti4oVrQZxClGV5zNA6fIMDY8Mci1UhLwjzp29POd/sez0vuHZ/Vmmygzoye4jTKr6c3jAh0u81FTzefBU+vIietm9RuV3sd7D9xq6EqOY1NDL+rkvBcTFtiwLEUm2kHYu/U67jk73pxOtmqxQvQeMU8oi42tehMZGLIGp3U5lGS8YGGl+GtkkQ2Z5/PSm67HGP81kTArG/QX+bX+ykypTJVg9hfb9zOFQidp1HkCRIez6YhDiP/ZLurd6Grt/wVfZPNBO8EOgy25AkRZlp+UD686BFg7qq5KKEbz3qmPrj8deHL3duacZcp + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + Register.it S.p.A. + Register.it S.p.A. + https//www.register.it + + + + + + + + + + + + + + khZostyVOFwCtkt5rHSLJ9UxgM6rKAR+7O+yLQ++5jI= + + + rnN5Omq7oWmuqqW13KbMr+taRMdqJ85W9+WpcWLX2Wsax26kTHBgD78vVfVWKuejMKeK6F8oSTkiipMlnGLXMniQplvI9rZJO3fj3ygG3qdwPCqFqJ7e9iYPZ5gIMjLuePyoAfcvSdAQiMBxeTX7nwjpLrzB51RaxGNTmJNJv44zbtdR++I1my8nSDRycq5o6+uR+k/SEOiR65+uQbiBvL9Or/N4sEoJyFx23AbQjurWiBzqgHwHPf8tDLBbDa8mjPhReXGK7aePymQU4GlSeZBxSHcSad04gQjlcgayp4d+O43SBczmtueV6szrAqURnhdj6L1PRvyusfNNk04bZA== + + + MIIINTCCBh2gAwIBAgIIJz+ujRbSAYwwDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNV BAYTAklUMQ0wCwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0 YWxpYSBEaWdpdGFsZTEwMC4GA1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8g ZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQcm9nZXR0byBTUElEIC0gR2VzdG9y aSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkqhkiG9w0BCQEWGnBy b3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05NzczNTAy MDU4NDAeFw0yMjA1MTAwMDAwMDBaFw0zMjA1MDkyMzU5NTlaMIGrMRowGAYDVQRh DBFWQVRJVC0wMTAzNTMxMDQxNDEcMBoGA1UEAwwTc3BpZC50ZWFtc3lzdGVtLmNv bTEaMBgGA1UECgwRVGVhbVN5c3RlbSBTLnAuQS4xKDAmBgNVBFMMH2h0dHBzOi8v c3BpZC50ZWFtc3lzdGVtLmNvbS9pZHAxCzAJBgNVBAYTAklUMQ8wDQYDVQQHDAZQ ZXNhcm8xCzAJBgNVBAgMAlBVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAyNJMgyn+iquzTvLR5Z/eYBfOoyJIfI3rYcj5WOSlTzlqXXBCzdcROm/JKgrf 3MOTEzH8RAn6XkSHXtJDtMpD7GlwYB0mo8scqDNtpszbhm/UXapJTrP7gy/UI3yf n99n4hvqkGOdld7w5vaAPS0w9PdcaRxY/7X4olHKBAx2cHAwiqhKuiFEDhfACRWs bw4gaIjVM7NuUtL/jG+PJV1NHrEn10vizE7IneMxDNqiQ14IjLL7pJMEPXwbXedz ZsModKKAXIX5reNSegEU1Y386BCkmg4IMWd+DglmMJ4uuzcga1AppgjDuqb8yFDa NOKy/0Jivh2rs7u9boE4cLVBPQIDAQABo4IDCTCCAwUwCQYDVR0TBAIwADAdBgNV HQ4EFgQU/q5NWlPmylmZTsX0C2MwZkrx3b4wHwYDVR0jBBgwFoAUyF8jl8Jbn9To hwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1UdEQQKMAiCBmlkcC5pdDAW BgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5odHRwczov L2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUF BwEBBF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2Nl cnRpZmljYXRpL1N1Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBz Oi8vMIIBzgYDVR0gBIIBxTCCAcEwCQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggr BgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmljYXRlIGNvbmZvcm1pbmcgd2l0 aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlmaWNhdG8gZWxl dHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAg ZGlnaXRhbGkgKElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJv dmlkZXIgKElkUCkwCAYGBACPegEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0 cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3Bz LnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lk Lmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG9w0B AQ0FAAOCAgEAG9XZeAkIuqSmYb6bq5WrcI2FQtVrfbMH1CXGDKytZUsH5phkGfk/ 8UaIfkbHhnWakM4H9J2gnvfhKorfMt2FHyXFFJ38hlWR8MhFziqthXLUxyLZpUMn h8CcNQyFpNz7xbZk/qN5yFfJyY4Rggm1qdgCNR1LsVI3hjuaORTAzvy4kLjfuU5r nVYPcxpHF7feJKlN03d8JRKYaIi5U+QVYtYJpTcE7jeYmn4Ewfry2BDCOsnljeYl gm3fF8EEVpMfHIhvJg8evATWmKWHpXL2BRtVrl7TfhvtWqKv4tLff+Lv2YqRpmYu oApA48/MB4QxwAPUBnmQb3CxVGs6OCbE/tdUfda9HuHP5MXYLtTVbRYu8pHEPnaN jPA8y90KRw2wiedgjgOG8BxOkhVF/cYs3yH+0hbPS5Oji27t0P2g9eG/p9TOy4AI gUykFimVFk6HV9znknrFSdgsePSp+T5zy45Jdi1z4/RgJN10szJfqEBuvd8MhUu4 meVgfDqXrqavCVzGpSLuicdk41sTOviBz+PEgbQ/qP9KHQv67SHoF4US9Pp9tkyj VFUs7lBnrlFAPpOzd97XdiZfotCA5umibqlxLshy4UK7yl2LZFllpxrfiXTCDASM KlMMIcIsWx0lU/qw5KPpqvXELiya791kohJTi+9pyG7LXIOHHA0whr0= + + + + + + https://www.spid.gov.it/SpidL1 + https://www.spid.gov.it/SpidL2 + + + P + LP + PG + PF + PX + + + + + + + MIIINTCCBh2gAwIBAgIIJz+ujRbSAYwwDQYJKoZIhvcNAQENBQAwgfsxCzAJBgNV BAYTAklUMQ0wCwYDVQQHDARSb21lMSYwJAYDVQQKDB1BZ2VuemlhIHBlciBsJ0l0 YWxpYSBEaWdpdGFsZTEwMC4GA1UECwwnU2Vydml6aW8gQWNjcmVkaXRhbWVudG8g ZSBwcm9nZXR0byBTUElEMTwwOgYDVQQDDDNQcm9nZXR0byBTUElEIC0gR2VzdG9y aSBkaSBJZGVudGl0w6AgRGlnaXRhbGUgKElkUCkxKTAnBgkqhkiG9w0BCQEWGnBy b3RvY29sbG9AcGVjLmFnaWQuZ292Lml0MRowGAYDVQQFExFWQVRJVC05NzczNTAy MDU4NDAeFw0yMjA1MTAwMDAwMDBaFw0zMjA1MDkyMzU5NTlaMIGrMRowGAYDVQRh DBFWQVRJVC0wMTAzNTMxMDQxNDEcMBoGA1UEAwwTc3BpZC50ZWFtc3lzdGVtLmNv bTEaMBgGA1UECgwRVGVhbVN5c3RlbSBTLnAuQS4xKDAmBgNVBFMMH2h0dHBzOi8v c3BpZC50ZWFtc3lzdGVtLmNvbS9pZHAxCzAJBgNVBAYTAklUMQ8wDQYDVQQHDAZQ ZXNhcm8xCzAJBgNVBAgMAlBVMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC AQEAyNJMgyn+iquzTvLR5Z/eYBfOoyJIfI3rYcj5WOSlTzlqXXBCzdcROm/JKgrf 3MOTEzH8RAn6XkSHXtJDtMpD7GlwYB0mo8scqDNtpszbhm/UXapJTrP7gy/UI3yf n99n4hvqkGOdld7w5vaAPS0w9PdcaRxY/7X4olHKBAx2cHAwiqhKuiFEDhfACRWs bw4gaIjVM7NuUtL/jG+PJV1NHrEn10vizE7IneMxDNqiQ14IjLL7pJMEPXwbXedz ZsModKKAXIX5reNSegEU1Y386BCkmg4IMWd+DglmMJ4uuzcga1AppgjDuqb8yFDa NOKy/0Jivh2rs7u9boE4cLVBPQIDAQABo4IDCTCCAwUwCQYDVR0TBAIwADAdBgNV HQ4EFgQU/q5NWlPmylmZTsX0C2MwZkrx3b4wHwYDVR0jBBgwFoAUyF8jl8Jbn9To hwSTF77f5QNJd18wDgYDVR0PAQH/BAQDAgbAMBEGA1UdEQQKMAiCBmlkcC5pdDAW BgNVHRIEDzANggtzcGlkLmdvdi5pdDA/BgNVHR8EODA2MDSgMqAwhi5odHRwczov L2VpZGFzLmFnaWQuZ292Lml0L2NybC9jcmxfU1BJRF9JZFAuY3JsMGoGCCsGAQUF BwEBBF4wXDBEBggrBgEFBQcwAoY4aHR0cDovL2VpZGFzLmFnaWQuZ292Lml0L2Nl cnRpZmljYXRpL1N1Yl9DQV9TUElEX0lkUC5jZXIwFAYIKwYBBQUHMAGGCGh0dHBz Oi8vMIIBzgYDVR0gBIIBxTCCAcEwCQYHBACORgEGAjCBlQYEK0wQBjCBjDBEBggr BgEFBQcCAjA4GjZFbGVjdHJvbmljIGNlcnRpZmljYXRlIGNvbmZvcm1pbmcgd2l0 aCBBR0lEIEd1aWRlbGluZXMwRAYIKwYBBQUHAgIwOBo2Q2VydGlmaWNhdG8gZWxl dHRyb25pY28gY29uZm9ybWUgYWxsZSBMaW5lZSBndWlkYSBBZ0lEMHIGBitMEAQB AjBoMDkGCCsGAQUFBwICMC0aK1NQSUQ6IGdlc3RvcmUgZGVsbGUgaWRlbnRpdOAg ZGlnaXRhbGkgKElkUCkwKwYIKwYBBQUHAgIwHxodU1BJRDogSWRlbnRpdHkgUHJv dmlkZXIgKElkUCkwCAYGBACPegEDME0GBCtMEAQwRTBDBggrBgEFBQcCARY3aHR0 cHM6Ly9laWRhcy5hZ2lkLmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3Bz LnBkZjBPBgYEAI5GAQUwRTBDBggrBgEFBQcCARY3aHR0cHM6Ly9laWRhcy5hZ2lk Lmdvdi5pdC9jcHMvQWdJRF9lSURBU19yb290Q0FfY3BzLnBkZjANBgkqhkiG9w0B AQ0FAAOCAgEAG9XZeAkIuqSmYb6bq5WrcI2FQtVrfbMH1CXGDKytZUsH5phkGfk/ 8UaIfkbHhnWakM4H9J2gnvfhKorfMt2FHyXFFJ38hlWR8MhFziqthXLUxyLZpUMn h8CcNQyFpNz7xbZk/qN5yFfJyY4Rggm1qdgCNR1LsVI3hjuaORTAzvy4kLjfuU5r nVYPcxpHF7feJKlN03d8JRKYaIi5U+QVYtYJpTcE7jeYmn4Ewfry2BDCOsnljeYl gm3fF8EEVpMfHIhvJg8evATWmKWHpXL2BRtVrl7TfhvtWqKv4tLff+Lv2YqRpmYu oApA48/MB4QxwAPUBnmQb3CxVGs6OCbE/tdUfda9HuHP5MXYLtTVbRYu8pHEPnaN jPA8y90KRw2wiedgjgOG8BxOkhVF/cYs3yH+0hbPS5Oji27t0P2g9eG/p9TOy4AI gUykFimVFk6HV9znknrFSdgsePSp+T5zy45Jdi1z4/RgJN10szJfqEBuvd8MhUu4 meVgfDqXrqavCVzGpSLuicdk41sTOviBz+PEgbQ/qP9KHQv67SHoF4US9Pp9tkyj VFUs7lBnrlFAPpOzd97XdiZfotCA5umibqlxLshy4UK7yl2LZFllpxrfiXTCDASM KlMMIcIsWx0lU/qw5KPpqvXELiya791kohJTi+9pyG7LXIOHHA0whr0= + + + + + + urn:oasis:names:tc:SAML:2.0:nameid-format:transient + + + + + + + + + + + + + + + + + + + + + + + + + + + + TeamSystem s.p.a. + TeamSystem s.p.a. + TeamSystem + TeamSystem + https://www.teamsystem.com + https://international.teamsystem.com/ww/ + + + \ No newline at end of file diff --git a/redis-storage/README.md b/redis-storage/README.md new file mode 100644 index 00000000..f89a265c --- /dev/null +++ b/redis-storage/README.md @@ -0,0 +1,103 @@ +# Lollipop SDK Redis Storage + +This module contains implementations of the storage interfaces using a Redis Client with the Lettuce implementation. + +## Configuration + +The following tables contains the properties of the [RedisStorageConfig](redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/config/RedisStorageConfig.java) +configuration class, to be used in order to statup a Redis Client within the module. + +| VARIABLE | DEFAULT VALUE | USAGE | +|-----------------------|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| mainNode | {"hostmane":"localhost","port": 6379} | RedisNode model containing a hostname and port to be used. If not in sentinel mode this is the mandatory main references to a Redis instance, otherwise valid Sentinel node host and port must be provided | +| withAuth | false | Flag to indicate if the connection requires authentication | +| username | username | Username to be used if authentication is required | +| password | password | Password to be used if authentication is required | +| clusterConnection | false | Flag to determine if the client will attempt to work in clustered mode | +| withSsl | false | Flag to determine if the client requires an ssl connection | +| clientName | LettuceClient | Name of the client | +| defaultDelay | 60 | The default eviction delay (in seconds) | +| withConnectionPooling | false | Flag to indicate if the connection with work with a connection pool | +| withSentinel | false | Flag to indicate if the client will attempt to connect in sentinel mode for HA | +| clusterNodeList | - | List of RedisNode data structures (json containing hostname and port), to be used for extra nodes in cluster mode | +| sentinelHostList | - | List of sentinel hosts, to be used if in sentinel mode | +| masterIds | - | Lost of masterIds to be used if in sentinel mode | + + +## Examples + +### Simple + +The following snippet provides a basic sample in order to use the implementation of the storage interfaces + +``` +RedisStorageConfig redisConfig = RedisStorageConfig.builder().mainNode( + RedisStorageConfig.RedisNode + .builder() + .hostname("redisnode1") + .port(6379) + .build()); +DefaultClientBuilder builder = new DefaultRedisClientBuilder(redisConfig); +RedisStorageProvisioner redisStoreProvisioner = new RedisStorageProvisioner(builder.build()); +idpRedisStorage = new RedisIdpCertStorageProvider(redisStoreProvisioner).provideStorage(idpCertStorageConfig); +assertionRedisStorage = new RedisAssertionStorageProvider(redisStoreProvisioner).provideStorage(assertionStorageConfig); + +``` + +In order to execute with connection pooling active, provide a redisConfig as for the following example: + +### Pooling startup + +``` +RedisStorageConfig redisConfig = RedisStorageConfig.builder() + .withConnectionPooling(true) + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .hostname("redisnode1") + .port(6379) + .build() + ); +``` + +### With Sentinel nodes + +In order to execute with sentinel active, provide a redisConfig as for the following example, defining a simple +configuration with a main node, an extra sentinel host, and two master nodes, in connection pooling mode: + +``` +RedisStorageConfig redisConfig = RedisStorageConfig.builder() + .withConnectionPooling(true) + .withSentinel(true) + .sentinelHostList(Arrays.asList("sentinel2")) + .masterIds(Arrays.asList("master1","master2")) + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .hostname("sentinel1") + .port(26379) + .build() + ); +``` + +### Clustered Connections + +In order to execute in clustered mode, one or more nodes must be provided, as follows: +``` +RedisStorageConfig redisConfig = RedisStorageConfig.builder() + .clusterConnection(true) + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .hostname("redisnode1") + .port(6379) + .build() + ) + .clusterNodeList(Arrays.asList( + RedisStorageConfig.RedisNode + .builder() + .hostname("redisnode2") + .port(6379) + .build() + )); +``` \ No newline at end of file diff --git a/redis-storage/build.gradle b/redis-storage/build.gradle new file mode 100644 index 00000000..dafa591d --- /dev/null +++ b/redis-storage/build.gradle @@ -0,0 +1,70 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'java-library' + id("io.freefair.lombok") version "8.0.0" +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } +} + +configurations { + implementation { + attributes { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, 'instrumented-core-jar')) + } + } +} + +abstract class InstrumentedJarsRule implements AttributeCompatibilityRule { + + @Override + void execute(CompatibilityCheckDetails details) { + if (details.consumerValue.name == 'instrumented-core-jar' && details.producerValue.name == 'jar') { + details.compatible() + } + } +} + +dependencies { + attributesSchema { + attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE) { + compatibilityRules.add(InstrumentedJarsRule) + } + } + implementation project(path: ':core') + // Use JUnit Jupiter for testing. + implementation 'io.lettuce:lettuce-core:6.2.3.RELEASE' + implementation 'com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.14.2' + implementation 'org.openapitools:jackson-databind-nullable:0.2.6' + implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.14.2' + // Use JUnit Jupiter for testing. + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' + testImplementation 'org.mockito:mockito-core:5.2.0' + testImplementation 'org.mockito:mockito-junit-jupiter:5.2.0' + testImplementation 'org.assertj:assertj-core:3.24.2' + testImplementation 'com.github.codemonstur:embedded-redis:1.0.0' + implementation 'org.apache.commons:commons-pool2:2.11.1' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/RedisStorage.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/RedisStorage.java new file mode 100644 index 00000000..1b21cfda --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/RedisStorage.java @@ -0,0 +1,51 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis; + +import io.lettuce.core.AbstractRedisClient; +import lombok.Data; + +/** + * Abstract class to be used in order to define a redis storage using a Lettuce implementation of + * the AbstractRedisClient + */ +@Data +public abstract class RedisStorage { + + private AbstractRedisClient redisClient; + + public RedisStorage(AbstractRedisClient redisClient) { + this.redisClient = redisClient; + } + + /** + * Attempts to recover a value from the redis instance, using the provided key + * + * @param key key to be used in order to attempt the retreival of a value from redis + * @return if found the String value obtained from redis, null otherwise + */ + public abstract String get(String key) throws Exception; + + /** + * Saves a value in the redis using the provided key + * + * @param key key to be used when saving the value + * @param value value to be stored in the redis instance + */ + public abstract void save(String key, String value) throws Exception; + + /** + * Saves a value in the redis using the provided key, with a defined TTL + * + * @param key key to be used when saving the value + * @param value value to be stored in the redis instance + * @param delayTime seconds defining the stored data TTL + */ + public abstract void save(String key, String value, Long delayTime) throws Exception; + + /** + * Deletes a value from redis using the provided key + * + * @param key key to be used for content deletion from redis + */ + public abstract void delete(String key) throws Exception; +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/RedisStorageProvisioner.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/RedisStorageProvisioner.java new file mode 100644 index 00000000..0de87c0e --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/RedisStorageProvisioner.java @@ -0,0 +1,26 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis; + +import it.pagopa.tech.lollipop.consumer.storage.redis.builder.ClientBuilder; + +/** + * This class provides a simple way to generate an instance of a RedisStorage, from the provided + * ClientBuilder interface + */ +public class RedisStorageProvisioner { + + private final ClientBuilder clientBuilder; + + public RedisStorageProvisioner(ClientBuilder clientBuilder) { + this.clientBuilder = clientBuilder; + } + + /** + * Provides an instance of the RedisStorage, produced by the defined clientBuilder + * + * @return an instance of the RedisStorage + */ + public RedisStorage getStorage() { + return clientBuilder.createStorage(); + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorage.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorage.java new file mode 100644 index 00000000..9a37d1d4 --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorage.java @@ -0,0 +1,89 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.assertion; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorage; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorage; +import java.util.concurrent.TimeUnit; +import lombok.SneakyThrows; +import org.openapitools.jackson.nullable.JsonNullableModule; + +/** + * Implementation of the {@link AssertionStorage} interface as a redis storage. + * + *

The storage can be configured via the {@link StorageConfig} configuration class. + */ +public class RedisAssertionStorage implements AssertionStorage { + + private final RedisStorage redisStorage; + private final StorageConfig storageConfig; + + private final ObjectMapper objectMapper; + + public RedisAssertionStorage(RedisStorage redisStorage, StorageConfig storageConfig) { + this.redisStorage = redisStorage; + this.storageConfig = storageConfig; + this.objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.registerModule(new JsonNullableModule()); + } + + /** + * Retrieve the SAML Assertion associated with the provided assertionRef if the storage is + * enabled {@link IdpCertStorageConfig}, otherwise no operation is performed. + * + * @param ref the assertion reference to be used as key + * @return the SAML Assertion if found, null if no data is present in the storage or the storage + * is disabled + */ + @SneakyThrows + @Override + public SamlAssertion getAssertion(String ref) { + if (!storageConfig.isAssertionStorageEnabled()) { + return null; + } + + String objString = redisStorage.get(ref); + + return objString == null ? null : objectMapper.readValue(objString, SamlAssertion.class); + } + + /** + * Store the SAML Assertion if the storage is enabled {@link StorageConfig}, otherwise no + * operation is performed. + * + *

Once the SAML Assertion is stored an eviction operation is scheduled with a delay + * configured via {@link AssertionStorage} + * + * @param assertionRef the assertion Ref + * @param samlAssertion SAML Assertion instance + */ + @SneakyThrows + @Override + public void saveAssertion(String assertionRef, SamlAssertion samlAssertion) { + if (!storageConfig.isAssertionStorageEnabled()) { + return; + } + + redisStorage.save( + assertionRef, + objectMapper.writeValueAsString(samlAssertion), + TimeUnit.SECONDS.convert( + storageConfig.getStorageEvictionDelay(), + storageConfig.getStorageEvictionDelayTimeUnit())); + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorageProvider.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorageProvider.java new file mode 100644 index 00000000..1fa6a040 --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorageProvider.java @@ -0,0 +1,31 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.assertion; + +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorage; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorageProvisioner; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Implementation of {@link AssertionStorageProvider} interface. It provides an instance of {@link + * RedisAssertionStorage} + */ +@Data +@AllArgsConstructor +public class RedisAssertionStorageProvider implements AssertionStorageProvider { + + private RedisStorageProvisioner redisStorageProvisioner; + + /** + * {@inheritDoc} + * + * @param storageConfig the storage configuration + * @return an instance of {@link RedisAssertionStorage} + */ + @Override + public AssertionStorage provideStorage(StorageConfig storageConfig) { + return new RedisAssertionStorage(redisStorageProvisioner.getStorage(), storageConfig); + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/builder/ClientBuilder.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/builder/ClientBuilder.java new file mode 100644 index 00000000..212712b5 --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/builder/ClientBuilder.java @@ -0,0 +1,10 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.builder; + +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorage; + +/** Interface abstracting a builder of RedisStorage instances */ +public interface ClientBuilder { + + RedisStorage createStorage(); +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/builder/DefaultRedisClientBuilder.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/builder/DefaultRedisClientBuilder.java new file mode 100644 index 00000000..c89eb8cd --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/builder/DefaultRedisClientBuilder.java @@ -0,0 +1,134 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.builder; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.SocketOptions; +import io.lettuce.core.cluster.ClusterClientOptions; +import io.lettuce.core.cluster.RedisClusterClient; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorage; +import it.pagopa.tech.lollipop.consumer.storage.redis.config.RedisStorageConfig; +import it.pagopa.tech.lollipop.consumer.storage.redis.storage.ClusteredRedisStorage; +import it.pagopa.tech.lollipop.consumer.storage.redis.storage.SimpleRedisStorage; +import java.util.ArrayList; +import java.util.List; + +/** + * An implementation of the {@link ClientBuilder} interface, to be used in order to define instances + * of Lettuce Redis clients + */ +public class DefaultRedisClientBuilder implements ClientBuilder { + + private final RedisStorageConfig redisStorageConfig; + + public DefaultRedisClientBuilder(RedisStorageConfig redisStorageConfig) { + this.redisStorageConfig = redisStorageConfig; + } + + /** + * Creates an instance of {@link RedisStorage} using the generated RedisClient, depending on the + * configured parameter it will be used a Clustered or Simple instance + * + * @return an instance of a RedisStorage + */ + @Override + public RedisStorage createStorage() { + return redisStorageConfig.isClusterConnection() + ? new ClusteredRedisStorage( + createRedisClusterClient(), + redisStorageConfig.getDefaultDelay(), + redisStorageConfig.isWithConnectionPooling()) + : new SimpleRedisStorage( + createRedisClient(), + redisStorageConfig.getDefaultDelay(), + redisStorageConfig.isWithConnectionPooling()); + } + + /** + * Creates a simple Redis Client using the provided configurations + * + * @return a RedisClient + */ + private RedisClient createRedisClient() { + + // Build Redis URI with host and authentication details. + + RedisURI redisURI = + getRedisURI( + redisStorageConfig.getMainNode().getHostname(), + redisStorageConfig.getMainNode().getPort()); + + // Create Lettuce Redis Client + RedisClient client = RedisClient.create(redisURI); + + // Configure the client options. + client.setOptions( + ClientOptions.builder() + .autoReconnect(true) + .socketOptions(SocketOptions.builder().keepAlive(true).build()) + .build()); + + return client; + } + + /** + * Creates a Clustered Redis Client using the provided configurations + * + * @return a RedisClient + */ + private RedisClusterClient createRedisClusterClient() { + + List redisURIList = new ArrayList<>(); + redisURIList.add( + getRedisURI( + redisStorageConfig.getMainNode().getHostname(), + redisStorageConfig.getMainNode().getPort())); + + if (redisStorageConfig.getClusterNodeList() != null) { + for (RedisStorageConfig.RedisNode redisNode : redisStorageConfig.getClusterNodeList()) { + redisURIList.add(getRedisURI(redisNode.getHostname(), redisNode.getPort())); + } + } + + // Create Lettuce Redis Client + RedisClusterClient client = RedisClusterClient.create(redisURIList); + + // Configure the client options. + client.setOptions( + ClusterClientOptions.builder() + .autoReconnect(true) + .socketOptions(SocketOptions.builder().keepAlive(true).build()) + .build()); + + return client; + } + + private RedisURI getRedisURI(String hostname, Integer port) { + RedisURI.Builder builder = + !redisStorageConfig.isWithSentinel() + ? RedisURI.Builder.redis(hostname).withPort(port) + : RedisURI.Builder.sentinel(hostname, port); + + builder.withSsl(redisStorageConfig.isWithSsl()); + + if (redisStorageConfig.getSentinelHostList() != null + && !redisStorageConfig.isClusterConnection()) { + redisStorageConfig.getSentinelHostList().forEach(builder::withSentinel); + } + + if (redisStorageConfig.getMasterIds() != null + && !redisStorageConfig.isClusterConnection()) { + redisStorageConfig.getMasterIds().forEach(builder::withSentinelMasterId); + } + + if (redisStorageConfig.isWithAuth()) { + builder.withAuthentication( + redisStorageConfig.getUsername(), redisStorageConfig.getPassword()); + } + + builder.withClientName(redisStorageConfig.getClientName()); + + return builder.build(); + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/config/RedisStorageConfig.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/config/RedisStorageConfig.java new file mode 100644 index 00000000..b04ab2ec --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/config/RedisStorageConfig.java @@ -0,0 +1,39 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.config; + +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class RedisStorageConfig { + + @Builder.Default private RedisNode mainNode = new RedisNode(); + @Builder.Default private boolean withAuth = false; + @Builder.Default private String username = "username"; + @Builder.Default private String password = "password"; + @Builder.Default private boolean clusterConnection = false; + @Builder.Default private boolean withSsl = false; + @Builder.Default private String clientName = "LettuceClient"; + @Builder.Default private Long defaultDelay = 60L; + @Builder.Default private boolean withConnectionPooling = false; + @Builder.Default private boolean withSentinel = false; + private List clusterNodeList; + private List sentinelHostList; + private List masterIds; + + @NoArgsConstructor + @AllArgsConstructor + @Builder + @Data + public static class RedisNode { + + @Builder.Default private String hostname = "localhost"; + @Builder.Default private Integer port = 6379; + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorage.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorage.java new file mode 100644 index 00000000..f75e4f09 --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorage.java @@ -0,0 +1,89 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.idp; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorage; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorage; +import java.util.concurrent.TimeUnit; +import lombok.SneakyThrows; +import org.openapitools.jackson.nullable.JsonNullableModule; + +/** + * Implementation of the {@link IdpCertStorage} interface as a redis storage. + * + *

The storage can be configured via the {@link IdpCertStorageConfig} configuration class. + */ +public class RedisIdpCertStorage implements IdpCertStorage { + + private final RedisStorage redisStorage; + private final IdpCertStorageConfig storageConfig; + + private ObjectMapper objectMapper; + + public RedisIdpCertStorage( + RedisStorage redisStorage, IdpCertStorageConfig idpCertStorageConfig) { + this.redisStorage = redisStorage; + this.storageConfig = idpCertStorageConfig; + this.objectMapper = new ObjectMapper(); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false); + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING); + objectMapper.enable(DeserializationFeature.READ_ENUMS_USING_TO_STRING); + objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE); + objectMapper.registerModule(new JavaTimeModule()); + objectMapper.registerModule(new JsonNullableModule()); + } + + /** + * Retrieve the idpCertData associated with the provided tag if the storage is enabled {@link + * IdpCertStorageConfig}, otherwise no operation is performed. + * + * @param tag the idpCertData issue instant + * @return the list of cert data if found, null if no cert data are present in the storage or + * the storage is disabled + */ + @SneakyThrows + @Override + public IdpCertData getIdpCertData(String tag) { + if (!storageConfig.isIdpCertDataStorageEnabled()) { + return null; + } + + String objString = redisStorage.get(tag); + + return objString == null ? null : objectMapper.readValue(objString, IdpCertData.class); + } + + /** + * Store the idpCertData if the storage is enabled {@link IdpCertStorageConfig}, otherwise no + * operation is performed. + * + *

Once the idpCertData is stored an eviction operation is scheduled with a delay configured + * via {@link IdpCertStorageConfig} + * + * @param tag the idpCertData issue instant + * @param idpCertData + */ + @SneakyThrows + @Override + public void saveIdpCertData(String tag, IdpCertData idpCertData) { + if (!storageConfig.isIdpCertDataStorageEnabled()) { + return; + } + + redisStorage.save( + tag, + objectMapper.writeValueAsString(idpCertData), + TimeUnit.SECONDS.convert( + storageConfig.getStorageEvictionDelay(), + storageConfig.getStorageEvictionDelayTimeUnit())); + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorageProvider.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorageProvider.java new file mode 100644 index 00000000..b30510ac --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorageProvider.java @@ -0,0 +1,31 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.idp; + +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorage; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorageProvisioner; +import lombok.AllArgsConstructor; +import lombok.Data; + +/** + * Implementation of {@link IdpCertStorageProvider} interface. It provides an instance of {@link + * RedisIdpCertStorage} + */ +@Data +@AllArgsConstructor +public class RedisIdpCertStorageProvider implements IdpCertStorageProvider { + + private RedisStorageProvisioner redisStorageProvisioner; + + /** + * {@inheritDoc} + * + * @param storageConfig the storage configuration + * @return an instance of {@link RedisIdpCertStorage} + */ + @Override + public IdpCertStorage provideStorage(IdpCertStorageConfig storageConfig) { + return new RedisIdpCertStorage(redisStorageProvisioner.getStorage(), storageConfig); + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/ClusteredRedisStorage.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/ClusteredRedisStorage.java new file mode 100644 index 00000000..4eb33221 --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/ClusteredRedisStorage.java @@ -0,0 +1,149 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.storage; + +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.support.ConnectionPoolSupport; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorage; +import lombok.Getter; +import lombok.Setter; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +@Getter +@Setter +/** Implements commands to a Redis Cluster using Lettuce */ +public class ClusteredRedisStorage extends RedisStorage { + + private Long defaultDelayTimeSeconds = 60L; + + private Boolean withConnectionPool = false; + + public ClusteredRedisStorage(RedisClusterClient redisClient) { + super(redisClient); + } + + public ClusteredRedisStorage(RedisClusterClient redisClient, Long defaultDelayTimeSeconds) { + super(redisClient); + this.defaultDelayTimeSeconds = defaultDelayTimeSeconds; + } + + public ClusteredRedisStorage( + RedisClusterClient redisClient, + Long defaultDelayTimeSeconds, + Boolean withConnectionPool) { + super(redisClient); + this.defaultDelayTimeSeconds = defaultDelayTimeSeconds; + this.withConnectionPool = withConnectionPool; + } + + /** + * Attempts to recover a value from the redis instance, using the provided key + * + * @param key key to be used in order to attempt the retreival of a value from redis + * @return if found the String value obtained from redis, null otherwise + */ + @Override + public String get(String key) throws Exception { + RedisClusterClient redisClient = (RedisClusterClient) getRedisClient(); + if (!withConnectionPool) { + try (StatefulRedisClusterConnection statefulConnection = + redisClient.connect()) { + return statefulConnection.sync().get(key); + } + } else { + try (GenericObjectPool> pool = + ConnectionPoolSupport.createGenericObjectPool( + redisClient::connect, new GenericObjectPoolConfig<>()); + StatefulRedisClusterConnection statefulConnection = + pool.borrowObject()) { + return statefulConnection.sync().get(key); + } + } + } + + /** + * Saves the key-value pair in the Redis instance, using a default TTL + * + * @param key key to be used when saving the value + * @param value value to be stored in the redis instance + */ + @Override + public void save(String key, String value) throws Exception { + save(key, value, defaultDelayTimeSeconds); + } + + /** + * Saves asynchronously the key-value pair in the Redis instance, using the provided TTL + * + * @param key key to be used when saving the value + * @param value value to be stored in the redis instance + * @param delayTime seconds defining the stored data TTL + */ + @Override + public void save(String key, String value, Long delayTime) throws Exception { + RedisClusterClient redisClient = (RedisClusterClient) getRedisClient(); + if (!withConnectionPool) { + try (StatefulRedisClusterConnection statefulConnection = + redisClient.connect()) { + executeSetMethod(key, value, delayTime, statefulConnection); + } + } else { + GenericObjectPool> pool = + ConnectionPoolSupport.createGenericObjectPool( + redisClient::connect, new GenericObjectPoolConfig<>()); + StatefulRedisClusterConnection statefulConnection = pool.borrowObject(); + executeSetMethod(key, value, delayTime, statefulConnection); + } + } + + /** + * Deletes asynchronously a value from the Redis instance using the provided key + * + * @param key key to be used for content deletion from redis + */ + @Override + public void delete(String key) throws Exception { + RedisClusterClient redisClient = (RedisClusterClient) getRedisClient(); + if (!withConnectionPool) { + try (StatefulRedisClusterConnection statefulConnection = + redisClient.connect()) { + executeAsyncDel(key, statefulConnection); + } + } else { + GenericObjectPool> pool = + ConnectionPoolSupport.createGenericObjectPool( + redisClient::connect, new GenericObjectPoolConfig<>()); + StatefulRedisClusterConnection statefulConnection = pool.borrowObject(); + executeAsyncDel(key, statefulConnection); + } + } + + private void executeAsyncDel( + String key, StatefulRedisClusterConnection statefulConnection) { + statefulConnection + .async() + .del(key) + .thenAccept( + result -> { + statefulConnection.closeAsync(); + }); + } + + private void executeSetMethod( + String key, + String value, + Long delayTime, + StatefulRedisClusterConnection statefulRedisConnection) { + statefulRedisConnection + .async() + .set(key, String.valueOf(value)) + .thenAccept( + result -> { + if ("OK".equals(result)) { + statefulRedisConnection.async().expire(key, delayTime); + } + statefulRedisConnection.closeAsync(); + }); + } +} diff --git a/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/SimpleRedisStorage.java b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/SimpleRedisStorage.java new file mode 100644 index 00000000..545a619a --- /dev/null +++ b/redis-storage/src/main/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/SimpleRedisStorage.java @@ -0,0 +1,145 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.storage; + +import io.lettuce.core.AbstractRedisClient; +import io.lettuce.core.RedisClient; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.support.ConnectionPoolSupport; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorage; +import lombok.*; +import org.apache.commons.pool2.impl.GenericObjectPool; +import org.apache.commons.pool2.impl.GenericObjectPoolConfig; + +@Getter +@Setter +/** Implements commands to a simple Redis instance using Lettuce */ +public class SimpleRedisStorage extends RedisStorage { + + private Long defaultDelayTimeSeconds = 60L; + private Boolean withConnectionPool = false; + + public SimpleRedisStorage(AbstractRedisClient redisClient) { + super(redisClient); + } + + public SimpleRedisStorage(RedisClient redisClient, Long defaultDelayTimeSeconds) { + super(redisClient); + this.defaultDelayTimeSeconds = defaultDelayTimeSeconds; + } + + public SimpleRedisStorage( + RedisClient redisClient, Long defaultDelayTimeSeconds, Boolean withConnectionPool) { + super(redisClient); + this.defaultDelayTimeSeconds = defaultDelayTimeSeconds; + this.withConnectionPool = withConnectionPool; + } + + /** + * Retrieves from a synchronous connection the value stored in the Redis instance using the + * provided key, closes the connection asynchronously + * + * @param key key to be used in order to attempt the retreival of a value from redis + * @return value stored in the Redis instance + */ + @Override + public String get(String key) throws Exception { + RedisClient redisClient = (RedisClient) getRedisClient(); + if (!withConnectionPool) { + try (StatefulRedisConnection statefulConnection = + redisClient.connect()) { + return statefulConnection.sync().get(key); + } + } else { + try (GenericObjectPool> pool = + ConnectionPoolSupport.createGenericObjectPool( + redisClient::connect, new GenericObjectPoolConfig<>()); + StatefulRedisConnection statefulConnection = + pool.borrowObject()) { + return statefulConnection.sync().get(key); + } + } + } + + /** + * Saves the key-value pair in the Redis instance, using a default TTL + * + * @param key key to be used when saving the value + * @param value value to be stored in the redis instance + */ + @Override + public void save(String key, String value) throws Exception { + save(key, value, defaultDelayTimeSeconds); + } + + /** + * Saves asynchronously the key-value pair in the Redis instance, using the provided TTL + * + * @param key key to be used when saving the value + * @param value value to be stored in the redis instance + * @param delayTime seconds defining the stored data TTL + */ + @Override + public void save(String key, String value, Long delayTime) throws Exception { + + RedisClient redisClient = (RedisClient) getRedisClient(); + if (!withConnectionPool) { + StatefulRedisConnection statefulConnection = redisClient.connect(); + executeSetMethod(key, value, delayTime, statefulConnection); + } else { + GenericObjectPool> pool = + ConnectionPoolSupport.createGenericObjectPool( + redisClient::connect, new GenericObjectPoolConfig<>()); + StatefulRedisConnection statefulConnection = pool.borrowObject(); + executeSetMethod(key, value, delayTime, statefulConnection); + } + } + + private void executeSetMethod( + String key, + String value, + Long delayTime, + StatefulRedisConnection statefulRedisConnection) { + statefulRedisConnection + .async() + .set(key, String.valueOf(value)) + .thenAccept( + result -> { + if ("OK".equals(result)) { + statefulRedisConnection.async().expire(key, delayTime); + } + statefulRedisConnection.closeAsync(); + }); + } + + /** + * Deletes asynchronously a value from the Redis instance using the provided key + * + * @param key key to be used for content deletion from redis + */ + @Override + public void delete(String key) throws Exception { + + RedisClient redisClient = (RedisClient) getRedisClient(); + if (!withConnectionPool) { + StatefulRedisConnection statefulConnection = redisClient.connect(); + executeAsyncDel(key, statefulConnection); + } else { + GenericObjectPool> pool = + ConnectionPoolSupport.createGenericObjectPool( + redisClient::connect, new GenericObjectPoolConfig<>()); + StatefulRedisConnection statefulConnection = pool.borrowObject(); + executeAsyncDel(key, statefulConnection); + } + } + + private void executeAsyncDel( + String key, StatefulRedisConnection statefulConnection) { + statefulConnection + .async() + .del(key) + .thenAccept( + result -> { + statefulConnection.closeAsync(); + }); + } +} diff --git a/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorageIntegrationTest.java b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorageIntegrationTest.java new file mode 100644 index 00000000..7a04b181 --- /dev/null +++ b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/assertion/RedisAssertionStorageIntegrationTest.java @@ -0,0 +1,240 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.assertion; + +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorage; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.model.SamlAssertion; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorageProvisioner; +import it.pagopa.tech.lollipop.consumer.storage.redis.builder.DefaultRedisClientBuilder; +import it.pagopa.tech.lollipop.consumer.storage.redis.config.RedisStorageConfig; +import java.io.IOException; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import lombok.SneakyThrows; +import org.junit.jupiter.api.*; +import redis.embedded.RedisSentinel; +import redis.embedded.RedisServer; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class RedisAssertionStorageIntegrationTest { + + private static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + private RedisServer redisServer; + + private RedisSentinel redisSentinel; + private AssertionStorage redisStorage; + + private StorageConfig storageConfig; + + @BeforeAll + public void setUp() throws IOException { + storageConfig = new StorageConfig(); + storageConfig.setStorageEvictionDelay(1); + storageConfig.setStorageEvictionDelayTimeUnit(TimeUnit.SECONDS); + redisServer = RedisServer.newRedisServer().build(); + redisSentinel = RedisSentinel.newRedisSentinel().build(); + redisServer.start(); + redisSentinel.start(); + redisStorage = + new RedisAssertionStorageProvider( + new RedisStorageProvisioner( + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .port(6379) + .build()) + .build()))) + .provideStorage(storageConfig); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorage() { + SamlAssertion samlAssertion = new SamlAssertion(); + samlAssertion.setAssertionRef("test-key"); + samlAssertion.setAssertionData(VALID_ASSERTION_XML); + redisStorage.saveAssertion("test-key", samlAssertion); + Thread.sleep(150); + SamlAssertion result = redisStorage.getAssertion("test-key"); + Assertions.assertEquals(samlAssertion, result); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorageWithConnectionPooling() { + redisStorage = + new RedisAssertionStorageProvider( + new RedisStorageProvisioner( + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .withConnectionPooling(true) + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .port(6379) + .build()) + .build()))) + .provideStorage(storageConfig); + SamlAssertion samlAssertion = new SamlAssertion(); + samlAssertion.setAssertionRef("test-key"); + samlAssertion.setAssertionData(VALID_ASSERTION_XML); + redisStorage.saveAssertion("test-key", samlAssertion); + Thread.sleep(150); + SamlAssertion result = redisStorage.getAssertion("test-key"); + Assertions.assertEquals(samlAssertion, result); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorageWithSentinelConnectionPooling() { + redisStorage = + new RedisAssertionStorageProvider( + new RedisStorageProvisioner( + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .withSentinel(true) + .withConnectionPooling(true) + .masterIds( + Collections.singletonList( + "mymaster")) + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .port(26379) + .build()) + .build()))) + .provideStorage(storageConfig); + SamlAssertion samlAssertion = new SamlAssertion(); + samlAssertion.setAssertionRef("test-key"); + samlAssertion.setAssertionData(VALID_ASSERTION_XML); + redisStorage.saveAssertion("test-key", samlAssertion); + Thread.sleep(150); + SamlAssertion result = redisStorage.getAssertion("test-key"); + Assertions.assertEquals(samlAssertion, result); + } + + @AfterAll + public void tearDown() throws IOException { + redisServer.stop(); + redisSentinel.stop(); + } +} diff --git a/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorageIntegrationTest.java b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorageIntegrationTest.java new file mode 100644 index 00000000..8c13a064 --- /dev/null +++ b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/idp/RedisIdpCertStorageIntegrationTest.java @@ -0,0 +1,247 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.idp; + +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorage; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import it.pagopa.tech.lollipop.consumer.storage.redis.RedisStorageProvisioner; +import it.pagopa.tech.lollipop.consumer.storage.redis.builder.DefaultRedisClientBuilder; +import it.pagopa.tech.lollipop.consumer.storage.redis.config.RedisStorageConfig; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import lombok.SneakyThrows; +import org.junit.jupiter.api.*; +import redis.embedded.RedisSentinel; +import redis.embedded.RedisServer; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class RedisIdpCertStorageIntegrationTest { + + private static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + private RedisServer redisServer; + + private RedisSentinel redisSentinel; + private IdpCertStorage redisStorage; + + private IdpCertStorageConfig idpCertStorageConfig; + + @BeforeAll + public void setUp() throws IOException { + idpCertStorageConfig = new IdpCertStorageConfig(); + idpCertStorageConfig.setStorageEvictionDelay(1); + idpCertStorageConfig.setStorageEvictionDelayTimeUnit(TimeUnit.SECONDS); + redisServer = RedisServer.newRedisServer().build(); + redisSentinel = RedisSentinel.newRedisSentinel().build(); + redisServer.start(); + redisSentinel.start(); + redisStorage = + new RedisIdpCertStorageProvider( + new RedisStorageProvisioner( + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .port(6379) + .build()) + .build()))) + .provideStorage(idpCertStorageConfig); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorage() { + IdpCertData idpCertDataToSave = new IdpCertData(); + idpCertDataToSave.setTag("aTag"); + idpCertDataToSave.setEntityId("anEntityId"); + idpCertDataToSave.setCertData( + new ArrayList<>(Collections.singletonList(VALID_ASSERTION_XML))); + redisStorage.saveIdpCertData("test-key", idpCertDataToSave); + Thread.sleep(150); + IdpCertData result = redisStorage.getIdpCertData("test-key"); + Assertions.assertEquals(idpCertDataToSave, result); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorageWithConnectionPooling() { + redisStorage = + new RedisIdpCertStorageProvider( + new RedisStorageProvisioner( + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .withConnectionPooling(true) + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .port(6379) + .build()) + .build()))) + .provideStorage(idpCertStorageConfig); + IdpCertData idpCertDataToSave = new IdpCertData(); + idpCertDataToSave.setTag("aTag"); + idpCertDataToSave.setEntityId("anEntityId"); + idpCertDataToSave.setCertData( + new ArrayList<>(Collections.singletonList(VALID_ASSERTION_XML))); + redisStorage.saveIdpCertData("test-key", idpCertDataToSave); + Thread.sleep(150); + IdpCertData result = redisStorage.getIdpCertData("test-key"); + Assertions.assertEquals(idpCertDataToSave, result); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorageWithSentinelConnectionPooling() { + redisStorage = + new RedisIdpCertStorageProvider( + new RedisStorageProvisioner( + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .withSentinel(true) + .withConnectionPooling(true) + .masterIds( + Collections.singletonList( + "mymaster")) + .mainNode( + RedisStorageConfig.RedisNode + .builder() + .port(26379) + .build()) + .build()))) + .provideStorage(idpCertStorageConfig); + IdpCertData idpCertDataToSave = new IdpCertData(); + idpCertDataToSave.setTag("aTag"); + idpCertDataToSave.setEntityId("anEntityId"); + idpCertDataToSave.setCertData( + new ArrayList<>(Collections.singletonList(VALID_ASSERTION_XML))); + redisStorage.saveIdpCertData("test-key", idpCertDataToSave); + Thread.sleep(150); + IdpCertData result = redisStorage.getIdpCertData("test-key"); + Assertions.assertEquals(idpCertDataToSave, result); + } + + @AfterAll + public void tearDown() throws IOException { + redisServer.stop(); + redisSentinel.stop(); + } +} diff --git a/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/ClusteredRedisStorageUnitTest.java b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/ClusteredRedisStorageUnitTest.java new file mode 100644 index 00000000..f243e61c --- /dev/null +++ b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/ClusteredRedisStorageUnitTest.java @@ -0,0 +1,89 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.storage; + +import static org.assertj.core.api.Assertions.assertThatNoException; + +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.async.RedisAdvancedClusterAsyncCommands; +import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; +import io.lettuce.core.protocol.AsyncCommand; +import it.pagopa.tech.lollipop.consumer.storage.redis.builder.DefaultRedisClientBuilder; +import it.pagopa.tech.lollipop.consumer.storage.redis.config.RedisStorageConfig; +import java.io.IOException; +import java.util.ArrayList; +import lombok.SneakyThrows; +import org.junit.jupiter.api.*; +import org.mockito.Mockito; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class ClusteredRedisStorageUnitTest { + + private ClusteredRedisStorage redisStorage; + + RedisClusterClient redisClusterClient; + + StatefulRedisClusterConnection redisClusterConnection; + + RedisAdvancedClusterAsyncCommands redisAdvancedClusterAsyncCommands; + + RedisAdvancedClusterCommands redisAdvancedClusterCommands; + + @BeforeAll + public void setUp() throws IOException { + + redisStorage = + (ClusteredRedisStorage) + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .clusterConnection(true) + .mainNode( + RedisStorageConfig.RedisNode.builder() + .port(6669) + .build()) + .clusterNodeList(new ArrayList<>()) + .build()) + .createStorage(); + redisClusterClient = Mockito.mock(RedisClusterClient.class); + redisClusterConnection = Mockito.mock(StatefulRedisClusterConnection.class); + redisAdvancedClusterAsyncCommands = Mockito.mock(RedisAdvancedClusterAsyncCommands.class); + redisAdvancedClusterCommands = Mockito.mock(RedisAdvancedClusterCommands.class); + + Mockito.when(redisClusterClient.connect()).thenReturn(redisClusterConnection); + Mockito.when(redisClusterConnection.async()).thenReturn(redisAdvancedClusterAsyncCommands); + Mockito.when(redisClusterConnection.sync()).thenReturn(redisAdvancedClusterCommands); + + Mockito.when(redisAdvancedClusterAsyncCommands.set(Mockito.any(), Mockito.any())) + .thenReturn(Mockito.mock(AsyncCommand.class)); + Mockito.when(redisAdvancedClusterAsyncCommands.del(Mockito.any())) + .thenReturn(Mockito.mock(AsyncCommand.class)); + Mockito.when(redisAdvancedClusterCommands.get(Mockito.any())).thenReturn("test"); + + redisStorage.setRedisClient(redisClusterClient); + } + + @SneakyThrows + @Test + public void testThatCallsSave() { + + assertThatNoException().isThrownBy(() -> redisStorage.save("test-key", "test")); + } + + @SneakyThrows + @Test + public void testThatCallsGet() { + + assertThatNoException().isThrownBy(() -> redisStorage.get("test-key")); + } + + @SneakyThrows + @Test + public void testThatCallsDelete() { + assertThatNoException().isThrownBy(() -> redisStorage.delete("test-key")); + } + + @AfterAll + public void wrapDown() throws IOException { + redisStorage.getRedisClient().close(); + } +} diff --git a/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/SimpleRedisStorageTest.java b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/SimpleRedisStorageTest.java new file mode 100644 index 00000000..b2377793 --- /dev/null +++ b/redis-storage/src/test/java/it/pagopa/tech/lollipop/consumer/storage/redis/storage/SimpleRedisStorageTest.java @@ -0,0 +1,139 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.storage.redis.storage; + +import it.pagopa.tech.lollipop.consumer.storage.redis.builder.DefaultRedisClientBuilder; +import it.pagopa.tech.lollipop.consumer.storage.redis.config.RedisStorageConfig; +import java.io.IOException; +import java.util.Collections; +import lombok.SneakyThrows; +import org.junit.jupiter.api.*; +import redis.embedded.RedisSentinel; +import redis.embedded.RedisServer; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class SimpleRedisStorageTest { + + private RedisServer redisServer; + private RedisSentinel redisSentinel; + private SimpleRedisStorage redisStorage; + + private SimpleRedisStorage sentinelRedisStoragere; + + @BeforeAll + public void setUp() throws IOException { + + redisServer = RedisServer.newRedisServer().build(); + redisSentinel = RedisSentinel.newRedisSentinel().build(); + redisServer.start(); + redisSentinel.start(); + redisStorage = + (SimpleRedisStorage) + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .mainNode( + RedisStorageConfig.RedisNode.builder() + .port(6379) + .build()) + .build()) + .createStorage(); + sentinelRedisStoragere = + (SimpleRedisStorage) + new DefaultRedisClientBuilder( + RedisStorageConfig.builder() + .withSentinel(true) + .masterIds(Collections.singletonList("mymaster")) + .mainNode( + RedisStorageConfig.RedisNode.builder() + .port(26379) + .build()) + .build()) + .createStorage(); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorage() { + redisStorage.save("test-key", "test"); + Thread.sleep(30); + String result = redisStorage.get("test-key"); + Assertions.assertEquals("test", result); + redisStorage.delete("test-key"); + Thread.sleep(30); + result = redisStorage.get("test-key"); + Assertions.assertNull(result); + } + + @SneakyThrows + @Test + public void successfulEvictOps() { + redisStorage.save("test-key", "test", 1L); + Thread.sleep(100); + String result = redisStorage.get("test-key"); + Assertions.assertEquals("test", result); + Thread.sleep(1100); + result = redisStorage.get("test-key"); + Assertions.assertNull(result); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorageWithConnectionPool() { + redisStorage.setWithConnectionPool(true); + redisStorage.save("test-key", "test"); + Thread.sleep(100); + String result = redisStorage.get("test-key"); + Assertions.assertEquals("test", result); + redisStorage.delete("test-key"); + Thread.sleep(30); + result = redisStorage.get("test-key"); + Assertions.assertNull(result); + } + + @SneakyThrows + @Test + public void successfulEvictOpsWithConnectionPool() { + redisStorage.setWithConnectionPool(true); + redisStorage.save("test-key", "test", 1L); + Thread.sleep(100); + String result = redisStorage.get("test-key"); + Assertions.assertEquals("test", result); + Thread.sleep(1100); + result = redisStorage.get("test-key"); + Assertions.assertNull(result); + } + + @SneakyThrows + @Test + public void successfulOperationsOnRedisStorageWithConnectionPoolAndSentinel() { + sentinelRedisStoragere.setWithConnectionPool(true); + sentinelRedisStoragere.save("test-key", "test"); + Thread.sleep(100); + String result = sentinelRedisStoragere.get("test-key"); + Assertions.assertEquals("test", result); + sentinelRedisStoragere.delete("test-key"); + Thread.sleep(30); + result = sentinelRedisStoragere.get("test-key"); + Assertions.assertNull(result); + } + + @SneakyThrows + @Test + public void successfulEvictOpsWithConnectionPoolAndSentinel() { + sentinelRedisStoragere.setWithConnectionPool(true); + sentinelRedisStoragere.save("test-key", "test", 1L); + Thread.sleep(100); + String result = sentinelRedisStoragere.get("test-key"); + Assertions.assertEquals("test", result); + Thread.sleep(1200); + result = sentinelRedisStoragere.get("test-key"); + Assertions.assertNull(result); + } + + @AfterAll + public void tearDown() throws IOException { + redisStorage.getRedisClient().close(); + sentinelRedisStoragere.getRedisClient().close(); + redisServer.stop(); + redisSentinel.stop(); + } +} diff --git a/samples/servlet/build.gradle b/samples/servlet/build.gradle new file mode 100644 index 00000000..dc35cfdb --- /dev/null +++ b/samples/servlet/build.gradle @@ -0,0 +1,59 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'application' +} + + +group = 'it.pagopa.tech.lollipop.consumer.samples' +version = '1.0.0-RC1' +sourceCompatibility = '11' + +application { + mainClassName = "it.pagopa.tech.lollipop.consumer.sample.LollipopConsumerServletSample" +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/pagopa/eng-lollipop-consumer-java-sdk" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } +} + +dependencies { + implementation 'org.apache.tomcat.embed:tomcat-embed-core:9.0.74' + + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:core:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:assertion-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:identity-service-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:http-verifier:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:servlet-impl:1.0.0-RC1' + // Use JUnit Jupiter for testing. + implementation 'org.mock-server:mockserver-netty:5.15.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/samples/servlet/settings.gradle b/samples/servlet/settings.gradle new file mode 100644 index 00000000..9f32e8b7 --- /dev/null +++ b/samples/servlet/settings.gradle @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + + +rootProject.name = 'servlet-lollipop-sample' \ No newline at end of file diff --git a/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/LollipopConsumerServletSample.java b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/LollipopConsumerServletSample.java new file mode 100644 index 00000000..6a06041c --- /dev/null +++ b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/LollipopConsumerServletSample.java @@ -0,0 +1,100 @@ +package it.pagopa.tech.lollipop.comsumer.sample; + +import it.pagopa.tech.lollipop.comsumer.sample.mock.AssertionMockServerConfig; +import it.pagopa.tech.lollipop.comsumer.sample.mock.IdpMockServerConfig; +import it.pagopa.tech.lollipop.comsumer.sample.servlet.DemoServlet; +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.impl.AssertionServiceFactoryImpl; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.SimpleAssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandBuilderImpl; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.exception.LollipopVerifierException; +import it.pagopa.tech.lollipop.consumer.helper.LollipopConsumerFactoryHelper; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.http_verifier.visma.VismaHttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.storage.SimpleIdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.idp.impl.IdpCertProviderFactoryImpl; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.service.impl.LollipopConsumerRequestValidationServiceImpl; +import it.pagopa.tech.lollipop.consumer.servlet.HttpVerifierServletFilter; +import org.apache.catalina.Context; +import org.apache.catalina.startup.Tomcat; +import org.apache.tomcat.util.descriptor.web.FilterDef; +import org.apache.tomcat.util.descriptor.web.FilterMap; +import org.mockserver.integration.ClientAndServer; + +import java.io.File; + +public class LollipopConsumerServletSample { + + private static final String LOLLIPOP_CONSUMER_PATH = "/api/v1/lollipop-consumer"; + private static final String SERVLET_NAME = "DemoServlet"; + + public static void main(String[] args) throws Exception { + + Tomcat tomcat = new Tomcat(); + tomcat.setPort(8080); + tomcat.getConnector(); + String contextPath = ""; + String docBase = new File(".").getAbsolutePath(); + + Context context = tomcat.addContext(contextPath, docBase); + + FilterDef filterDef = buildFilterDef(); + context.addFilterDef(filterDef); + context.addFilterMap(buildFilterMap(filterDef)); + + Tomcat.addServlet(context, SERVLET_NAME, new DemoServlet()); + context.addServletMappingDecoded(LOLLIPOP_CONSUMER_PATH, SERVLET_NAME); + + tomcat.start(); + ClientAndServer.startClientAndServer(3001, 3000); + AssertionMockServerConfig.startAssertionMockServer(); + IdpMockServerConfig.startIdpMockServer(); + tomcat.getServer().await(); + } + + private static FilterMap buildFilterMap(FilterDef filterDef) { + FilterMap filterMap = new FilterMap(); + filterMap.setFilterName(filterDef.getFilterName()); + filterMap.addURLPattern(LOLLIPOP_CONSUMER_PATH); + return filterMap; + } + + private static FilterDef buildFilterDef() throws LollipopVerifierException { + FilterDef filterDef = new FilterDef(); + filterDef.setFilter(new HttpVerifierServletFilter(new LollipopConsumerCommandBuilderImpl(buildLollipopConsumerFactoryHelper()))); + filterDef.setFilterName("HttpVerifierServletFilter"); + return filterDef; + } + + private static LollipopConsumerFactoryHelper buildLollipopConsumerFactoryHelper() throws LollipopVerifierException { + LollipopConsumerRequestConfig lollipopConsumerRequestConfig = LollipopConsumerRequestConfig.builder() + .assertionNotBeforeDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + .assertionInstantDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + .build(); + HttpMessageVerifierFactory messageVerifierFactory = new VismaHttpMessageVerifierFactory("UTF-8", + LollipopConsumerRequestConfig.builder().build()); + AssertionStorageProvider assertionStorageProvider = new SimpleAssertionStorageProvider(); + IdpCertProviderFactory idpCertProviderFactory = new IdpCertProviderFactoryImpl( + new IdpCertSimpleClientProvider(IdpCertSimpleClientConfig.builder().baseUri("http://localhost:3001").build(), + new SimpleIdpCertStorageProvider(), new IdpCertStorageConfig())); + AssertionClientProvider assertionClientProvider = + new AssertionSimpleClientProvider(AssertionSimpleClientConfig.builder().build()); + AssertionServiceFactory assertionServiceFactory = new AssertionServiceFactoryImpl( + assertionStorageProvider, assertionClientProvider, new StorageConfig()); + LollipopLoggerServiceFactory lollipopLoggerServiceFactory = new LollipopLogbackLoggerServiceFactory(); + return new LollipopConsumerFactoryHelper(lollipopLoggerServiceFactory, messageVerifierFactory, idpCertProviderFactory, assertionServiceFactory, + new LollipopConsumerRequestValidationServiceImpl(lollipopConsumerRequestConfig), lollipopConsumerRequestConfig); + } +} diff --git a/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/mock/AssertionMockServerConfig.java b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/mock/AssertionMockServerConfig.java new file mode 100644 index 00000000..9aa3590f --- /dev/null +++ b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/mock/AssertionMockServerConfig.java @@ -0,0 +1,146 @@ +package it.pagopa.tech.lollipop.comsumer.sample.mock; + +import org.mockserver.client.MockServerClient; +import org.mockserver.model.Header; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +public class AssertionMockServerConfig { + + private static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + private static final String ASSERTION_REF = "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw"; + private static final String JWT = "aValidJWT"; + + public static MockServerClient startAssertionMockServer() { + MockServerClient mockServerClient = new MockServerClient("localhost", 3000); + mockServerClient + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(200).withBody(VALID_ASSERTION_XML)); + return mockServerClient; + } +} diff --git a/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/mock/IdpMockServerConfig.java b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/mock/IdpMockServerConfig.java new file mode 100644 index 00000000..285706db --- /dev/null +++ b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/mock/IdpMockServerConfig.java @@ -0,0 +1,131 @@ +package it.pagopa.tech.lollipop.comsumer.sample.mock; + +import org.mockserver.client.MockServerClient; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +public class IdpMockServerConfig { + + private static final String IDP_CLIENT_RESPONSE_STRING = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + private static final String IDP_TAG = "latest"; + + public static MockServerClient startIdpMockServer() { + MockServerClient mockServerClient = new MockServerClient("localhost", 3001); + mockServerClient + .when(request().withMethod("GET").withPath("/idp-keys/spid")) + .respond(response().withStatusCode(200).withBody("[\"" + IDP_TAG + "\"]")); + mockServerClient + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", IDP_TAG)) + .respond(response().withStatusCode(200).withBody(IDP_CLIENT_RESPONSE_STRING)); + return mockServerClient; + } +} diff --git a/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/servlet/DemoServlet.java b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/servlet/DemoServlet.java new file mode 100644 index 00000000..f9a61750 --- /dev/null +++ b/samples/servlet/src/main/java/it/pagopa/tech/lollipop/comsumer/sample/servlet/DemoServlet.java @@ -0,0 +1,19 @@ +package it.pagopa.tech.lollipop.comsumer.sample.servlet; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; + +public class DemoServlet extends HttpServlet { + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + response.setContentType("text/html"); + PrintWriter out = response.getWriter(); + out.println("Test"); + } +} diff --git a/samples/simple/build.gradle b/samples/simple/build.gradle new file mode 100644 index 00000000..70ff860a --- /dev/null +++ b/samples/simple/build.gradle @@ -0,0 +1,56 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'application' +} + + +group = 'it.pagopa.tech.lollipop.consumer.samples' +version = '1.0.0-RC1' +sourceCompatibility = '11' + +application { + mainClassName = "it.pagopa.tech.sample.LollipopConsumerSample" +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/pagopa/eng-lollipop-consumer-java-sdk" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } +} + +dependencies { + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:core:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:assertion-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:identity-service-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:http-verifier:1.0.0-RC1' + // Use JUnit Jupiter for testing. + implementation 'org.mock-server:mockserver-netty:5.15.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/samples/simple/gradlew b/samples/simple/gradlew new file mode 100644 index 00000000..4f906e0c --- /dev/null +++ b/samples/simple/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/samples/simple/gradlew.bat b/samples/simple/gradlew.bat new file mode 100644 index 00000000..107acd32 --- /dev/null +++ b/samples/simple/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/simple/settings.gradle b/samples/simple/settings.gradle new file mode 100644 index 00000000..bf66c794 --- /dev/null +++ b/samples/simple/settings.gradle @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + + +rootProject.name = 'simple-lollipop-sample' \ No newline at end of file diff --git a/samples/simple/src/main/java/it/pagopa/tech/sample/ClientMocksConfig.java b/samples/simple/src/main/java/it/pagopa/tech/sample/ClientMocksConfig.java new file mode 100644 index 00000000..f6ad64f5 --- /dev/null +++ b/samples/simple/src/main/java/it/pagopa/tech/sample/ClientMocksConfig.java @@ -0,0 +1,274 @@ +package it.pagopa.tech.sample; + +import org.mockserver.client.MockServerClient; +import org.mockserver.model.Header; + +import static it.pagopa.tech.sample.LollipopConstants.*; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +public class ClientMocksConfig { + + public static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + public static final String IDP_CLIENT_RESPONSE_STRING = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + private static final String LOCALHOST = "localhost"; + + public static MockServerClient createExpectationAssertionFound() { + MockServerClient mockServerClient = new MockServerClient(LOCALHOST, 3000); + mockServerClient + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", VALID_ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", VALID_JWT))) + .respond(response().withStatusCode(200).withBody(VALID_ASSERTION_XML)); + return mockServerClient; + } + + public static MockServerClient createExpectationIdpTagsFound() { + MockServerClient mockServerClient = new MockServerClient(LOCALHOST, 3001); + mockServerClient + .when(request().withMethod("GET").withPath("/idp-keys/spid")) + .respond(response().withStatusCode(200).withBody("[\"" + IDP_TAG + "\"]")); + return mockServerClient; + } + + public static MockServerClient createExpectationIdpDataFound() { + MockServerClient mockServerClient = new MockServerClient(LOCALHOST, 3001); + mockServerClient + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", IDP_TAG)) + .respond(response().withStatusCode(200).withBody(IDP_CLIENT_RESPONSE_STRING)); + return mockServerClient; + } + + private ClientMocksConfig() {} +} diff --git a/samples/simple/src/main/java/it/pagopa/tech/sample/LollipopConstants.java b/samples/simple/src/main/java/it/pagopa/tech/sample/LollipopConstants.java new file mode 100644 index 00000000..79e11c95 --- /dev/null +++ b/samples/simple/src/main/java/it/pagopa/tech/sample/LollipopConstants.java @@ -0,0 +1,52 @@ +package it.pagopa.tech.sample; + +public class LollipopConstants { + public static final String VALID_ENCODING_UTF8 = "UTF-8"; + public static final String INVALID_ENCODING_UTF_326 = "UTF-326"; + public static final String VALID_CONTENT_DIGEST = "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:"; + public static final String INVALID_CONTENT_DIGEST = "sha-256=:fadsfeagsdage76ad564=:"; + public static final String VALID_MESSAGE_PAYLOAD = "{\"message\":\"a valid message payload\"}"; + public static final String INVALID_MESSAGE_PAYLOAD = "{\"message\":\"an invalid message payload\"}"; + public static final String SIGNATURE_HEADER_VALUE = "sig123=:6scl8sMzJdyG/OrnJXHRM9ajmIjrJ/zrLUDqvfOxj2h51DUKztTua3vR1kSUj/c/VT1ioDlt1QIMARABhquewg==:"; + public static final String INVALID_SIGNATURE_HEADER_VALUE = "sig123=:lTTTRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:"; + public static final String SIGNATURE_INPUT_HEADER_VALUE = "sig123=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + + public static final String ECDSA_LOLLIPOP_JWT_KEY = "eyJrdHkiOiJFQyIsIngiOiJTaHlZa0ZyN1F3eE9rOE5BRXF6aklkTnc4dEVKODlZOVBlWFF1eVVOWDVjIiwieSI6InlULVJxNWc2VlVadENUd0ZnRExDM2RneGNuM2RsSmNGRjhnWGdxYWgyS0UiLCJjcnYiOiJQLTI1NiJ9"; + + public static final String VALID_MULTI_ECDSA_SIGNATURE_INPUT = + "sig1=(\"x-io-sign-qtspclauses\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\"," + + " sig2=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + public static final String VALID_MULTI_ECDSA_SIGNATURE = + "sig1=:8MB/iT9iZO2HfVjMds6WdFMQeutkPnoyBDhzeyvIQDhb/tX0nE6HeRSoRBsrl4GUzo6OItnzfzF43Sd14P7tAw==:," + + "sig2=:ZDWu2x+6APQG0Ioj10uNzTBv+5JbFBYnjhqcpL66oGFtwznROAUouXkx80ekzUY5h0HoJWE/ecqxRK2OVeHTiQ==:"; + + public static final String VALID_RSA_PSS_SIGNATURE_INPUT = + "sig1=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678814391;nonce=\"aNonce\";" + + "alg=\"rsa-pss-sha256\";keyid=\"sha256-A3OhKGLYwSvdJ2txHi_SGQ3G-sHLh2Ibu91ErqFx_58\""; + + public static final String VALID_RSA_PSS_SIGNATURE = + "sig1=:q3Og7m8yL18HkrY+zgV92Gj05lrWaFMIEFSPg2PEnO5a46+2Tt/2n7kjqVaGjI1ZXtys+Wyh3cVXCdda" + + "dNARizt0BpCRdT9S4r48xsGO79Ucq4IFwZyyHNudKu5WSH4/55j5yX/YmeCtH+Nt6Nun02OZynn3iQwg" + + "LJB+CGe3h6X02iSvl4wJjKaMGE64RFHa5osE4MctoPD1j0tRkcOtgwrGmFMr282Kqrkabbx1vUpmO9T1k" + + "hjouxIryfUln9zIaZ+wWmukpAZv7TKO3CltNWgfx1XT9m/iwzHiGmtvcHbWVExdAyey8lH23MgLY43AM7y" + + "tLQNSlk1s/bPNbGmPwg==:"; + + public static String LOLLIPOP_RSA_PUBKEY = "eyJrdHkiOiJSU0EiLCJlIjoiQVFBQiIsImtpZCI6InRlc3Qta2V5LXJzYS1wc3MiLCJuIjoicjR0bW0zc" + + "jIwV2RfUGJxdlAxczItUUV0dnB1UmFWOFlxNDBnalVSOHkyUmp4YTZkcEcyR1hIYlBmdk0gIHM4Y3Q" + + "tTGgxR0g0NXgyOFJ3M1J5NTNtbS1vQVhqeVE4Nk9uRGtaNU44bFliZ2dENE8zdzZNNnBBdkxraGs5NU" + + "FuICBkVHJpZmJJRlBOVThQUE1PN095ckZBSHFnRHN6bmpQRm1UT3RDRWNOMloxRnBXZ2Nod3VZTFBMLV" + + "dva3FsdGQxMSAgbnFxemktYko5Y3ZTS0FEWWRVQUFONVdVdHpkcGl5NkxiVGdTeFA3b2NpVTRUbjBnNU" + + "k2YURaSjdBOEx6bzBLU3kgIFpZb0E0ODVtcWNPMEdWQWRWdzlscTRhT1Q5djZkLW5iNGJuTmtRVmtsTFE" + + "zZlZBdkptLXhkRE9wOUxDTkNONDhWICAycG5ET2tGVjYtVTluVjVveWM2WEkydyJ9"; + + public static final String VALID_ASSERTION_REF = "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw"; + public static final String VALID_FISCAL_CODE = "GDNNWA12H81Y874F"; + public static final String VALID_JWT = "aValidJWT"; + + public static final String IDP_TAG = "latest"; + + private LollipopConstants() {} +} diff --git a/samples/simple/src/main/java/it/pagopa/tech/sample/LollipopConsumerSample.java b/samples/simple/src/main/java/it/pagopa/tech/sample/LollipopConsumerSample.java new file mode 100644 index 00000000..3cb43e5f --- /dev/null +++ b/samples/simple/src/main/java/it/pagopa/tech/sample/LollipopConsumerSample.java @@ -0,0 +1,134 @@ +package it.pagopa.tech.sample; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.impl.AssertionServiceFactoryImpl; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.SimpleAssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandBuilderImpl; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.helper.LollipopConsumerFactoryHelper; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.http_verifier.visma.VismaHttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.storage.SimpleIdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.idp.impl.IdpCertProviderFactoryImpl; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.service.impl.LollipopConsumerRequestValidationServiceImpl; +import org.mockserver.client.MockServerClient; +import org.mockserver.integration.ClientAndServer; + +import java.util.HashMap; + +import static it.pagopa.tech.sample.LollipopConstants.*; + + +public class LollipopConsumerSample { + + private static LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + + public static void main(String[] args) throws Exception { + try (ClientAndServer clientAndServer = ClientAndServer.startClientAndServer(3000, 3001); + MockServerClient mockServerClientAssertion = ClientMocksConfig.createExpectationAssertionFound(); + MockServerClient mockServerClientIdpTag = ClientMocksConfig.createExpectationIdpTagsFound(); + MockServerClient mockServerClientIdpData = ClientMocksConfig.createExpectationIdpDataFound()) { + + LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper = buildLollipopConsumerFactoryHelper(); + LollipopConsumerCommandBuilder commandBuilder = new LollipopConsumerCommandBuilderImpl(lollipopConsumerFactoryHelper); + + // Success + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)) + .doExecute(); + + // Request with invalid content digest + commandBuilder.createCommand(buildLollipopRequest( + INVALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)).doExecute(); + + // Request with invalid message payload + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, INVALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)).doExecute(); + + // Request with invalid encoding + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, INVALID_ENCODING_UTF_326, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)).doExecute(); + + // Request with invalid signature + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, INVALID_SIGNATURE_HEADER_VALUE)).doExecute(); + + // Success with multi signature + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + VALID_MULTI_ECDSA_SIGNATURE_INPUT, VALID_MULTI_ECDSA_SIGNATURE)).doExecute(); + + // thumbprint validation failed + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, + LOLLIPOP_RSA_PUBKEY, VALID_RSA_PSS_SIGNATURE_INPUT, VALID_RSA_PSS_SIGNATURE)).doExecute(); + + } + } + + private static LollipopConsumerFactoryHelper buildLollipopConsumerFactoryHelper() throws Exception { + lollipopConsumerRequestConfig = LollipopConsumerRequestConfig.builder() + .assertionNotBeforeDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + .assertionInstantDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'") + .build(); + HttpMessageVerifierFactory messageVerifierFactory = new VismaHttpMessageVerifierFactory(VALID_ENCODING_UTF8, + LollipopConsumerRequestConfig.builder().build()); + AssertionStorageProvider assertionStorageProvider = new SimpleAssertionStorageProvider(); + IdpCertProviderFactory idpCertProviderFactory = new IdpCertProviderFactoryImpl( + new IdpCertSimpleClientProvider(IdpCertSimpleClientConfig.builder().baseUri("http://localhost:3001").build(), + new SimpleIdpCertStorageProvider(), new IdpCertStorageConfig())); + AssertionClientProvider assertionClientProvider = + new AssertionSimpleClientProvider(AssertionSimpleClientConfig.builder().build()); + AssertionServiceFactory assertionServiceFactory = new AssertionServiceFactoryImpl( + assertionStorageProvider, assertionClientProvider, new StorageConfig()); + LollipopLoggerServiceFactory lollipopLoggerServiceFactory = new LollipopLogbackLoggerServiceFactory(); + return new LollipopConsumerFactoryHelper(lollipopLoggerServiceFactory, messageVerifierFactory, idpCertProviderFactory, assertionServiceFactory, + new LollipopConsumerRequestValidationServiceImpl(lollipopConsumerRequestConfig), lollipopConsumerRequestConfig); + } + + private static LollipopConsumerRequest buildLollipopRequest( + String contentDigest, + String encoding, + String payload, + String lollipopKey, + String signatureInput, + String signature) { + HashMap lollipopHeaderParams = new HashMap<>(); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getContentDigestHeader(), contentDigest); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getContentEncodingHeader(), encoding); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getSignatureInputHeader(), signatureInput); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getSignatureHeader(), signature); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getPublicKeyHeader(), lollipopKey); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getOriginalMethodHeader(), lollipopConsumerRequestConfig.getExpectedFirstLcOriginalMethod()); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getOriginalURLHeader(), lollipopConsumerRequestConfig.getExpectedFirstLcOriginalUrl()); + lollipopHeaderParams.put("X-io-sign-qtspclauses","anIoSignClauses"); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getAssertionRefHeader(), VALID_ASSERTION_REF); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getAssertionTypeHeader(), "SAML"); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getAuthJWTHeader(), VALID_JWT); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getUserIdHeader(), VALID_FISCAL_CODE); + + + return LollipopConsumerRequest.builder() + .requestBody(payload) + .headerParams(lollipopHeaderParams) + .build(); + } +} diff --git a/samples/simpleTypesafe/build.gradle b/samples/simpleTypesafe/build.gradle new file mode 100644 index 00000000..8ffcd293 --- /dev/null +++ b/samples/simpleTypesafe/build.gradle @@ -0,0 +1,58 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * This generated file contains a sample Java library project to get you started. + * For more details take a look at the 'Building Java & JVM projects' chapter in the Gradle + * User Manual available at https://docs.gradle.org/8.0.2/userguide/building_java_projects.html + */ + +plugins { + // Apply the java-library plugin for API and implementation separation. + id 'application' +} + + +group = 'it.pagopa.tech.lollipop.consumer.samples' +version = '1.0.0-RC1' +sourceCompatibility = '11' + +application { + mainClassName = "it.pagopa.tech.sample.LollipopConsumerSample" +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/pagopa/eng-lollipop-consumer-java-sdk" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } +} + +dependencies { + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:core:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:assertion-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:identity-service-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:http-verifier:1.0.0-RC1' + + implementation 'com.typesafe:config:1.3.3' + // Use JUnit Jupiter for testing. + implementation 'org.mock-server:mockserver-netty:5.15.0' + testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2' +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/samples/simpleTypesafe/gradle/wrapper/gradle-wrapper.jar b/samples/simpleTypesafe/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..ccebba77 Binary files /dev/null and b/samples/simpleTypesafe/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/simpleTypesafe/gradle/wrapper/gradle-wrapper.properties b/samples/simpleTypesafe/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..42defcc9 --- /dev/null +++ b/samples/simpleTypesafe/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/simpleTypesafe/gradlew b/samples/simpleTypesafe/gradlew new file mode 100755 index 00000000..79a61d42 --- /dev/null +++ b/samples/simpleTypesafe/gradlew @@ -0,0 +1,244 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/simpleTypesafe/gradlew.bat b/samples/simpleTypesafe/gradlew.bat new file mode 100644 index 00000000..93e3f59f --- /dev/null +++ b/samples/simpleTypesafe/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/simpleTypesafe/settings.gradle b/samples/simpleTypesafe/settings.gradle new file mode 100644 index 00000000..bf5ecc03 --- /dev/null +++ b/samples/simpleTypesafe/settings.gradle @@ -0,0 +1,9 @@ +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + + +rootProject.name = 'typesafe-lollipop-sample' \ No newline at end of file diff --git a/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/ClientMocksConfig.java b/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/ClientMocksConfig.java new file mode 100644 index 00000000..f6ad64f5 --- /dev/null +++ b/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/ClientMocksConfig.java @@ -0,0 +1,274 @@ +package it.pagopa.tech.sample; + +import org.mockserver.client.MockServerClient; +import org.mockserver.model.Header; + +import static it.pagopa.tech.sample.LollipopConstants.*; +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +public class ClientMocksConfig { + + public static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + public static final String IDP_CLIENT_RESPONSE_STRING = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + + private static final String LOCALHOST = "localhost"; + + public static MockServerClient createExpectationAssertionFound() { + MockServerClient mockServerClient = new MockServerClient(LOCALHOST, 3000); + mockServerClient + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", VALID_ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", VALID_JWT))) + .respond(response().withStatusCode(200).withBody(VALID_ASSERTION_XML)); + return mockServerClient; + } + + public static MockServerClient createExpectationIdpTagsFound() { + MockServerClient mockServerClient = new MockServerClient(LOCALHOST, 3001); + mockServerClient + .when(request().withMethod("GET").withPath("/idp-keys/spid")) + .respond(response().withStatusCode(200).withBody("[\"" + IDP_TAG + "\"]")); + return mockServerClient; + } + + public static MockServerClient createExpectationIdpDataFound() { + MockServerClient mockServerClient = new MockServerClient(LOCALHOST, 3001); + mockServerClient + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", IDP_TAG)) + .respond(response().withStatusCode(200).withBody(IDP_CLIENT_RESPONSE_STRING)); + return mockServerClient; + } + + private ClientMocksConfig() {} +} diff --git a/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/LollipopConstants.java b/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/LollipopConstants.java new file mode 100644 index 00000000..79e11c95 --- /dev/null +++ b/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/LollipopConstants.java @@ -0,0 +1,52 @@ +package it.pagopa.tech.sample; + +public class LollipopConstants { + public static final String VALID_ENCODING_UTF8 = "UTF-8"; + public static final String INVALID_ENCODING_UTF_326 = "UTF-326"; + public static final String VALID_CONTENT_DIGEST = "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:"; + public static final String INVALID_CONTENT_DIGEST = "sha-256=:fadsfeagsdage76ad564=:"; + public static final String VALID_MESSAGE_PAYLOAD = "{\"message\":\"a valid message payload\"}"; + public static final String INVALID_MESSAGE_PAYLOAD = "{\"message\":\"an invalid message payload\"}"; + public static final String SIGNATURE_HEADER_VALUE = "sig123=:6scl8sMzJdyG/OrnJXHRM9ajmIjrJ/zrLUDqvfOxj2h51DUKztTua3vR1kSUj/c/VT1ioDlt1QIMARABhquewg==:"; + public static final String INVALID_SIGNATURE_HEADER_VALUE = "sig123=:lTTTRytp53GuUMOB4Rz1z97Y96gfSeEOm/xVpO39d3HR6lLAy4KYiGq+1hZ7nmRFBt2bASWEpen7ov5O4wU3kQ==:"; + public static final String SIGNATURE_INPUT_HEADER_VALUE = "sig123=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + + public static final String ECDSA_LOLLIPOP_JWT_KEY = "eyJrdHkiOiJFQyIsIngiOiJTaHlZa0ZyN1F3eE9rOE5BRXF6aklkTnc4dEVKODlZOVBlWFF1eVVOWDVjIiwieSI6InlULVJxNWc2VlVadENUd0ZnRExDM2RneGNuM2RsSmNGRjhnWGdxYWgyS0UiLCJjcnYiOiJQLTI1NiJ9"; + + public static final String VALID_MULTI_ECDSA_SIGNATURE_INPUT = + "sig1=(\"x-io-sign-qtspclauses\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\"," + + " sig2=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678299228;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + public static final String VALID_MULTI_ECDSA_SIGNATURE = + "sig1=:8MB/iT9iZO2HfVjMds6WdFMQeutkPnoyBDhzeyvIQDhb/tX0nE6HeRSoRBsrl4GUzo6OItnzfzF43Sd14P7tAw==:," + + "sig2=:ZDWu2x+6APQG0Ioj10uNzTBv+5JbFBYnjhqcpL66oGFtwznROAUouXkx80ekzUY5h0HoJWE/ecqxRK2OVeHTiQ==:"; + + public static final String VALID_RSA_PSS_SIGNATURE_INPUT = + "sig1=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678814391;nonce=\"aNonce\";" + + "alg=\"rsa-pss-sha256\";keyid=\"sha256-A3OhKGLYwSvdJ2txHi_SGQ3G-sHLh2Ibu91ErqFx_58\""; + + public static final String VALID_RSA_PSS_SIGNATURE = + "sig1=:q3Og7m8yL18HkrY+zgV92Gj05lrWaFMIEFSPg2PEnO5a46+2Tt/2n7kjqVaGjI1ZXtys+Wyh3cVXCdda" + + "dNARizt0BpCRdT9S4r48xsGO79Ucq4IFwZyyHNudKu5WSH4/55j5yX/YmeCtH+Nt6Nun02OZynn3iQwg" + + "LJB+CGe3h6X02iSvl4wJjKaMGE64RFHa5osE4MctoPD1j0tRkcOtgwrGmFMr282Kqrkabbx1vUpmO9T1k" + + "hjouxIryfUln9zIaZ+wWmukpAZv7TKO3CltNWgfx1XT9m/iwzHiGmtvcHbWVExdAyey8lH23MgLY43AM7y" + + "tLQNSlk1s/bPNbGmPwg==:"; + + public static String LOLLIPOP_RSA_PUBKEY = "eyJrdHkiOiJSU0EiLCJlIjoiQVFBQiIsImtpZCI6InRlc3Qta2V5LXJzYS1wc3MiLCJuIjoicjR0bW0zc" + + "jIwV2RfUGJxdlAxczItUUV0dnB1UmFWOFlxNDBnalVSOHkyUmp4YTZkcEcyR1hIYlBmdk0gIHM4Y3Q" + + "tTGgxR0g0NXgyOFJ3M1J5NTNtbS1vQVhqeVE4Nk9uRGtaNU44bFliZ2dENE8zdzZNNnBBdkxraGs5NU" + + "FuICBkVHJpZmJJRlBOVThQUE1PN095ckZBSHFnRHN6bmpQRm1UT3RDRWNOMloxRnBXZ2Nod3VZTFBMLV" + + "dva3FsdGQxMSAgbnFxemktYko5Y3ZTS0FEWWRVQUFONVdVdHpkcGl5NkxiVGdTeFA3b2NpVTRUbjBnNU" + + "k2YURaSjdBOEx6bzBLU3kgIFpZb0E0ODVtcWNPMEdWQWRWdzlscTRhT1Q5djZkLW5iNGJuTmtRVmtsTFE" + + "zZlZBdkptLXhkRE9wOUxDTkNONDhWICAycG5ET2tGVjYtVTluVjVveWM2WEkydyJ9"; + + public static final String VALID_ASSERTION_REF = "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw"; + public static final String VALID_FISCAL_CODE = "GDNNWA12H81Y874F"; + public static final String VALID_JWT = "aValidJWT"; + + public static final String IDP_TAG = "latest"; + + private LollipopConstants() {} +} diff --git a/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/LollipopConsumerSample.java b/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/LollipopConsumerSample.java new file mode 100644 index 00000000..86a86203 --- /dev/null +++ b/samples/simpleTypesafe/src/main/java/it/pagopa/tech/sample/LollipopConsumerSample.java @@ -0,0 +1,138 @@ +package it.pagopa.tech.sample; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.assertion.client.AssertionClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.impl.AssertionServiceFactoryImpl; +import it.pagopa.tech.lollipop.consumer.assertion.storage.AssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.SimpleAssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandBuilderImpl; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.helper.LollipopConsumerFactoryHelper; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.http_verifier.visma.VismaHttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.storage.SimpleIdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.idp.impl.IdpCertProviderFactoryImpl; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.model.LollipopConsumerRequest; +import it.pagopa.tech.lollipop.consumer.service.impl.LollipopConsumerRequestValidationServiceImpl; +import it.pagopa.tech.lollipop.consumer.utils.LollipopTypesafeConfig; +import org.mockserver.client.MockServerClient; +import org.mockserver.integration.ClientAndServer; + +import java.util.HashMap; + +import static it.pagopa.tech.sample.LollipopConstants.*; + + +public class LollipopConsumerSample { + + private static LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + + public static void main(String[] args) throws Exception { + try (ClientAndServer clientAndServer = ClientAndServer.startClientAndServer(3000, 3001); + MockServerClient mockServerClientAssertion = ClientMocksConfig.createExpectationAssertionFound(); + MockServerClient mockServerClientIdpTag = ClientMocksConfig.createExpectationIdpTagsFound(); + MockServerClient mockServerClientIdpData = ClientMocksConfig.createExpectationIdpDataFound()) { + + LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper = buildLollipopConsumerFactoryHelper(); + LollipopConsumerCommandBuilder commandBuilder = new LollipopConsumerCommandBuilderImpl(lollipopConsumerFactoryHelper); + + // Success + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)) + .doExecute(); + + // Request with invalid content digest + commandBuilder.createCommand(buildLollipopRequest( + INVALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)).doExecute(); + + // Request with invalid message payload + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, INVALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)).doExecute(); + + // Request with invalid encoding + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, INVALID_ENCODING_UTF_326, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, SIGNATURE_HEADER_VALUE)).doExecute(); + + // Request with invalid signature + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + SIGNATURE_INPUT_HEADER_VALUE, INVALID_SIGNATURE_HEADER_VALUE)).doExecute(); + + // Success with multi signature + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, ECDSA_LOLLIPOP_JWT_KEY, + VALID_MULTI_ECDSA_SIGNATURE_INPUT, VALID_MULTI_ECDSA_SIGNATURE)).doExecute(); + + // thumbprint validation failed + commandBuilder.createCommand(buildLollipopRequest( + VALID_CONTENT_DIGEST, VALID_ENCODING_UTF8, VALID_MESSAGE_PAYLOAD, + LOLLIPOP_RSA_PUBKEY, VALID_RSA_PSS_SIGNATURE_INPUT, VALID_RSA_PSS_SIGNATURE)).doExecute(); + + } + } + + private static LollipopConsumerFactoryHelper buildLollipopConsumerFactoryHelper() throws Exception { + LollipopTypesafeConfig typesafeConfig = new LollipopTypesafeConfig(ConfigFactory.parseResources("application.properties")); + lollipopConsumerRequestConfig = LollipopConsumerRequestConfig.builder() + .assertionNotBeforeDateFormat(typesafeConfig.getLOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT()) + .assertionInstantDateFormat(typesafeConfig.getLOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT()) + .build(); + HttpMessageVerifierFactory messageVerifierFactory = new VismaHttpMessageVerifierFactory(VALID_ENCODING_UTF8, + LollipopConsumerRequestConfig.builder().build()); + AssertionStorageProvider assertionStorageProvider = new SimpleAssertionStorageProvider(); + IdpCertProviderFactory idpCertProviderFactory = new IdpCertProviderFactoryImpl( + new IdpCertSimpleClientProvider(IdpCertSimpleClientConfig.builder().baseUri(typesafeConfig.getIDP_CLIENT_BASE_URI()).build(), + new SimpleIdpCertStorageProvider(), new IdpCertStorageConfig())); + AssertionClientProvider assertionClientProvider = + new AssertionSimpleClientProvider(AssertionSimpleClientConfig.builder().build()); + AssertionServiceFactory assertionServiceFactory = new AssertionServiceFactoryImpl( + assertionStorageProvider, assertionClientProvider, new StorageConfig()); + LollipopLoggerServiceFactory lollipopLoggerServiceFactory = new LollipopLogbackLoggerServiceFactory(); + return new LollipopConsumerFactoryHelper(lollipopLoggerServiceFactory, messageVerifierFactory, idpCertProviderFactory, assertionServiceFactory, + new LollipopConsumerRequestValidationServiceImpl(lollipopConsumerRequestConfig), lollipopConsumerRequestConfig); + } + + private static LollipopConsumerRequest buildLollipopRequest( + String contentDigest, + String encoding, + String payload, + String lollipopKey, + String signatureInput, + String signature) { + HashMap lollipopHeaderParams = new HashMap<>(); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getContentDigestHeader(), contentDigest); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getContentEncodingHeader(), encoding); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getSignatureInputHeader(), signatureInput); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getSignatureHeader(), signature); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getPublicKeyHeader(), lollipopKey); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getOriginalMethodHeader(), lollipopConsumerRequestConfig.getExpectedFirstLcOriginalMethod()); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getOriginalURLHeader(), lollipopConsumerRequestConfig.getExpectedFirstLcOriginalUrl()); + lollipopHeaderParams.put("X-io-sign-qtspclauses","anIoSignClauses"); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getAssertionRefHeader(), VALID_ASSERTION_REF); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getAssertionTypeHeader(), "SAML"); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getAuthJWTHeader(), VALID_JWT); + lollipopHeaderParams.put(lollipopConsumerRequestConfig.getUserIdHeader(), VALID_FISCAL_CODE); + + + return LollipopConsumerRequest.builder() + .requestBody(payload) + .headerParams(lollipopHeaderParams) + .build(); + } +} diff --git a/samples/simpleTypesafe/src/main/resources/application.properties b/samples/simpleTypesafe/src/main/resources/application.properties new file mode 100644 index 00000000..b6ae2542 --- /dev/null +++ b/samples/simpleTypesafe/src/main/resources/application.properties @@ -0,0 +1,10 @@ +lollipop.idp.client.mock.enabled=true +lollipop.assertion.client.mock.enabled=true + +lollipop.core.config.assertionNotBeforeDateFormat=yyyy-MM-dd'T'HH:mm:ss.SSS'Z' +lollipop.core.config.assertionInstantDateFormat=yyyy-MM-dd'T'HH:mm:ss.SSS'Z' + +lollipop.idp.rest.config.baseUri=http://localhost:3001 + + + diff --git a/samples/spring/.gitignore b/samples/spring/.gitignore new file mode 100644 index 00000000..c2065bc2 --- /dev/null +++ b/samples/spring/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/samples/spring/README.md b/samples/spring/README.md new file mode 100644 index 00000000..78c0e627 --- /dev/null +++ b/samples/spring/README.md @@ -0,0 +1,55 @@ +# eng-lollipop-consumer-java-sdk + +## Spring sample application + +### Run the sample + ++ #### Publish library dependencies to maven local +In the project root folder run + +```bash +./gradlew publishToMavenLocal +``` + ++ #### Define environment variables for the sample +To run the application with the existing examples, define the following variables in the sample run configuration: + +| VARIABLE | VALUE | +|-------------------------------------------|---------------------------| +| ASSERTION_CLIENT_MOCK_ENABLED | true | +| IDP_CLIENT_MOCK_ENABLED | true | +| IDP_CLIENT_BASE_URI | http://localhost:3001 | +| LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT | yyyy-MM-dd'T'HH:mm:ss.'Z' | +| LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT | yyyy-MM-dd'T'HH:mm:ss.'Z' | + +You can configure these variables for your custom usage (see "Configuration" paragraph) + ++ #### Run the sample +You can now run the sample, it will be exposed to http://localhost:8080/api/v1/lollipop-consumer + +To test the sample you can use our examples with these postman [environment](https://github.com/pagopa/eng-lollipop-consumer-java-sdk/blob/0f92a666b0f5e71ec13f11560e435be82df0f5e9/e2e/env/lollipopEnvironmentVariables.postman_environment.json) +and [collection](https://github.com/pagopa/eng-lollipop-consumer-java-sdk/blob/0f92a666b0f5e71ec13f11560e435be82df0f5e9/e2e/collections/lollipopSDKTest.postman_collection.json) + +### Configuration +The configurable variables are the following: + +| VARIABLE | DEFAULT | USAGE | +|-------------------------------------------|-----------------------------------------------------------------------|--------------------------------------------------------------------| +| ASSERTION_CLIENT_MOCK_ENABLED | false | Enable Mockserver client | +| IDP_CLIENT_MOCK_ENABLED | false | Enable Mockserver client | +| SAMPLE_LOLLIPOP_CONSUMER_ENDPOINT | /api/v1/lollipop-consumer | Define sample controller endpoint | +| LOLLIPOP_ASSERTION_EXPIRE_IN_DAYS | 180 | Define after how many days assertion expires | +| LOLLIPOP_EXPECTED_LC_ORIGINAL_URL | https://api-app.io.pagopa.it/first-lollipop/sign | Define original url expected in request's header | +| LOLLIPOP_EXPECTED_LC_ORIGINAL_METHOD | POST | Define original method expected in request's header | +| LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT | yyyy-MM-dd'T'HH:mm:ss.SSS'Z' | Define the date format used in the Assertion's notBefore field | +| LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT | yyyy-MM-dd'T'HH:mm:ss.SSS'Z' | Define the date format used in the Assertion's Issue Instant field | +| IDP_CLIENT_CIEID | https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO | Define entity id for CIE identity provider | +| IDP_CLIENT_BASE_URI | https://api.is.eng.pagopa.it | Define base uri to retrieve IDP certification data | +| IDP_CLIENT_CIE_ENDPOINT | /idp-keys/cie | Define endpoint to IDP_CLIENT_BASE_URI for CIE's certification | +| IDP_CLIENT_SPID_ENDPOINT | /idp-keys/spid | Define endpoint to IDP_CLIENT_BASE_URI for SPID's certification | +| IDP_STORAGE_ENABLED | true | Enable internal cache storage for IDP certification data | +| IDP_STORAGE_EVICTION_DELAY | 1 | Define storage eviction delay for IDP's storage (in Minutes by default) | +| ASSERTION_REST_URI | http://localhost:3000 | Define base uri to retrieve the Assertion | +| ASSERTION_REST_ENDPOINT | /assertions | Define endpoint to ASSERTION_REST_URI | +| ASSERTION_STORAGE_ENABLED | true | Enable internal cache storage for assertions | +| ASSERTION_STORAGE_EVICTION_DELAY | 1 | Define storage eviction delay for assertion's storage (in Minutes by default) | \ No newline at end of file diff --git a/samples/spring/build.gradle b/samples/spring/build.gradle new file mode 100644 index 00000000..dd574b67 --- /dev/null +++ b/samples/spring/build.gradle @@ -0,0 +1,60 @@ +plugins { + id 'java' + id 'org.springframework.boot' version '2.7.10' + id 'io.spring.dependency-management' version '1.0.15.RELEASE' +} + +group = 'it.pagopa.tech.lollipop.consumer.samples' +version = '1.0.0-RC1' +sourceCompatibility = '11' + +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} + +repositories { + mavenCentral() + mavenLocal() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/pagopa/eng-lollipop-consumer-java-sdk" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } +} + +dependencies { + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:core:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:assertion-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:identity-service-rest-client-native:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:http-verifier:1.0.0-RC1' + implementation 'it.pagopa.tech.lollipop-consumer-java-sdk:spring-impl:1.0.0-RC1' + + implementation 'org.springframework.boot:spring-boot-starter-actuator' + + implementation 'org.mock-server:mockserver-netty:5.15.0' + implementation 'org.springframework.boot:spring-boot-starter-web' + testImplementation 'ch.qos.logback:logback-classic:1.2.11' + testImplementation 'ch.qos.logback:logback-core:1.2.11' + testImplementation 'org.codehaus.janino:janino:3.1.9' + testImplementation 'org.slf4j:slf4j-api:1.7.36' + compileOnly 'org.projectlombok:lombok' + developmentOnly 'org.springframework.boot:spring-boot-devtools' + annotationProcessor 'org.projectlombok:lombok' + testImplementation 'org.springframework.boot:spring-boot-starter-test' +} + +tasks.named('test') { + useJUnitPlatform() +} diff --git a/samples/spring/gradle/wrapper/gradle-wrapper.jar b/samples/spring/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..249e5832 Binary files /dev/null and b/samples/spring/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/spring/gradle/wrapper/gradle-wrapper.properties b/samples/spring/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..774fae87 --- /dev/null +++ b/samples/spring/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/spring/gradlew b/samples/spring/gradlew new file mode 100644 index 00000000..a69d9cb6 --- /dev/null +++ b/samples/spring/gradlew @@ -0,0 +1,240 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License 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. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/samples/spring/gradlew.bat b/samples/spring/gradlew.bat new file mode 100644 index 00000000..f127cfd4 --- /dev/null +++ b/samples/spring/gradlew.bat @@ -0,0 +1,91 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/spring/settings.gradle b/samples/spring/settings.gradle new file mode 100644 index 00000000..edb53beb --- /dev/null +++ b/samples/spring/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'spring-application-sample' diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/SampleApplication.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/SampleApplication.java new file mode 100644 index 00000000..dae8bbf8 --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/SampleApplication.java @@ -0,0 +1,12 @@ +package it.pagopa.tech.lollipop.consumer.sample; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "it.pagopa.tech") +public class SampleApplication { + + public static void main(String[] args) { + SpringApplication.run(SampleApplication.class, args); + } +} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/AssertionMockServerConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/AssertionMockServerConfig.java new file mode 100644 index 00000000..dfb7e8fd --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/AssertionMockServerConfig.java @@ -0,0 +1,153 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import org.mockserver.client.MockServerClient; +import org.mockserver.integration.ClientAndServer; +import org.mockserver.model.Header; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +@Component +@ConditionalOnProperty( + value="lollipop.assertion.client.mock.enabled", + havingValue = "true" +) +public class AssertionMockServerConfig { + + private static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + private static final String ASSERTION_REF = "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw"; + private static final String JWT = "aValidJWT"; + + public AssertionMockServerConfig() { + ClientAndServer.startClientAndServer(3000); + new MockServerClient("localhost", 3000) + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(200).withBody(VALID_ASSERTION_XML)); + } +} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/IdpMockServerConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/IdpMockServerConfig.java new file mode 100644 index 00000000..86483ab0 --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/IdpMockServerConfig.java @@ -0,0 +1,138 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import org.mockserver.client.MockServerClient; +import org.mockserver.integration.ClientAndServer; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +@Component +@ConditionalOnProperty( + value="lollipop.idp.client.mock.enabled", + havingValue = "true" +) +public class IdpMockServerConfig { + + private static final String IDP_CLIENT_RESPONSE_STRING = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + private static final String IDP_TAG = "latest"; + + public IdpMockServerConfig() { + ClientAndServer.startClientAndServer(3001); + new MockServerClient("localhost", 3001) + .when(request().withMethod("GET").withPath("/idp-keys/spid")) + .respond(response().withStatusCode(200).withBody("[\"" + IDP_TAG + "\"]")); + new MockServerClient("localhost", 3001) + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", IDP_TAG)) + .respond(response().withStatusCode(200).withBody(IDP_CLIENT_RESPONSE_STRING)); + } +} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleLollipopConsumerConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleLollipopConsumerConfig.java new file mode 100644 index 00000000..ab2ec0cd --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleLollipopConsumerConfig.java @@ -0,0 +1,13 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationProperties(prefix = "sample.lollipop.consumer.config") +@ConfigurationPropertiesScan +@Data +public class SampleLollipopConsumerConfig { + + private String endpoint; +} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleServicesConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleServicesConfig.java new file mode 100644 index 00000000..0baec8b3 --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleServicesConfig.java @@ -0,0 +1,55 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.impl.AssertionServiceFactoryImpl; +import it.pagopa.tech.lollipop.consumer.assertion.storage.SimpleAssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.http_verifier.visma.VismaHttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.storage.SimpleIdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.idp.impl.IdpCertProviderFactoryImpl; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.spring.config.SpringLollipopConsumerRequestConfig; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableConfigurationProperties(value = {SpringLollipopConsumerRequestConfig.class, SpringAssertionStorageConfig.class, + SpringAssertionSimpleClientConfig.class, SpringIdpCertSimpleClientConfig.class, SpringIdpCertStorageConfig.class, SampleLollipopConsumerConfig.class}) +public class SampleServicesConfig { + + @Bean + public LollipopLoggerServiceFactory lollipopLoggerServiceFactory() { + return new LollipopLogbackLoggerServiceFactory(); + } + + @Bean + public HttpMessageVerifierFactory httpMessageVerifierFactory( + SpringLollipopConsumerRequestConfig springLollipopConsumerRequestConfig) throws Exception { + return new VismaHttpMessageVerifierFactory("UTF-8", springLollipopConsumerRequestConfig); + } + + @Bean + public IdpCertProviderFactory idpCertProviderFactory(IdpCertSimpleClientConfig simpleClientConfig, + IdpCertStorageConfig idpCertStorageConfig) { + return new IdpCertProviderFactoryImpl( + new IdpCertSimpleClientProvider(simpleClientConfig,new SimpleIdpCertStorageProvider(), idpCertStorageConfig)); + } + + @Bean + public AssertionServiceFactory assertionServiceFactory( + SpringAssertionSimpleClientConfig assertionSimpleClientConfig, + SpringAssertionStorageConfig assertionStorageConfig) { + return new AssertionServiceFactoryImpl( + new SimpleAssertionStorageProvider(), + new AssertionSimpleClientProvider(assertionSimpleClientConfig), + assertionStorageConfig); + } + +} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleWebConfigurer.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleWebConfigurer.java new file mode 100644 index 00000000..c2b583fb --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SampleWebConfigurer.java @@ -0,0 +1,37 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import it.pagopa.tech.lollipop.consumer.spring.HttpVerifierHandlerInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import org.springframework.util.AntPathMatcher; +import org.springframework.web.filter.CommonsRequestLoggingFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Component +public class SampleWebConfigurer implements WebMvcConfigurer { + + @Autowired + private HttpVerifierHandlerInterceptor interceptor; + @Autowired + private SampleLollipopConsumerConfig sampleLollipopConsumerConfig; + + @Bean + public CommonsRequestLoggingFilter loggingFilter() { + CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); + filter.setIncludeQueryString(true); + filter.setIncludeClientInfo(true); + filter.setIncludeHeaders(true); + filter.setIncludePayload(true); + return filter; + } + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor) + .addPathPatterns(sampleLollipopConsumerConfig.getEndpoint()) + .pathMatcher(new AntPathMatcher()); + } +} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringAssertionSimpleClientConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringAssertionSimpleClientConfig.java new file mode 100644 index 00000000..ab04050b --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringAssertionSimpleClientConfig.java @@ -0,0 +1,11 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationProperties( + prefix = "lollipop.assertion.rest.config" +) +@ConfigurationPropertiesScan +public class SpringAssertionSimpleClientConfig extends AssertionSimpleClientConfig {} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringAssertionStorageConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringAssertionStorageConfig.java new file mode 100644 index 00000000..77087256 --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringAssertionStorageConfig.java @@ -0,0 +1,12 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationProperties( + prefix = "lollipop.assertion.storage.config" +) +@ConfigurationPropertiesScan +public class SpringAssertionStorageConfig extends StorageConfig { +} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringIdpCertSimpleClientConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringIdpCertSimpleClientConfig.java new file mode 100644 index 00000000..dc94202e --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringIdpCertSimpleClientConfig.java @@ -0,0 +1,11 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationProperties( + prefix = "lollipop.idp.rest.config" +) +@ConfigurationPropertiesScan +public class SpringIdpCertSimpleClientConfig extends IdpCertSimpleClientConfig {} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringIdpCertStorageConfig.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringIdpCertStorageConfig.java new file mode 100644 index 00000000..1c8adecd --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/config/SpringIdpCertStorageConfig.java @@ -0,0 +1,11 @@ +package it.pagopa.tech.lollipop.consumer.sample.config; + +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +@ConfigurationProperties( + prefix = "lollipop.idp.storage.config" +) +@ConfigurationPropertiesScan +public class SpringIdpCertStorageConfig extends IdpCertStorageConfig {} diff --git a/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/controller/SampleController.java b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/controller/SampleController.java new file mode 100644 index 00000000..80b7ffbf --- /dev/null +++ b/samples/spring/src/main/java/it/pagopa/tech/lollipop/consumer/sample/controller/SampleController.java @@ -0,0 +1,12 @@ +package it.pagopa.tech.lollipop.consumer.sample.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SampleController { + + @RequestMapping("${sample.lollipop.consumer.config.endpoint}") + public @ResponseBody String sample() { return "Sample";} +} diff --git a/samples/spring/src/main/resources/application.properties b/samples/spring/src/main/resources/application.properties new file mode 100644 index 00000000..091162b0 --- /dev/null +++ b/samples/spring/src/main/resources/application.properties @@ -0,0 +1,34 @@ +spring.application.name=spring-lollipop-consumer + +sample.lollipop.consumer.config.endpoint=${SAMPLE_LOLLIPOP_CONSUMER_ENDPOINT:/api/v1/lollipop-consumer} + +##Client mocks config +lollipop.idp.client.mock.enabled=${IDP_CLIENT_MOCK_ENABLED:false} +lollipop.assertion.client.mock.enabled=${ASSERTION_CLIENT_MOCK_ENABLED:false} + +##General Lollipop Configs Sample +lollipop.core.config.assertionExpireInDays=${LOLLIPOP_ASSERTION_EXPIRE_IN_DAYS:180} +lollipop.core.config.expectedFirstLcOriginalUrl=${LOLLIPOP_EXPECTED_LC_ORIGINAL_URL:https://api-app.io.pagopa.it/first-lollipop/sign} +lollipop.core.config.expectedFirstLcOriginalMethod=${LOLLIPOP_EXPECTED_LC_ORIGINAL_METHOD:POST} +lollipop.core.config.assertionNotBeforeDateFormat=${LOLLIPOP_ASSERTION_NOT_BEFORE_DATE_FORMAT:yyyy-MM-dd'T'HH:mm:ss.SSS'Z'} +lollipop.core.config.assertionInstantDateFormat=${LOLLIPOP_ASSERTION_INSTANT_DATE_FORMAT:yyyy-MM-dd'T'HH:mm:ss.SSS'Z'} + +###Idp Client Configs +lollipop.idp.rest.config.cieEntityId=${IDP_CLIENT_CIEID:https://idserver.servizicie.interno.gov.it/idp/profile/SAML2/POST/SSO} +lollipop.idp.rest.config.baseUri=${IDP_CLIENT_BASE_URI:https://api.is.eng.pagopa.it} +lollipop.idp.rest.config.idpKeysCieEndpoint=${IDP_CLIENT_CIE_ENDPOINT:/idp-keys/cie} +lollipop.idp.rest.config.idpKeysSpidEndpoint=${IDP_CLIENT_SPID_ENDPOINT:/idp-keys/spid} + +###Idp Storage Configs +lollipop.idp.storage.config.idpCertDataStorageEnabled=${IDP_STORAGE_ENABLED:true} +lollipop.idp.storage.config.storageEvictionDelay=${IDP_STORAGE_EVICTION_DELAY:1} + +##Assertion Client Configs +lollipop.assertion.rest.config.baseUri=${ASSERTION_REST_URI:http://localhost:3000} +lollipop.assertion.rest.config.assertionRequestEndpoint=${ASSERTION_REST_ENDPOINT:/assertions} + +##Assertion Storage Configs +lollipop.assertion.rest.config.assertionStorageEnabled=${ASSERTION_STORAGE_ENABLED:true} +lollipop.assertion.rest.config.storageEvictionDelay=${ASSERTION_STORAGE_EVICTION_DELAY:1} + + diff --git a/samples/spring/src/test/java/it/pagopa/tech/lollipop/consumer/sample/SampleApplicationTests.java b/samples/spring/src/test/java/it/pagopa/tech/lollipop/consumer/sample/SampleApplicationTests.java new file mode 100644 index 00000000..694fd01f --- /dev/null +++ b/samples/spring/src/test/java/it/pagopa/tech/lollipop/consumer/sample/SampleApplicationTests.java @@ -0,0 +1,13 @@ +package it.pagopa.tech.lollipop.consumer.sample; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class SampleApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/servlet-impl/README.md b/servlet-impl/README.md new file mode 100644 index 00000000..a82228f6 --- /dev/null +++ b/servlet-impl/README.md @@ -0,0 +1,30 @@ +# Lollipop SDK Servlet Implementations + +This module contains implementations of general utility of the core SDK with a servlet filter. + +## Servlet filter Example + +In order to use the provided Servlet filter it is possible to register it in the application context, providing +a configuration for the Spring application to use in order to determine which endpoints should be validated using +the library functionalities + +``` +@Configuration +public class DemoWebConfigurer implements WebMvcConfigurer { + + @Autowired private LollipopConsumerCommandBuilder commandBuilder; + + @Bean + public FilterRegistrationBean requestFilter() { + FilterRegistrationBean registrationBean = + new FilterRegistrationBean<>(); + + registrationBean.setFilter(new HttpVerifierServletFilter(commandBuilder)); + registrationBean.addUrlPatterns("/*"); + + return registrationBean; + } +} +``` + +Otherwise, it is possible to register the filter together with a servlet in a web server like Tomcat as shown in the [Servlet sample](samples/servlet) \ No newline at end of file diff --git a/servlet-impl/build.gradle b/servlet-impl/build.gradle new file mode 100644 index 00000000..719fe829 --- /dev/null +++ b/servlet-impl/build.gradle @@ -0,0 +1,42 @@ +plugins { + id 'java-library' + id("io.freefair.lombok") version "8.0.0" + id 'io.spring.dependency-management' version '1.0.15.RELEASE' +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } +} + +dependencies { + implementation(project(':core')) { + exclude group: 'ch.qos.logback', module: 'logback-classic' + exclude group: 'ch.qos.logback', module: 'logback-core' + } + implementation 'javax.servlet:javax.servlet-api:3.1.0' + implementation 'ch.qos.logback:logback-classic:1.2.11' + implementation 'ch.qos.logback:logback-core:1.2.11' + + testImplementation 'org.springframework.boot:spring-boot-starter-web:2.7.10' + testImplementation 'org.springframework.boot:spring-boot-starter-test:2.7.10' + testImplementation 'org.slf4j:slf4j-api:1.7.36' + testImplementation 'org.mock-server:mockserver-netty:5.15.0' + testImplementation 'org.mockito:mockito-core:5.2.0' + testImplementation 'org.mockito:mockito-junit-jupiter:5.2.0' + testImplementation project(':http-verifier') + testImplementation project(':assertion-rest-client-native') + testImplementation project(':identity-service-rest-client-native') +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/servlet-impl/src/main/java/it/pagopa/tech/lollipop/consumer/servlet/HttpVerifierServletFilter.java b/servlet-impl/src/main/java/it/pagopa/tech/lollipop/consumer/servlet/HttpVerifierServletFilter.java new file mode 100644 index 00000000..75d10ab2 --- /dev/null +++ b/servlet-impl/src/main/java/it/pagopa/tech/lollipop/consumer/servlet/HttpVerifierServletFilter.java @@ -0,0 +1,62 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet; + +import static it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandImpl.VERIFICATION_SUCCESS_CODE; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommand; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.model.CommandResult; +import it.pagopa.tech.lollipop.consumer.utils.LollipopConsumerConverter; +import java.io.IOException; +import javax.servlet.*; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +/** Instance of Servlet filter, to be used for Lollipop Request validations */ +@Slf4j +@AllArgsConstructor +public class HttpVerifierServletFilter implements Filter { + + private final LollipopConsumerCommandBuilder consumerCommandBuilder; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + log.info("Servlet filter initialized"); + } + + /** + * @param servletRequest current request + * @param servletResponse current request + * @param filterChain the filter chain + * @throws IOException throws exception if the conversion of a http request fails + * @throws ServletException throws in case of request validation failure + */ + @Override + public void doFilter( + ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + LollipopConsumerCommand lollipopConsumerCommand = + consumerCommandBuilder.createCommand( + LollipopConsumerConverter.convertToLollipopRequest( + (HttpServletRequest) servletRequest)); + + try { + CommandResult commandResult = lollipopConsumerCommand.doExecute(); + LollipopConsumerConverter.interceptResult( + commandResult, (HttpServletResponse) servletResponse); + + if (commandResult.getResultCode().equals(VERIFICATION_SUCCESS_CODE)) { + filterChain.doFilter(servletRequest, servletResponse); + } + } catch (Exception e) { + log.error("Error verifying request", e); + } + } + + @Override + public void destroy() { + log.info("Servlet filter destroyed"); + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/HttpVerifierServletFilterTest.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/HttpVerifierServletFilterTest.java new file mode 100644 index 00000000..95ed9d49 --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/HttpVerifierServletFilterTest.java @@ -0,0 +1,94 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet; + +import static it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandImpl.VERIFICATION_SUCCESS_CODE; +import static org.mockito.Mockito.*; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommand; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.model.CommandResult; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Collections; +import java.util.Enumeration; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class HttpVerifierServletFilterTest { + + private HttpServletRequest requestMock; + private HttpServletResponse responseMock; + private FilterChain filterChainMock; + private LollipopConsumerCommandBuilder commandBuilderMock; + private LollipopConsumerCommand commandMock; + + private HttpVerifierServletFilter sut; + + @BeforeEach + void setUp() { + requestMock = mock(HttpServletRequest.class); + responseMock = mock(HttpServletResponse.class); + filterChainMock = mock(FilterChain.class); + commandBuilderMock = mock(LollipopConsumerCommandBuilder.class); + commandMock = mock(LollipopConsumerCommand.class); + + sut = new HttpVerifierServletFilter(commandBuilderMock); + + String header1 = "Header1"; + Enumeration headers = Collections.enumeration(Collections.singletonList(header1)); + doReturn(headers).when(requestMock).getHeaderNames(); + doReturn("value").when(requestMock).getHeader(header1); + } + + @Test + void testDoFilterSuccess() throws IOException, ServletException { + CommandResult commandResult = + new CommandResult(VERIFICATION_SUCCESS_CODE, "request validation success"); + + doReturn(commandMock).when(commandBuilderMock).createCommand(any()); + doReturn(commandResult).when(commandMock).doExecute(); + doNothing().when(filterChainMock).doFilter(requestMock, responseMock); + + sut.doFilter(requestMock, responseMock, filterChainMock); + + verify(responseMock, never()).setStatus(401); + verify(responseMock, never()).getWriter(); + verify(filterChainMock).doFilter(requestMock, responseMock); + } + + @Test + void testDoFilterFailForValidationError() throws IOException, ServletException { + PrintWriter printWriter = mock(PrintWriter.class); + CommandResult commandResult = new CommandResult("FAIL", "request validation failure"); + + doReturn(commandMock).when(commandBuilderMock).createCommand(any()); + doReturn(commandResult).when(commandMock).doExecute(); + doReturn(printWriter).when(responseMock).getWriter(); + + sut.doFilter(requestMock, responseMock, filterChainMock); + + verify(responseMock).setStatus(401); + verify(responseMock).getWriter(); + verify(printWriter).write(commandResult.getResultMessage()); + verify(filterChainMock, never()).doFilter(requestMock, responseMock); + } + + @Test + void testDoFilterFailForConversionException() throws IOException, ServletException { + CommandResult commandResult = new CommandResult("FAIL", "request validation failure"); + + doReturn(commandMock).when(commandBuilderMock).createCommand(any()); + doReturn(commandResult).when(commandMock).doExecute(); + doThrow(IOException.class).when(responseMock).getWriter(); + + sut.doFilter(requestMock, responseMock, filterChainMock); + + verify(responseMock).setStatus(401); + verify(responseMock).getWriter(); + verify(filterChainMock, never()).doFilter(requestMock, responseMock); + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/ServletDemoApplication.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/ServletDemoApplication.java new file mode 100644 index 00000000..759a2c96 --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/ServletDemoApplication.java @@ -0,0 +1,13 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "it.pagopa.tech") +public class ServletDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(ServletDemoApplication.class, args); + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/ServletIntegrationTest.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/ServletIntegrationTest.java new file mode 100644 index 00000000..69093036 --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/ServletIntegrationTest.java @@ -0,0 +1,180 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet; + +import static it.pagopa.tech.lollipop.consumer.servlet.utils.SimpleClientsTestUtils.*; +import static org.mockserver.integration.ClientAndServer.startClientAndServer; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.servlet.config.HttpVerifierConfiguration; +import it.pagopa.tech.lollipop.consumer.servlet.utils.SimpleClientsTestUtils; +import java.io.IOException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockserver.integration.ClientAndServer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.client.RestTemplate; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration( + classes = { + ServletDemoApplication.class, + LollipopConsumerRequestConfig.class, + HttpVerifierConfiguration.class + }) +public class ServletIntegrationTest { + + @LocalServerPort private int port; + @Autowired private TestRestTemplate restTemplate; + @Autowired private LollipopConsumerRequestConfig lollipopConsumerRequestConfig; + @Autowired private IdpCertSimpleClientConfig idpCertSimpleClientConfig; + + private static ClientAndServer mockServer; + + private static final String CONTENT_DIGEST = + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:"; + private static final String USER_ID = "GDNNWA12H81Y874F"; + private static final String SIGNATURE_INPUT = + "sig123=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + private static final String SIGNATURE = + "sig123=:6scl8sMzJdyG/OrnJXHRM9ajmIjrJ/zrLUDqvfOxj2h51DUKztTua3vR1kSUj/c/VT1ioDlt1QIMARABhquewg==:"; + + @BeforeAll + public static void startServer() { + mockServer = startClientAndServer(3000, 3001); + } + + @Test + void testWithValidRequestReturnsSuccess() throws IOException { + SimpleClientsTestUtils.createExpectationAssertionFound(); + SimpleClientsTestUtils.createExpectationIdpFound(); + lollipopConsumerRequestConfig.setAssertionNotBeforeDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + lollipopConsumerRequestConfig.setAssertionInstantDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + idpCertSimpleClientConfig.setBaseUri("http://localhost:3001"); + + RestTemplate exec = restTemplate.getRestTemplate(); + exec.getClientHttpRequestInitializers() + .add( + request -> { + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getContentDigestHeader(), + CONTENT_DIGEST); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getOriginalURLHeader(), + lollipopConsumerRequestConfig + .getExpectedFirstLcOriginalUrl()); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getOriginalMethodHeader(), + lollipopConsumerRequestConfig + .getExpectedFirstLcOriginalMethod()); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getPublicKeyHeader(), + VALID_PUBLIC_KEY); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getAssertionRefHeader(), + ASSERTION_REF); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getAssertionTypeHeader(), + "SAML"); + request.getHeaders() + .add(lollipopConsumerRequestConfig.getAuthJWTHeader(), JWT); + request.getHeaders() + .add(lollipopConsumerRequestConfig.getUserIdHeader(), USER_ID); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getSignatureInputHeader(), + SIGNATURE_INPUT); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getSignatureHeader(), + SIGNATURE); + }); + + ResponseEntity response = + exec.postForEntity( + "http://localhost:" + port, + "{\"message\":\"a valid message payload\"}", + String.class); + Assertions.assertNotNull(response); + Assertions.assertEquals(200, response.getStatusCodeValue()); + } + + @Test + void testWithInvalidPayloadRequestReturnsUnauthorized() throws IOException { + SimpleClientsTestUtils.createExpectationAssertionFound(); + + RestTemplate exec = restTemplate.getRestTemplate(); + exec.getClientHttpRequestInitializers() + .add( + request -> { + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getContentDigestHeader(), + CONTENT_DIGEST); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getOriginalURLHeader(), + lollipopConsumerRequestConfig + .getExpectedFirstLcOriginalUrl()); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getOriginalMethodHeader(), + lollipopConsumerRequestConfig + .getExpectedFirstLcOriginalMethod()); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getPublicKeyHeader(), + VALID_PUBLIC_KEY); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getAssertionRefHeader(), + ASSERTION_REF); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getAssertionTypeHeader(), + "SAML"); + request.getHeaders() + .add(lollipopConsumerRequestConfig.getAuthJWTHeader(), JWT); + request.getHeaders() + .add(lollipopConsumerRequestConfig.getUserIdHeader(), USER_ID); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getSignatureInputHeader(), + SIGNATURE_INPUT); + request.getHeaders() + .add( + lollipopConsumerRequestConfig.getSignatureHeader(), + SIGNATURE); + }); + + ResponseEntity response = + exec.postForEntity( + "http://localhost:" + port, + "{\"message\":\"an invalid message payload\"}", + String.class); + Assertions.assertNotNull(response); + Assertions.assertEquals(401, response.getStatusCodeValue()); + } + + @AfterAll + public static void stopServer() { + mockServer.stop(); + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/DemoServicesConfig.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/DemoServicesConfig.java new file mode 100644 index 00000000..0101d310 --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/DemoServicesConfig.java @@ -0,0 +1,68 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet.config; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.impl.AssertionServiceFactoryImpl; +import it.pagopa.tech.lollipop.consumer.assertion.storage.SimpleAssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.http_verifier.visma.VismaHttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.storage.SimpleIdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.idp.impl.IdpCertProviderFactoryImpl; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerServiceFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DemoServicesConfig { + + @Bean + public LollipopLoggerServiceFactory lollipopLoggerServiceFactory() { + return new LollipopLogbackLoggerServiceFactory(); + } + + @Bean + public LollipopConsumerRequestConfig verifierConfiguration() { + return new LollipopConsumerRequestConfig(); + } + + @Bean + public HttpMessageVerifierFactory httpMessageVerifierFactory() throws Exception { + return new VismaHttpMessageVerifierFactory("UTF-8", verifierConfiguration()); + } + + @Bean + public IdpCertProviderFactory idpCertProviderFactory() { + return new IdpCertProviderFactoryImpl( + new IdpCertSimpleClientProvider( + idpCertSimpleClientConfig(), + new SimpleIdpCertStorageProvider(), + new IdpCertStorageConfig())); + } + + @Bean + public AssertionServiceFactory assertionServiceFactory() { + return new AssertionServiceFactoryImpl( + new SimpleAssertionStorageProvider(), + new AssertionSimpleClientProvider(AssertionSimpleClientConfig.builder().build()), + storageConfig()); + } + + @Bean + public StorageConfig storageConfig() { + return new StorageConfig(); + } + + @Bean + public IdpCertSimpleClientConfig idpCertSimpleClientConfig() { + return IdpCertSimpleClientConfig.builder().build(); + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/DemoWebConfigurer.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/DemoWebConfigurer.java new file mode 100644 index 00000000..cfe451e2 --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/DemoWebConfigurer.java @@ -0,0 +1,38 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet.config; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.servlet.HttpVerifierServletFilter; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.filter.CommonsRequestLoggingFilter; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class DemoWebConfigurer implements WebMvcConfigurer { + + @Autowired private LollipopConsumerCommandBuilder commandBuilder; + + @Bean + public CommonsRequestLoggingFilter loggingFilter() { + CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); + filter.setIncludeQueryString(true); + filter.setIncludeClientInfo(true); + filter.setIncludeHeaders(true); + filter.setIncludePayload(true); + return filter; + } + + @Bean + public FilterRegistrationBean requestFilter() { + FilterRegistrationBean registrationBean = + new FilterRegistrationBean<>(); + + registrationBean.setFilter(new HttpVerifierServletFilter(commandBuilder)); + registrationBean.addUrlPatterns("/*"); + + return registrationBean; + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/HttpVerifierConfiguration.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/HttpVerifierConfiguration.java new file mode 100644 index 00000000..d5a94646 --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/config/HttpVerifierConfiguration.java @@ -0,0 +1,48 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet.config; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandBuilderImpl; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.helper.LollipopConsumerFactoryHelper; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import it.pagopa.tech.lollipop.consumer.service.impl.LollipopConsumerRequestValidationServiceImpl; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class HttpVerifierConfiguration { + + @Bean + public LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper( + LollipopLoggerServiceFactory lollipopLoggerServiceFactory, + HttpMessageVerifierFactory httpMessageVerifierFactory, + IdpCertProviderFactory idpCertProviderFactory, + AssertionServiceFactory assertionServiceFactory, + LollipopConsumerRequestValidationService lollipopConsumerRequestValidationService, + LollipopConsumerRequestConfig lollipopConsumerRequestConfig) { + return new LollipopConsumerFactoryHelper( + lollipopLoggerServiceFactory, + httpMessageVerifierFactory, + idpCertProviderFactory, + assertionServiceFactory, + lollipopConsumerRequestValidationService, + lollipopConsumerRequestConfig); + } + + @Bean + public LollipopConsumerRequestValidationService getLollipopConsumerRequestValidationService( + LollipopConsumerRequestConfig lollipopConsumerRequestConfig) { + return new LollipopConsumerRequestValidationServiceImpl(lollipopConsumerRequestConfig); + } + + @Bean + public LollipopConsumerCommandBuilder lollipopConsumerCommandBuilder( + LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper) { + return new LollipopConsumerCommandBuilderImpl(lollipopConsumerFactoryHelper); + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/controller/DemoController.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/controller/DemoController.java new file mode 100644 index 00000000..ebe7ff5d --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/controller/DemoController.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DemoController { + + @RequestMapping("/") + public @ResponseBody String test() { + return "Test"; + } +} diff --git a/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/utils/SimpleClientsTestUtils.java b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/utils/SimpleClientsTestUtils.java new file mode 100644 index 00000000..94ff3420 --- /dev/null +++ b/servlet-impl/src/test/java/it/pagopa/tech/lollipop/consumer/servlet/utils/SimpleClientsTestUtils.java @@ -0,0 +1,283 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.servlet.utils; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import org.mockserver.client.MockServerClient; +import org.mockserver.model.Header; + +public class SimpleClientsTestUtils { + + public static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + public static final String IDP_CLIENT_RESPONSE_STRING = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + public static final String ASSERTION_REF = "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw"; + public static final String WRONG_ASSERTION_REF = + "sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfXXXXX"; + public static final String JWT = "aValidJWT"; + public static final String VALID_PUBLIC_KEY = + "eyJrdHkiOiJFQyIsIngiOiJTaHlZa0ZyN1F3eE9rOE5BRXF6aklkTnc4dEVKODlZOVBlWFF1eVVOWDVjIiwieSI6InlULVJxNWc2VlVadENUd0ZnRExDM2RneGNuM2RsSmNGRjhnWGdxYWgyS0UiLCJjcnYiOiJQLTI1NiJ9"; + private static final String IDP_TAG = "latest"; + + public static void createExpectationAssertionFound() { + new MockServerClient("localhost", 3000) + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(200).withBody(VALID_ASSERTION_XML)); + } + + public static void createExpectationAssertionNotFound() { + new MockServerClient("localhost", 2000) + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", WRONG_ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(404).withBody("{}")); + } + + public static void createExpectationIdpFound() { + new MockServerClient("localhost", 3001) + .when(request().withMethod("GET").withPath("/idp-keys/spid")) + .respond(response().withStatusCode(200).withBody("[\"" + IDP_TAG + "\"]")); + new MockServerClient("localhost", 3001) + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", IDP_TAG)) + .respond(response().withStatusCode(200).withBody(IDP_CLIENT_RESPONSE_STRING)); + } + + private SimpleClientsTestUtils() {} +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..6cdd7ae8 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,32 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * + * Detailed information about configuring a multi-project build in Gradle can be found + * in the user manual at https://docs.gradle.org/8.0.2/userguide/multi_project_builds.html + */ + +pluginManagement { + repositories { + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("com.gradle.enterprise") version("3.9") +} + +gradleEnterprise { + if (System.getenv("CI") != null) { + buildScan { + publishAlways() + termsOfServiceUrl = "https://gradle.com/terms-of-service" + termsOfServiceAgree = "yes" + } + } +} + +rootProject.name = 'eng-lollipop-consumer-java-sdk' +include 'core', 'assertion-rest-client-native', 'http-verifier', 'identity-service-rest-client-native', 'redis-storage', 'test-coverage', 'spring-impl', 'servlet-impl' diff --git a/spring-impl/README.md b/spring-impl/README.md new file mode 100644 index 00000000..95533606 --- /dev/null +++ b/spring-impl/README.md @@ -0,0 +1,33 @@ +# Lollipop SDK Spring Implementations + +This module contains implementations of general utility of the core SDK, to be used in the context of a Spring +application. + +## Configurations + +An extension of the configuration class within the core module is provided, in order to enable property loading +through the Spring functionalities. Other configurations are expected to be provided from the application using +this specific implementation (See the [spring-sample](../samples/spring) as a reference for a complete setup +of the other classes, as well as the application.properties file to be used). + +## Interceptor Registry Example + +In order to use the provided Http Interceptor it is required to register it in the application context, providing +a configuration for the Spring application to use in order to determine which endpoints should be validated using +the library functionalities + +``` +@Component +public class SampleWebConfigurer implements WebMvcConfigurer { + + @Autowired + private HttpVerifierHandlerInterceptor interceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor) + .addPathPatterns("/") + .pathMatcher(new AntPathMatcher()); + } +} +``` \ No newline at end of file diff --git a/spring-impl/build.gradle b/spring-impl/build.gradle new file mode 100644 index 00000000..408b479e --- /dev/null +++ b/spring-impl/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'java-library' + id("io.freefair.lombok") version "8.0.0" + id 'io.spring.dependency-management' version '1.0.15.RELEASE' +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + } +} + +dependencies { + implementation(project(':core')) { + exclude group: 'ch.qos.logback', module: 'logback-classic' + exclude group: 'ch.qos.logback', module: 'logback-core' + } + implementation 'org.springframework.boot:spring-boot-starter-web:2.7.10' + testImplementation 'org.springframework.boot:spring-boot-starter-test:2.7.10' + testImplementation 'ch.qos.logback:logback-classic:1.2.11' + testImplementation 'ch.qos.logback:logback-core:1.2.11' + testImplementation 'org.codehaus.janino:janino:3.1.9' + testImplementation 'org.slf4j:slf4j-api:1.7.36' + //Mockserver for testing api + testImplementation 'org.mock-server:mockserver-netty:5.15.0' + testImplementation project(':http-verifier') + testImplementation project(':assertion-rest-client-native') + testImplementation project(':identity-service-rest-client-native') +} + +tasks.named('test') { + // Use JUnit Platform for unit tests. + useJUnitPlatform() +} diff --git a/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/HttpVerifierHandlerInterceptor.java b/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/HttpVerifierHandlerInterceptor.java new file mode 100644 index 00000000..37ac688c --- /dev/null +++ b/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/HttpVerifierHandlerInterceptor.java @@ -0,0 +1,58 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring; + +import static it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandImpl.VERIFICATION_SUCCESS_CODE; + +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommand; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.model.CommandResult; +import it.pagopa.tech.lollipop.consumer.utils.LollipopConsumerConverter; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.web.servlet.HandlerInterceptor; + +/** + * Instance of a Spring Http {@link HandlerInterceptor}, to be used for Lollipop Request validations + */ +@AllArgsConstructor +public class HttpVerifierHandlerInterceptor implements HandlerInterceptor { + + private final LollipopConsumerCommandBuilder consumerCommandBuilder; + private static final Log log = LogFactory.getLog(HttpVerifierHandlerInterceptor.class); + + /** + * @param request current HTTP request + * @param response current HTTP response + * @param handler chosen handler to execute, for type and/or instance evaluation + * @return boolean to determine if the handle completes successfully + * @throws IOException throws exception if the conversion of a http request fails + */ + @Override + public boolean preHandle( + HttpServletRequest request, HttpServletResponse response, Object handler) + throws IOException { + + LollipopConsumerCommand lollipopConsumerCommand = + consumerCommandBuilder.createCommand( + LollipopConsumerConverter.convertToLollipopRequest(request)); + + try { + CommandResult commandResult = lollipopConsumerCommand.doExecute(); + + LollipopConsumerConverter.interceptResult(commandResult, response); + + if (commandResult.getResultCode().equals(VERIFICATION_SUCCESS_CODE)) { + return true; + } + + } catch (Exception e) { + log.error("Error verifying request", e); + } + + return false; + } +} diff --git a/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/config/HttpVerifierConfiguration.java b/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/config/HttpVerifierConfiguration.java new file mode 100644 index 00000000..7b60deb9 --- /dev/null +++ b/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/config/HttpVerifierConfiguration.java @@ -0,0 +1,59 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring.config; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.command.LollipopConsumerCommandBuilder; +import it.pagopa.tech.lollipop.consumer.command.impl.LollipopConsumerCommandBuilderImpl; +import it.pagopa.tech.lollipop.consumer.helper.LollipopConsumerFactoryHelper; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.service.LollipopConsumerRequestValidationService; +import it.pagopa.tech.lollipop.consumer.service.impl.LollipopConsumerRequestValidationServiceImpl; +import it.pagopa.tech.lollipop.consumer.spring.HttpVerifierHandlerInterceptor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Instance of Spring configuration of the core elements, the implementations of the related + * services are delegated to external configurations + */ +@Configuration +public class HttpVerifierConfiguration { + + @Bean + public LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper( + LollipopLoggerServiceFactory lollipopLoggerServiceFactory, + HttpMessageVerifierFactory httpMessageVerifierFactory, + IdpCertProviderFactory idpCertProviderFactory, + AssertionServiceFactory assertionServiceFactory, + LollipopConsumerRequestValidationService lollipopConsumerRequestValidationService, + SpringLollipopConsumerRequestConfig springLollipopConsumerRequestConfig) { + return new LollipopConsumerFactoryHelper( + lollipopLoggerServiceFactory, + httpMessageVerifierFactory, + idpCertProviderFactory, + assertionServiceFactory, + lollipopConsumerRequestValidationService, + springLollipopConsumerRequestConfig); + } + + @Bean + public LollipopConsumerRequestValidationService getLollipopConsumerRequestValidationService( + SpringLollipopConsumerRequestConfig springLollipopConsumerRequestConfig) { + return new LollipopConsumerRequestValidationServiceImpl( + springLollipopConsumerRequestConfig); + } + + @Bean + public LollipopConsumerCommandBuilder lollipopConsumerCommandBuilder( + LollipopConsumerFactoryHelper lollipopConsumerFactoryHelper) { + return new LollipopConsumerCommandBuilderImpl(lollipopConsumerFactoryHelper); + } + + @Bean + public HttpVerifierHandlerInterceptor httpVerifierHandlerInterceptor( + LollipopConsumerCommandBuilder lollipopConsumerCommandBuilder) { + return new HttpVerifierHandlerInterceptor(lollipopConsumerCommandBuilder); + } +} diff --git a/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/config/SpringLollipopConsumerRequestConfig.java b/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/config/SpringLollipopConsumerRequestConfig.java new file mode 100644 index 00000000..fc19f254 --- /dev/null +++ b/spring-impl/src/main/java/it/pagopa/tech/lollipop/consumer/spring/config/SpringLollipopConsumerRequestConfig.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring.config; + +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; + +/** Spring instance of the {@link LollipopConsumerRequestConfig} */ +@ConfigurationProperties(prefix = "lollipop.core.config") +@ConfigurationPropertiesScan +@NoArgsConstructor +@Data +public class SpringLollipopConsumerRequestConfig extends LollipopConsumerRequestConfig {} diff --git a/spring-impl/src/main/resources/logback-spring.xml b/spring-impl/src/main/resources/logback-spring.xml new file mode 100644 index 00000000..36316dda --- /dev/null +++ b/spring-impl/src/main/resources/logback-spring.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + + + + true + 20000 + 0 + + + + + + + + \ No newline at end of file diff --git a/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/MockAssertionVerifierService.java b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/MockAssertionVerifierService.java new file mode 100644 index 00000000..f082955d --- /dev/null +++ b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/service/impl/MockAssertionVerifierService.java @@ -0,0 +1,29 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.service.impl; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionService; +import it.pagopa.tech.lollipop.consumer.config.LollipopConsumerRequestConfig; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProvider; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerService; +import it.pagopa.tech.lollipop.consumer.model.IdpCertData; +import java.util.List; +import org.w3c.dom.Document; + +public class MockAssertionVerifierService extends AssertionVerifierServiceImpl { + + public MockAssertionVerifierService( + IdpCertProvider idpCertProvider, + AssertionService assertionService, + LollipopConsumerRequestConfig lollipopRequestConfig) { + super( + new LollipopLogbackLoggerService(), + idpCertProvider, + assertionService, + lollipopRequestConfig); + } + + @Override + protected boolean validateSignature(Document assertionDoc, List idpCertDataList) { + return true; + } +} diff --git a/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/DemoApplication.java b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/DemoApplication.java new file mode 100644 index 00000000..2608818e --- /dev/null +++ b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/DemoApplication.java @@ -0,0 +1,13 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication(scanBasePackages = "it.pagopa.tech") +public class DemoApplication { + + public static void main(String[] args) { + SpringApplication.run(DemoApplication.class, args); + } +} diff --git a/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/HttpVerifierHandlerInterceptorIntegrationTest.java b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/HttpVerifierHandlerInterceptorIntegrationTest.java new file mode 100644 index 00000000..76282702 --- /dev/null +++ b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/HttpVerifierHandlerInterceptorIntegrationTest.java @@ -0,0 +1,206 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring; + +import static it.pagopa.tech.lollipop.consumer.spring.SimpleClientsTestUtils.*; +import static org.mockserver.integration.ClientAndServer.startClientAndServer; + +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.spring.config.HttpVerifierConfiguration; +import it.pagopa.tech.lollipop.consumer.spring.config.SpringLollipopConsumerRequestConfig; +import java.io.IOException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockserver.integration.ClientAndServer; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.client.RestTemplate; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@ContextConfiguration( + classes = { + DemoApplication.class, + SpringLollipopConsumerRequestConfig.class, + HttpVerifierConfiguration.class, + }) +public class HttpVerifierHandlerInterceptorIntegrationTest { + + @LocalServerPort private int port; + @Autowired private TestRestTemplate restTemplate; + @Autowired private SpringLollipopConsumerRequestConfig springLollipopConsumerRequestConfig; + @Autowired private HttpVerifierHandlerInterceptor interceptor; + @Autowired private IdpCertSimpleClientConfig idpCertSimpleClientConfig; + + private static ClientAndServer mockServer; + + private static final String CONTENT_DIGEST = + "sha-256=:cpyRqJ1VhoVC+MSs9fq4/4wXs4c46EyEFriskys43Zw=:"; + private static final String USER_ID = "GDNNWA12H81Y874F"; + private static final String SIGNATURE_INPUT = + "sig123=(\"content-digest\" \"x-pagopa-lollipop-original-method\"" + + " \"x-pagopa-lollipop-original-url\");created=1678293988;nonce=\"aNonce\";alg=\"ecdsa-p256-sha256\";keyid=\"sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfcI-Dg\""; + private static final String SIGNATURE = + "sig123=:6scl8sMzJdyG/OrnJXHRM9ajmIjrJ/zrLUDqvfOxj2h51DUKztTua3vR1kSUj/c/VT1ioDlt1QIMARABhquewg==:"; + + @BeforeAll + public static void startServer() { + mockServer = startClientAndServer(3000, 3001); + } + + @Test + void testWithValidRequestReturnsSuccess() throws IOException { + SimpleClientsTestUtils.createExpectationAssertionFound(); + SimpleClientsTestUtils.createExpectationIdpFound(); + springLollipopConsumerRequestConfig.setAssertionNotBeforeDateFormat( + "yyyy-MM-dd'T'HH:mm:ss'Z'"); + springLollipopConsumerRequestConfig.setAssertionInstantDateFormat( + "yyyy-MM-dd'T'HH:mm:ss'Z'"); + idpCertSimpleClientConfig.setBaseUri("http://localhost:3001"); + + RestTemplate exec = restTemplate.getRestTemplate(); + exec.getClientHttpRequestInitializers() + .add( + request -> { + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getContentDigestHeader(), + CONTENT_DIGEST); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getOriginalURLHeader(), + springLollipopConsumerRequestConfig + .getExpectedFirstLcOriginalUrl()); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getOriginalMethodHeader(), + springLollipopConsumerRequestConfig + .getExpectedFirstLcOriginalMethod()); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getPublicKeyHeader(), + VALID_PUBLIC_KEY); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getAssertionRefHeader(), + ASSERTION_REF); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getAssertionTypeHeader(), + "SAML"); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig.getAuthJWTHeader(), + JWT); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig.getUserIdHeader(), + USER_ID); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getSignatureInputHeader(), + SIGNATURE_INPUT); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getSignatureHeader(), + SIGNATURE); + }); + + ResponseEntity response = + exec.postForEntity( + "http://localhost:" + port, + "{\"message\":\"a valid message payload\"}", + String.class); + Assertions.assertNotNull(response); + Assertions.assertEquals(200, response.getStatusCodeValue()); + } + + @Test + void testWithInvalidPayloadRequestReturnsUnauthorized() throws IOException { + SimpleClientsTestUtils.createExpectationAssertionFound(); + + RestTemplate exec = restTemplate.getRestTemplate(); + exec.getClientHttpRequestInitializers() + .add( + request -> { + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getContentDigestHeader(), + CONTENT_DIGEST); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getOriginalURLHeader(), + springLollipopConsumerRequestConfig + .getExpectedFirstLcOriginalUrl()); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getOriginalMethodHeader(), + springLollipopConsumerRequestConfig + .getExpectedFirstLcOriginalMethod()); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getPublicKeyHeader(), + VALID_PUBLIC_KEY); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getAssertionRefHeader(), + ASSERTION_REF); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getAssertionTypeHeader(), + "SAML"); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig.getAuthJWTHeader(), + JWT); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig.getUserIdHeader(), + USER_ID); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getSignatureInputHeader(), + SIGNATURE_INPUT); + request.getHeaders() + .add( + springLollipopConsumerRequestConfig + .getSignatureHeader(), + SIGNATURE); + }); + + ResponseEntity response = + exec.postForEntity( + "http://localhost:" + port, + "{\"message\":\"an invalid message payload\"}", + String.class); + Assertions.assertNotNull(response); + Assertions.assertEquals(401, response.getStatusCodeValue()); + } + + @AfterAll + public static void stopServer() { + mockServer.stop(); + } +} diff --git a/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/SimpleClientsTestUtils.java b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/SimpleClientsTestUtils.java new file mode 100644 index 00000000..42b69912 --- /dev/null +++ b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/SimpleClientsTestUtils.java @@ -0,0 +1,283 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring; + +import static org.mockserver.model.HttpRequest.request; +import static org.mockserver.model.HttpResponse.response; + +import org.mockserver.client.MockServerClient; +import org.mockserver.model.Header; + +public class SimpleClientsTestUtils { + + public static final String VALID_ASSERTION_XML = + "{\"response_xml\": \"\\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>s\\/DqYePHC7eCXX5ncsiFYNLyKbzS6P5C8331H1b8e30=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WcasorooElvhmK0kxFUdBVCqyRYi0SCNGRSZZnC9Q2sZHOYGZbERe4\\/T8OSuRKbSrEivXIHRgNr8WskZTM2CiywfWChHfGvhERsLuPJE8oh9CR3eicX\\/eg0ynJqwx4IoYhTb2NOwqMFc66nnutMhG\\/Smdtjs4SFz0RQYYVeZ5Ho51iTHd94uBV9ZHXjqcvs3EitUsJ0Zg1Pkw352tt8y7niUcGjAd8nydI72S12sF5ePv05AunFp7vZpYbKqi62fQLORCn1ZP7WKFD75hL0bCvZaSRF285GkfSnLfe1S4tLff2SlTQWevOMU\\/wCkHJmQwHT1LMwcWRnMvv4V+vd1XQ==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " \\n" + + " <\\/samlp:Status>\\n" + + " \\n" + + " https:\\/\\/spid-testenv2:8088<\\/saml:Issuer><\\/ds:Transforms>4cqgG29TSKgNLy2\\/1eFPXhd5WRVPxZBGcd8DgTvd5Fo=<\\/ds:DigestValue><\\/ds:Reference><\\/ds:SignedInfo>WqxM+y+vtZDcEaIaw2WfcuMuwXUeTOY9ZjaXwzHw+RE8uUr5s8BE1tpaodcKmmqSJK1JQYNr8AUV+W9V79EKfmNtFvfaf0WYeUee7Td7E24QqiyVHjr1YgfDWhSdItFLYJfQUkotj2BepbdwVQGY5yN0Rw6Fq98hgNOgsxty7g6oqxG1OXB4WJ2He20iOoYWQl8ApxlbU\\/\\/hwnefFYe9ghDPy3rDbcNl3JetT07NR\\/+AzhKH4e+JCwKjTkdCBTW30fK4eiV9yBk74Lobip4hMaQhMaByl8egaU3A8AsnsZQuov2B6Wo2sDiQPjIulb8K3DOwFyL8PzEk8BB5YoAfwg==<\\/ds:SignatureValue>MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR\\/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2\\/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI\\/sp\\n" + + "ILywgDxVMMtv\\/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAf\\n" + + "BgNVHSMEGDAWgBQEVmzA\\/L1\\/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH\\/MA0G\\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8\\/o13vMw4feGxro1hMeUilRtH52funrW\\n" + + "C+FgPrqk3o\\/8cZOnq+CqnFFDfILLiEb\\/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\\n" + + "Da1fG\\/Pi0fG2F0yw\\/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk\\/ZbEHdKcofnziDyl0V8gglP2\\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\\n" + + "<\\/ds:X509Certificate><\\/ds:X509Data><\\/ds:KeyInfo><\\/ds:Signature>\\n" + + " \\n" + + " id_48129c2a9d5e9077422591baf495747cfda668c5<\\/saml:NameID>\\n" + + " \\n" + + " \\n" + + " <\\/saml:SubjectConfirmation>\\n" + + " <\\/saml:Subject>\\n" + + " \\n" + + " \\n" + + " https:\\/\\/spid.agid.gov.it\\/cd<\\/saml:Audience>\\n" + + " <\\/saml:AudienceRestriction>\\n" + + " <\\/saml:Conditions>\\n" + + " \\n" + + " \\n" + + " " + + " https:\\/\\/www.spid.gov.it\\/SpidL2<\\/saml:AuthnContextClassRef>\\n" + + " <\\/saml:AuthnContext>\\n" + + " <\\/saml:AuthnStatement>\\n" + + " \\n" + + " \\n" + + " info@agid.gov.it<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Mario<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " Bianchi<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " GDNNWA12H81Y874F<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " \\n" + + " 1991-12-12<\\/saml:AttributeValue>\\n" + + " <\\/saml:Attribute>\\n" + + " <\\/saml:AttributeStatement>\\n" + + " <\\/saml:Assertion>\\n" + + "<\\/samlp:Response>\"}"; + public static final String IDP_CLIENT_RESPONSE_STRING = + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " " + + " MIIC7TCCAdWgAwIBAgIJAMbxPOoBth1LMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV\n" + + "BAYTAklUMB4XDTE4MDkwNDE0MDAxM1oXDTE4MTAwNDE0MDAxM1owDTELMAkGA1UE\n" + + "BhMCSVQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDJrW3y8Zd2jESP\n" + + "XGMRY04cHC4Qfo3302HEY1C6x1aDfW7aR/tXzNplfdw8ZtZugSSmHZBxVrR8aA08\n" + + "dUVbbtUw5qD0uAWKIeREqGfhM+J1STAMSI2/ZxA6t2fLmv8l1eRd1QGeRDm7yF9E\n" + + "EKGY9iUZD3LJf2mWdVBAzzYlG23M769k+9JuGZxuviNWMjojgYRiQFgzypUJJQz+\n" + + "Ihh3q7LMjjiQiiULVb9vnJg7UdU9Wf3xGRkxk6uiGP9SzWigSObUekYYQ4ZAI/sp\n" + + "ILywgDxVMMtv/eVniUFKLABtljn5cE9zltECahPbm7wIuMJpDDu5GYHGdYO0j+K7\n" + + "fhjvF2mzAgMBAAGjUDBOMB0GA1UdDgQWBBQEVmzA/L1/fd70ok+6xtDRF8A3HjAf\n" + + "BgNVHSMEGDAWgBQEVmzA/L1/fd70ok+6xtDRF8A3HjAMBgNVHRMEBTADAQH/MA0G\n" + + "CSqGSIb3DQEBCwUAA4IBAQCRMo4M4PqS0iLTTRWfikMF4hYMapcpmuna6p8aee7C\n" + + "wTjS5y7y18RLvKTi9l8OI0dVkgokH8fq8/o13vMw4feGxro1hMeUilRtH52funrW\n" + + "C+FgPrqk3o/8cZOnq+CqnFFDfILLiEb/PVJMddvTXgv2f9O6u17f8GmMLzde1yvY\n" + + "Da1fG/Pi0fG2F0yw/CmtP8OTLSvxjPtJ+ZckGzZa9GotwHsoVJ+Od21OU2lOeCnO\n" + + "jJOAbewHgqwkCB4O4AT5RM4ThAQtoU8QibjD1XDk/ZbEHdKcofnziDyl0V8gglP2\n" + + "SxpzDaPX0hm4wgHk9BOtSikb72tfOw+pNfeSrZEr6ItQ\n" + + "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " urn:oasis:names:tc:SAML:2.0:nameid-format:transient\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + "\n"; + public static final String ASSERTION_REF = "sha256-chG21HBOK-wJp2hHuYPrx7tAII2UGWVF-IFo0crUOtw"; + public static final String WRONG_ASSERTION_REF = + "sha256-a7qE0Y0DyqeOFFREIQSLKfu5WlbckdxVXKFasfXXXXX"; + public static final String JWT = "aValidJWT"; + public static final String VALID_PUBLIC_KEY = + "eyJrdHkiOiJFQyIsIngiOiJTaHlZa0ZyN1F3eE9rOE5BRXF6aklkTnc4dEVKODlZOVBlWFF1eVVOWDVjIiwieSI6InlULVJxNWc2VlVadENUd0ZnRExDM2RneGNuM2RsSmNGRjhnWGdxYWgyS0UiLCJjcnYiOiJQLTI1NiJ9"; + private static final String IDP_TAG = "latest"; + + public static void createExpectationAssertionFound() { + new MockServerClient("localhost", 3000) + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(200).withBody(VALID_ASSERTION_XML)); + } + + public static void createExpectationAssertionNotFound() { + new MockServerClient("localhost", 2000) + .when( + request() + .withMethod("GET") + .withPath("/assertions/{assertion}") + .withPathParameter("assertion", WRONG_ASSERTION_REF) + .withHeaders( + new Header("Accept", "application/json"), + new Header("x-pagopa-lollipop-auth", JWT))) + .respond(response().withStatusCode(404).withBody("{}")); + } + + public static void createExpectationIdpFound() { + new MockServerClient("localhost", 3001) + .when(request().withMethod("GET").withPath("/idp-keys/spid")) + .respond(response().withStatusCode(200).withBody("[\"" + IDP_TAG + "\"]")); + new MockServerClient("localhost", 3001) + .when( + request() + .withMethod("GET") + .withPath("/idp-keys/spid/{tag}") + .withPathParameter("tag", IDP_TAG)) + .respond(response().withStatusCode(200).withBody(IDP_CLIENT_RESPONSE_STRING)); + } + + private SimpleClientsTestUtils() {} +} diff --git a/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/config/DemoServicesConfig.java b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/config/DemoServicesConfig.java new file mode 100644 index 00000000..c6ed5cbc --- /dev/null +++ b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/config/DemoServicesConfig.java @@ -0,0 +1,67 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring.config; + +import it.pagopa.tech.lollipop.consumer.assertion.AssertionServiceFactory; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.assertion.client.simple.AssertionSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.assertion.impl.AssertionServiceFactoryImpl; +import it.pagopa.tech.lollipop.consumer.assertion.storage.SimpleAssertionStorageProvider; +import it.pagopa.tech.lollipop.consumer.assertion.storage.StorageConfig; +import it.pagopa.tech.lollipop.consumer.http_verifier.HttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.http_verifier.visma.VismaHttpMessageVerifierFactory; +import it.pagopa.tech.lollipop.consumer.idp.IdpCertProviderFactory; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientConfig; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.IdpCertSimpleClientProvider; +import it.pagopa.tech.lollipop.consumer.idp.client.simple.storage.SimpleIdpCertStorageProvider; +import it.pagopa.tech.lollipop.consumer.idp.impl.IdpCertProviderFactoryImpl; +import it.pagopa.tech.lollipop.consumer.idp.storage.IdpCertStorageConfig; +import it.pagopa.tech.lollipop.consumer.logger.LollipopLoggerServiceFactory; +import it.pagopa.tech.lollipop.consumer.logger.impl.LollipopLogbackLoggerServiceFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class DemoServicesConfig { + + @Bean + public LollipopLoggerServiceFactory lollipopLoggerServiceFactory() { + return new LollipopLogbackLoggerServiceFactory(); + } + + @Bean + public SpringLollipopConsumerRequestConfig verifierConfiguration() { + return new SpringLollipopConsumerRequestConfig(); + } + + @Bean + public HttpMessageVerifierFactory httpMessageVerifierFactory() throws Exception { + return new VismaHttpMessageVerifierFactory("UTF-8", verifierConfiguration()); + } + + @Bean + public IdpCertProviderFactory idpCertProviderFactory() { + return new IdpCertProviderFactoryImpl( + new IdpCertSimpleClientProvider( + idpCertSimpleClientConfig(), + new SimpleIdpCertStorageProvider(), + new IdpCertStorageConfig())); + } + + @Bean + public AssertionServiceFactory assertionServiceFactory() { + return new AssertionServiceFactoryImpl( + new SimpleAssertionStorageProvider(), + new AssertionSimpleClientProvider(AssertionSimpleClientConfig.builder().build()), + storageConfig()); + } + + @Bean + public StorageConfig storageConfig() { + return new StorageConfig(); + } + + @Bean + public IdpCertSimpleClientConfig idpCertSimpleClientConfig() { + return IdpCertSimpleClientConfig.builder().build(); + } +} diff --git a/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/config/DemoWebConfigurer.java b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/config/DemoWebConfigurer.java new file mode 100644 index 00000000..c45f4d01 --- /dev/null +++ b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/config/DemoWebConfigurer.java @@ -0,0 +1,31 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring.config; + +import it.pagopa.tech.lollipop.consumer.spring.HttpVerifierHandlerInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.CommonsRequestLoggingFilter; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Component +public class DemoWebConfigurer implements WebMvcConfigurer { + + @Autowired private HttpVerifierHandlerInterceptor interceptor; + + @Bean + public CommonsRequestLoggingFilter loggingFilter() { + CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter(); + filter.setIncludeQueryString(true); + filter.setIncludeClientInfo(true); + filter.setIncludeHeaders(true); + filter.setIncludePayload(true); + return filter; + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(interceptor); + } +} diff --git a/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/controller/DemoController.java b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/controller/DemoController.java new file mode 100644 index 00000000..3b8cb50d --- /dev/null +++ b/spring-impl/src/test/java/it/pagopa/tech/lollipop/consumer/spring/controller/DemoController.java @@ -0,0 +1,15 @@ +/* (C)2023 */ +package it.pagopa.tech.lollipop.consumer.spring.controller; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class DemoController { + + @RequestMapping("/") + public @ResponseBody String test() { + return "Test"; + } +} diff --git a/test-coverage/build.gradle b/test-coverage/build.gradle new file mode 100644 index 00000000..6f9ebeaa --- /dev/null +++ b/test-coverage/build.gradle @@ -0,0 +1,49 @@ +plugins { + id 'base' + id 'jacoco-report-aggregation' +} + +repositories { + mavenLocal() + mavenCentral() + maven { + url = uri("https://maven.pkg.github.com/pagopa/eng-http-signatures") + credentials { + username = project.findProperty("gpr.user") ?: System.getenv("GITHUB_ACTOR") + password = project.findProperty("gpr.key") ?: System.getenv("GITHUB_TOKEN") + } + }} + +dependencies { + jacocoAggregation project(':http-verifier') + jacocoAggregation project(':identity-service-rest-client-native') + jacocoAggregation project(':assertion-rest-client-native') + jacocoAggregation project(':spring-impl') +} + +reporting { + reports { + testCodeCoverageReport(JacocoCoverageReport) { + testType = TestSuiteType.UNIT_TEST + } + } +} + +tasks.named('check') { + dependsOn tasks.named('testCodeCoverageReport', JacocoReport) +} + +testCodeCoverageReport { + getClassDirectories().setFrom(files( + [project(':http-verifier'), + project(':identity-service-rest-client-native'), + project(':assertion-rest-client-native'), + project(':core'), + project(':spring-impl') + ].collect { + it.fileTree(dir: "${it.buildDir}/classes", exclude: [ + "**/config/*","**/*Mock*","**/model/**","**/entity/*","**/*Stub*","**/*Config*","**/*Exception*" + ]) + } + )) +} diff --git a/test-coverage/src/main/resources/dummydata b/test-coverage/src/main/resources/dummydata new file mode 100644 index 00000000..e69de29b diff --git a/test-coverage/src/test/resources/dummydata b/test-coverage/src/test/resources/dummydata new file mode 100644 index 00000000..e69de29b