diff --git a/.github/workflows/ci_e2e.yaml b/.github/workflows/ci_e2e.yaml new file mode 100644 index 00000000..ff815d4c --- /dev/null +++ b/.github/workflows/ci_e2e.yaml @@ -0,0 +1,147 @@ +name: E2E Tests + +on: + pull_request + +jobs: + + extract_metadata: + runs-on: ubuntu-latest + name: Extract supported_features + outputs: + supported-features: ${{ steps.supported-features.outputs.value }} + steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + - name: extract supported features + id: supported-features + run: echo "value=$(node -p -e "require('./px_metadata.json').supported_features?.join(' or ') || ''")" >> "$GITHUB_OUTPUT" + + + CI: + name: "E2E tests" + env: + MOCK_COLLECTOR_IMAGE_TAG: 1.3.5 + SAMPLE_SITE_IMAGE_TAG: 1.0.0 + ENFORCER_SPEC_TESTS_IMAGE_TAG: 1.8.1 + + runs-on: ubuntu-latest + timeout-minutes: 60 + needs: + - extract_metadata + + steps: + + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Set up Docker + uses: docker/setup-buildx-action@v3 + + - name: Build local cluster + run: ./ci_files/build_cluster.sh + + - name: Build Enforcer Docker image + run: | + docker build . -t localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG && \ + docker push localhost:5001/java-enforcer-sample-site:$SAMPLE_SITE_IMAGE_TAG + + - uses: azure/setup-helm@v3 + with: + version: '3.14.1' + + - name: Clone helm charts repo - mock-collector + uses: actions/checkout@v4 + with: + repository: PerimeterX/connect-helm-charts + token: ${{ secrets.CONNECT_PULL_TOKEN }} + ref: mock-collector-0.1.1 + path: ./deploy_charts/mock-collector + + + - name: Clone helm charts repo - enforcer-tests + uses: actions/checkout@v4 + with: + repository: PerimeterX/connect-helm-charts + token: ${{ secrets.CONNECT_PULL_TOKEN }} + ref: enforcer-spec-tests-0.7.1 + path: ./deploy_charts/enforcer-spec-tests + + + - name: Clone helm charts repo - sample-site + uses: actions/checkout@v4 + with: + repository: PerimeterX/connect-helm-charts + token: ${{ secrets.CONNECT_PULL_TOKEN }} + ref: sample-site-0.5.0 + path: ./deploy_charts/sample-site + + - name: Set up Google Cloud SDK + id: 'auth' + uses: 'google-github-actions/auth@v2' + with: + credentials_json: '${{ secrets.GCR_SA_KEY }}' + + - name: Configure Docker credentials + run: | + gcloud auth configure-docker gcr.io + + - name: pull mock collector image + run: | + docker pull gcr.io/px-docker-repo/connecteam/mock-collector:$MOCK_COLLECTOR_IMAGE_TAG && \ + docker tag gcr.io/px-docker-repo/connecteam/mock-collector:$MOCK_COLLECTOR_IMAGE_TAG localhost:5001/mock-collector:$MOCK_COLLECTOR_IMAGE_TAG && \ + docker push localhost:5001/mock-collector:$MOCK_COLLECTOR_IMAGE_TAG + + - name: deploy mock collector + run: | + helm install mock-collector ./deploy_charts/mock-collector/charts/mock-collector \ + --set image.repository=localhost:5001/mock-collector \ + --set image.tag=$MOCK_COLLECTOR_IMAGE_TAG \ + --set imagePullPolicy=Always --wait + + - name: set secrets in enforcer config + run: | + cat ./ci_files/enforcer-config.json |\ + jq '.px_app_id="${{ secrets.PX_APP_ID }}"' |\ + jq '.px_cookie_secret="${{ secrets.TEST_COOKIE_SECRET }}"' |\ + jq '.px_auth_token="${{ secrets.PX_AUTH_TOKEN }}"' > /tmp/enforcer-config.json + + - name: log enforcer config + run: cat /tmp/enforcer-config.json + + - name: deploy java enforcer + run: | + helm install java-enforcer ./deploy_charts/sample-site/charts/sample-site \ + -f ./ci_files/enforcer-values.yaml \ + --set image.name=localhost:5001/java-enforcer-sample-site \ + --set image.tag=$SAMPLE_SITE_IMAGE_TAG \ + --set-file enforcerConfig.content=/tmp/enforcer-config.json \ + --wait + + - name: pull enforcer tests image + run: | + docker pull gcr.io/px-docker-repo/connecteam/enforcer-specs-tests:$ENFORCER_SPEC_TESTS_IMAGE_TAG && \ + docker tag gcr.io/px-docker-repo/connecteam/enforcer-specs-tests:$ENFORCER_SPEC_TESTS_IMAGE_TAG localhost:5001/enforcer-spec-tests:$ENFORCER_SPEC_TESTS_IMAGE_TAG && \ + docker push localhost:5001/enforcer-spec-tests:$ENFORCER_SPEC_TESTS_IMAGE_TAG + + - name: run enforcer tests + run: | + helm install enforcer-spec-tests ./deploy_charts/enforcer-spec-tests/charts/enforcer-spec-tests \ + --set image.tag=$ENFORCER_SPEC_TESTS_IMAGE_TAG \ + --set cookieSecret=${{ secrets.TEST_COOKIE_SECRET }} \ + --set supportedFeatures="${{ needs.extract_metadata.outputs.supported-features }}" \ + --set authToken="${{ secrets.PX_AUTH_TOKEN }}" \ + --set appId=${{ secrets.PX_APP_ID }} \ + --set-file enforcerMetadataContent=./px_metadata.json \ + -f ./ci_files/spec-tests-values.yaml \ + --wait \ + --timeout 60m0s \ + --wait-for-jobs + + - name: get tests results + if: ${{ always() }} + run: kubectl logs job/enforcer-spec-tests diff --git a/.github/workflows/ci_verify_version.yaml b/.github/workflows/ci_verify_version.yaml new file mode 100644 index 00000000..9fcd468a --- /dev/null +++ b/.github/workflows/ci_verify_version.yaml @@ -0,0 +1,44 @@ +name: Verify version +on: + pull_request: + branches: + - master +jobs: + verify-version: + name: Verify version + runs-on: ubuntu-latest + steps: + - name: Checkout code - ${{ github.base_ref }} + uses: actions/checkout@v4 + with: + ref: ${{ github.base_ref }} + + - name: Get ${{ github.base_ref }} SDK version + id: base-version + run: echo "project=$( mvn help:evaluate -Dexpression=project.version -q -DforceStdout )" >> "$GITHUB_OUTPUT" + + - name: Checkout code - current commit + uses: actions/checkout@v4 + + - name: Get current SDK versions + id: new-version + run: | + echo "project=$( mvn help:evaluate -Dexpression=project.version -q -DforceStdout )" >> "$GITHUB_OUTPUT" && \ + echo "px_metadata=$( cat px_metadata.json | jq -r '.version' )" >> "$GITHUB_OUTPUT" && \ + echo "demo_app_dependency=$( mvn help:evaluate -Dexpression=com.perimeterx.version -q -DforceStdout -f web/pom.xml)" >> "$GITHUB_OUTPUT" + + - name: Verify same version + run: | + [ $PROJECT_VERSION = $PX_METADATA_VERSION ] && \ + [ $PROJECT_VERSION = $DEMO_APP_DEPENDENCY_VERSION ] + env: + PROJECT_VERSION: ${{ steps.new-version.outputs.project }} + PX_METADATA_VERSION: ${{ steps.new-version.outputs.px_metadata }} + DEMO_APP_DEPENDENCY_VERSION: ${{ steps.new-version.outputs.demo_app_dependency }} + + - name: Verify version increment + run: ./ci_files/verify-version-inc.sh $BASE_VERSION $NEW_VERSION + env: + NEW_VERSION: ${{ steps.new-version.outputs.project }} + BASE_VERSION: ${{ steps.base-version.outputs.project }} + diff --git a/CHANGELOG.md b/CHANGELOG.md index c3c26562..aefb1899 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Change Log +## [vX.XX.X](https://github.com/PerimeterX/perimeterx-java-sdk/compare/X.XX.X...HEAD) (YYYY-MM-DD) +- Added Dockerfile for web application example. +- Bugfix - Sensitive headers are now case-insensitive. +- Block page HTML align with spec. +- Automatically running e2e tests on pull request. +- Automatically verify version on pull request to master. + ## [v6.11.0](https://github.com/PerimeterX/perimeterx-java-sdk/compare/6.11.0...HEAD) (2024-02-18) * Added base64-encoded request http method to captcha script query parameters on block pages diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e1ab3293 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM maven:3.8.6-openjdk-11-slim as builder +WORKDIR /app + +# Building the SDK. +COPY pom.xml . +COPY src/main/resources src/main/resources +RUN mvn verify clean -f pom.xml +COPY src ./src +RUN mvn clean install -DskipTests=true + +# Building the Demo app. +COPY web/pom.xml web/pom.xml +RUN mvn verify clean -f web/pom.xml +COPY web ./web +RUN mvn clean install war:war -DskipTests=true -f web/pom.xml + +FROM tomcat:9.0.68 + +COPY --from=builder /app/web/target/web-1.0.0 /usr/local/tomcat/webapps/ROOT + +## Enforcer configuration json file is located at: +## /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/src/main/resources/enforcer_config.json +COPY web/src/main/resources/ /usr/local/tomcat/webapps/ROOT/WEB-INF/classes/src/main/resources + +EXPOSE 8080 + +ENV CATALINA_OPTS="-Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true" + +CMD ["catalina.sh", "run"] \ No newline at end of file diff --git a/ci_files/build_cluster.sh b/ci_files/build_cluster.sh new file mode 100755 index 00000000..429bcc09 --- /dev/null +++ b/ci_files/build_cluster.sh @@ -0,0 +1,65 @@ +#!/bin/sh +set -o errexit + + +# 2. Create registry container unless it already exists +reg_name='kind-registry' +reg_port='5001' +if [ "$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" != 'true' ]; then + docker run \ + -d --restart=always -p "127.0.0.1:${reg_port}:5000" --name "${reg_name}" \ + registry:2 +fi + +# 3. Create kind cluster with containerd registry config dir enabled +# extraPortMapping - for mapping traffic from outside the cluster to the localstack inside the cluster. +# by that, every traffic to localhost:4566 will arrive to the cluster and will be map inside the cluster to port 31566 - localstack. +cat < sensitiveRoutes, String uri } public String getCollectorURL() { - return String.format("%s%s%s", Constants.API_COLLECTOR_PREFIX, appId, Constants.API_COLLECTOR_POSTFIX); + return pxConfiguration.getCollectorUrl(); } public void setCookieVersion(String cookieVersion) { diff --git a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java index 4443f37f..a17fc9ca 100644 --- a/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java +++ b/src/main/java/com/perimeterx/models/configuration/PXConfiguration.java @@ -38,6 +38,7 @@ import java.util.Set; import java.util.function.Function; import java.util.function.Predicate; +import java.util.stream.Collectors; import static com.perimeterx.utils.Constants.*; @@ -87,7 +88,7 @@ public static void setPxLoggerSeverity(LoggerSeverity severity) { @Builder.Default @JsonProperty("px_sensitive_headers") - private Set sensitiveHeaders = new HashSet<>(Arrays.asList("cookieOrig", "cookies")); + private Set sensitiveHeaders = new HashSet<>(Arrays.asList("cookieorig", "cookies")); @Builder.Default @JsonProperty("px_max_buffer_length") @@ -404,5 +405,19 @@ public PXConfigurationBuilder appId(String appId) { } return this; } + + public PXConfigurationBuilder sensitiveHeaders(Set headers) { + if (headers == null) { + this.sensitiveHeaders$value = new HashSet<>(); + } else { + this.sensitiveHeaders$value = headers + .stream() + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + + this.sensitiveHeaders$set = true; + return this; + } } } diff --git a/src/main/java/com/perimeterx/models/configuration/credentialsIntelligenceconfig/PathType.java b/src/main/java/com/perimeterx/models/configuration/credentialsIntelligenceconfig/PathType.java index f7c41789..b0b7aab0 100644 --- a/src/main/java/com/perimeterx/models/configuration/credentialsIntelligenceconfig/PathType.java +++ b/src/main/java/com/perimeterx/models/configuration/credentialsIntelligenceconfig/PathType.java @@ -5,5 +5,7 @@ public enum PathType { @JsonProperty("regex") - REGEX + REGEX, + @JsonProperty("exact") + EXACT } diff --git a/src/main/java/com/perimeterx/utils/ActivityUtil.java b/src/main/java/com/perimeterx/utils/ActivityUtil.java index bad93f0a..21cd6261 100644 --- a/src/main/java/com/perimeterx/utils/ActivityUtil.java +++ b/src/main/java/com/perimeterx/utils/ActivityUtil.java @@ -13,7 +13,8 @@ public class ActivityUtil { return null; } return headers.entrySet().stream() - .filter(entry -> !sensitiveHeaders.contains(entry.getKey())) + .filter(entry -> entry != null && entry.getKey() != null) + .filter(entry -> !sensitiveHeaders.contains(entry.getKey().toLowerCase())) .map(entry -> new ActivityHeader(entry.getKey(), entry.getValue())).collect(Collectors.toList()); } diff --git a/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache b/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache index 74fb7f95..4a1503bf 100644 --- a/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache +++ b/src/main/resources/com/perimeterx/api/blockhandler/templates/captcha_template.mustache @@ -6,7 +6,7 @@ Access to this page has been denied {{#cssRef}} - + {{/cssRef}} @@ -20,7 +20,6 @@ window._pxJsClientSrc = '{{{jsClientSrc}}}'; window._pxMobile = {{isMobile}}; window._pxFirstPartyEnabled = {{firstPartyEnabled}}; - var pxCaptchaSrc = '{{{blockScript}}}'; var script = document.createElement('script'); script.src = pxCaptchaSrc; @@ -29,7 +28,6 @@ var onScriptErrorCalled; document.head.appendChild(script); var timeoutID = setTimeout(onScriptError, 5000); - function onScriptLoad() { clearTimeout(timeoutID); setTimeout(function() { @@ -59,7 +57,6 @@ function isCaptchaNotLoaded() { return !document.querySelector('div'); } - window._pxOnError = function () { var style = document.createElement('style'); style.innerText = '@import url(https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap);body{background-color:#fafbfc}.px-captcha-error-container{position:fixed;height:340px;background-color:#fff;font-family:Roboto,sans-serif}.px-captcha-error-header{color:#f0f1f2;font-size:29px;margin:67px 0 33px;font-weight:500;line-height:.83;text-align:center}.px-captcha-error-message{color:#f0f1f2;font-size:18px;margin:0 0 29px;line-height:1.33;text-align:center}.px-captcha-error-button{text-align:center;line-height:48px;width:253px;margin:auto;border-radius:50px;border:solid 1px #f0f1f2;font-size:20px;color:#f0f1f2}.px-captcha-error-wrapper{margin:18px 0 0}div.px-captcha-error{margin:auto;text-align:center;width:400px;height:30px;font-size:12px;background-color:#fcf0f2;color:#ce0e2d}img.px-captcha-error{margin:6px 8px -2px 0}.px-captcha-error-refid{border-top:solid 1px #f0eeee;height:27px;margin:13px 0 0;border-radius:0 0 3px 3px;background-color:#fafbfc;font-size:10px;line-height:2.5;text-align:center;color:#b1b5b8}@media (min-width:620px){.px-captcha-error-container{width:530px;top:50%;left:50%;margin-top:-170px;margin-left:-265px;border-radius:3px;box-shadow:0 2px 9px -1px rgba(0,0,0,.13)}}@media (min-width:481px) and (max-width:620px){.px-captcha-error-container{width:85%;top:50%;left:50%;margin-top:-170px;margin-left:-42.5%;border-radius:3px;box-shadow:0 2px 9px -1px rgba(0,0,0,.13)}}@media (max-width:480px){body{background-color:#fff}.px-captcha-error-header{color:#f0f1f2;font-size:29px;margin:55px 0 33px}.px-captcha-error-container{width:530px;top:50%;left:50%;margin-top:-170px;margin-left:-265px}.px-captcha-error-refid{position:fixed;width:100%;left:0;bottom:0;border-radius:0;font-size:14px;line-height:2}}@media (max-width:390px){div.px-captcha-error{font-size:10px}.px-captcha-error-refid{font-size:11px;line-height:2.5}}'; diff --git a/src/test/java/com/perimeterx/models/PXConfigurationTest.java b/src/test/java/com/perimeterx/models/PXConfigurationTest.java index ba4e8f72..15f4ccac 100644 --- a/src/test/java/com/perimeterx/models/PXConfigurationTest.java +++ b/src/test/java/com/perimeterx/models/PXConfigurationTest.java @@ -39,4 +39,48 @@ public void testMergeConfigurations() throws FileNotFoundException { Assert.assertEquals(pxConfiguration.getModuleMode(), ModuleMode.MONITOR); Assert.assertEquals(pxConfiguration.getRemoteConfigurationInterval(), 1); } + + @Test + public void testSensitiveHeadersDefaultValue() { + PXConfiguration configuration = PXConfiguration.builder() + .appId("appId") + .cookieKey("cookieKey") + .authToken("authToken") + .build(); + + Assert.assertEquals(configuration.getSensitiveHeaders(), new HashSet<>(Arrays.asList("cookieorig", "cookies"))); + } + @Test + public void testSensitiveHeadersNonDefaultValues() { + PXConfiguration configuration = PXConfiguration.builder() + .appId("appId") + .cookieKey("cookieKey") + .authToken("authToken") + .sensitiveHeaders(new HashSet<>(Arrays.asList("a","b","c"))) + .build(); + + Assert.assertEquals(configuration.getSensitiveHeaders(), new HashSet<>(Arrays.asList("a","b","c"))); + } + @Test + public void testSensitiveHeadersNonDefaultWithUpperCaseValues() { + PXConfiguration configuration = PXConfiguration.builder() + .appId("appId") + .cookieKey("cookieKey") + .authToken("authToken") + .sensitiveHeaders(new HashSet<>(Arrays.asList("a","B","c"))) + .build(); + + Assert.assertEquals(configuration.getSensitiveHeaders(), new HashSet<>(Arrays.asList("a","b","c"))); + } + @Test + public void testSensitiveHeadersNullValue() { + PXConfiguration configuration = PXConfiguration.builder() + .appId("appId") + .cookieKey("cookieKey") + .authToken("authToken") + .sensitiveHeaders(null) + .build(); + + Assert.assertEquals(configuration.getSensitiveHeaders(), new HashSet<>()); + } } diff --git a/web/pom.xml b/web/pom.xml index be1c8aa9..536fd145 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -25,7 +25,7 @@ com.perimeterx perimeterx-sdk - 6.11.0 + ${com.perimeterx.version} compile @@ -44,11 +44,27 @@ 1.2.17 - - + + + + maven-war-plugin + 3.4.0 + + + + src/main/java/templates + ** + /templates + + + + + + 8 8 + 6.11.0 diff --git a/web/src/main/java/com/web/Config.java b/web/src/main/java/com/web/Config.java index 24ed4bbc..1d7333f6 100644 --- a/web/src/main/java/com/web/Config.java +++ b/web/src/main/java/com/web/Config.java @@ -1,11 +1,11 @@ package com.web; -import com.google.gson.Gson; import com.perimeterx.api.additionalContext.credentialsIntelligence.CIProtocol; import com.perimeterx.api.additionalContext.credentialsIntelligence.loginresponse.LoginResponseValidationReportingMethod; import com.perimeterx.models.configuration.ModuleMode; import com.perimeterx.models.configuration.PXConfiguration; import com.perimeterx.models.configuration.credentialsIntelligenceconfig.CILoginMap; +import com.perimeterx.models.risk.CustomParameters; import org.json.JSONArray; import org.json.JSONObject; @@ -35,7 +35,7 @@ public PXConfiguration getPxConfiguration() { case "px_logger_auth_token": builder.loggerAuthToken(enforcerConfig.getString(key)); break; - case "px_enabled": + case "px_module_enabled": builder.moduleEnabled(enforcerConfig.getBoolean(key)); break; case "px_encryption_enabled": @@ -47,7 +47,7 @@ public PXConfiguration getPxConfiguration() { case "px_sensitive_headers": builder.sensitiveHeaders(jsonArrayToSet(enforcerConfig.getJSONArray(key))); break; - case "px_max_buffer_length": + case "px_max_buffer_len": builder.maxBufferLen(enforcerConfig.getInt(key)); break; case "px_s2s_timeout": @@ -59,7 +59,7 @@ public PXConfiguration getPxConfiguration() { case "px_send_async_activities": builder.sendPageActivities(enforcerConfig.getBoolean(key)); break; - case "px_server_url": + case "px_backend_url": builder.serverURL(enforcerConfig.getString(key)); break; case "px_custom_logo": @@ -89,8 +89,8 @@ public PXConfiguration getPxConfiguration() { case "px_first_party_enabled": builder.firstPartyEnabled(enforcerConfig.getBoolean(key)); break; - case "px_collector_url": - builder.serverURL(enforcerConfig.getString(key)); + case "px_backend_collector_url": + builder.collectorUrl(enforcerConfig.getString(key)); break; case "px_module_mode": if (enforcerConfig.getString(key).equals("active_blocking")) { @@ -99,7 +99,7 @@ public PXConfiguration getPxConfiguration() { builder.moduleMode(ModuleMode.MONITOR); } break; - case "px_static_files": + case "px_filter_by_extension": builder.staticFilesExt(jsonArrayToSet(enforcerConfig.getJSONArray(key))); break; case "px_remote_configuration_interval_ms": @@ -164,6 +164,17 @@ public PXConfiguration getPxConfiguration() { } + builder.customParametersExtraction(req -> { + CustomParameters customParameters = new CustomParameters(); + customParameters.customParam1 = "test1"; + customParameters.customParam2 = "test2"; + customParameters.customParam3 = "3"; + customParameters.customParam4 = "4"; + customParameters.customParam5 = "5"; + customParameters.customParam6 = "6"; + return customParameters; + }); + return builder.build(); }