diff --git a/.github/workflows/documentation-build.yml b/.github/workflows/documentation-build.yml index 739927575b..b8ad7bf2ed 100644 --- a/.github/workflows/documentation-build.yml +++ b/.github/workflows/documentation-build.yml @@ -43,7 +43,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 7c0312b196..cff4a334aa 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -25,7 +25,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/publish-libraries.yml b/.github/workflows/publish-libraries.yml index d27a04e157..5ee9db8ce9 100644 --- a/.github/workflows/publish-libraries.yml +++ b/.github/workflows/publish-libraries.yml @@ -34,7 +34,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-client-server-pds.yml b/.github/workflows/release-client-server-pds.yml index 5683de4363..81905a93f2 100644 --- a/.github/workflows/release-client-server-pds.yml +++ b/.github/workflows/release-client-server-pds.yml @@ -97,7 +97,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-pds-tools.yml b/.github/workflows/release-pds-tools.yml index 351c1ab1db..ef169c33de 100644 --- a/.github/workflows/release-pds-tools.yml +++ b/.github/workflows/release-pds-tools.yml @@ -53,7 +53,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-webui.yml b/.github/workflows/release-webui.yml index 80bb91ee60..17cf54b421 100644 --- a/.github/workflows/release-webui.yml +++ b/.github/workflows/release-webui.yml @@ -64,7 +64,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-wrapper-checkmarx.yml b/.github/workflows/release-wrapper-checkmarx.yml index 3810c9cb27..2b48caba92 100644 --- a/.github/workflows/release-wrapper-checkmarx.yml +++ b/.github/workflows/release-wrapper-checkmarx.yml @@ -43,7 +43,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-wrapper-owaspzap.yml b/.github/workflows/release-wrapper-owaspzap.yml index bbc5459ba8..29b79be1a1 100644 --- a/.github/workflows/release-wrapper-owaspzap.yml +++ b/.github/workflows/release-wrapper-owaspzap.yml @@ -44,7 +44,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-wrapper-prepare.yml b/.github/workflows/release-wrapper-prepare.yml index baa49de1cb..56ed6d93d2 100644 --- a/.github/workflows/release-wrapper-prepare.yml +++ b/.github/workflows/release-wrapper-prepare.yml @@ -43,7 +43,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-wrapper-validation.yml b/.github/workflows/release-wrapper-validation.yml index 7605ac60dc..b68cb694ab 100644 --- a/.github/workflows/release-wrapper-validation.yml +++ b/.github/workflows/release-wrapper-validation.yml @@ -43,7 +43,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/.github/workflows/release-wrapper-xray.yml b/.github/workflows/release-wrapper-xray.yml index 8f50bd05c9..492539b1d0 100644 --- a/.github/workflows/release-wrapper-xray.yml +++ b/.github/workflows/release-wrapper-xray.yml @@ -43,7 +43,7 @@ jobs: distribution: temurin - name: Set up Gradle - uses: gradle/actions/setup-gradle@af1da67850ed9a4cedd57bfd976089dd991e2582 + uses: gradle/actions/setup-gradle@16bf8bc8fe830fa669c3c9f914d3eb147c629707 with: cache-read-only: false diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitImportOptions.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitImportOptions.java index 0ff7365ba4..9e62666c42 100644 --- a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitImportOptions.java +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitImportOptions.java @@ -1,170 +1,106 @@ // SPDX-License-Identifier: MIT package mercedesbenz.com.sechub.archunit; -import java.util.List; - import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.core.importer.Location; public class ArchUnitImportOptions { - public static final String SECHUB_ROOT_FOLDER = "../../sechub/"; - - static List ignoreBuildFolders = new ArchUnitRuntimeSupport().createImportOptionsForBuildSystem(); + public static String SECHUB_ROOT_PATH = "../"; - /* Ignore specific packages */ - static ImportOption ignoreAllTests = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/test/"); // ignore any URI to sources that contains '/test/' - } + /* Ignore specific directories */ + static ImportOption ignoreAllTests = location -> { + return !location.contains("/test/"); // ignore any URI to sources that contains '/test/' }; - static ImportOption ignoreSechubOpenAPIJava = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-openapi-java(*)/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreSechubOpenAPIJava = location -> { + return !location.contains("/sechub-openapi-java/"); // ignore any URI to sources that contains '/sechub-openapi-java/' }; - static ImportOption ignoreSechubTestframework = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-testframework/"); // ignore any URI to sources that contains '/sechub-testframework/' - } + static ImportOption ignoreSechubTestframework = location -> { + return !location.contains("/sechub-testframework/"); // ignore any URI to sources that contains '/sechub-testframework/' }; - static ImportOption ignoreSharedkernelTest = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("com/mercedesbenz/sechub/sharedkernel/test"); // ignore any URI to sources that contains '/sechub-shared-kernel/' - } + static ImportOption ignoreSharedkernelTest = location -> { + return !location.contains("/sharedkernel/test/"); // ignore any URI to sources that contains '/sechub-shared-kernel/test/' }; - static ImportOption ignoreIntegrationTest = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-integrationtest/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreIntegrationTest = location -> { + return !location.contains("/sechub-integrationtest/"); // ignore any URI to sources that contains '/sechub-integrationtest/' }; - static ImportOption ignoreDocGen = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/docgen/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreDocGen = location -> { + return !location.contains("/docgen/"); // ignore any URI to sources that contains '/docgen/' }; - static ImportOption ignoreDevelopertools = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/developertools/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreDevelopertools = location -> { + return !location.contains("/developertools/"); // ignore any URI to sources that contains '/developertools/' }; - static ImportOption ignoreTools = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/tools/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreTools = location -> { + return !location.contains("/tools/"); // ignore any URI to sources that contains '/tools/' }; - static ImportOption ignoreBuildSrc = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/buildSrc/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreBuildSrc = location -> { + return !location.contains("/buildSrc/"); // ignore any URI to sources that contains '/buildSrc/' }; - static ImportOption ignoreExamples = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-examples/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreExamples = location -> { + return !location.contains("/sechub-examples/"); // ignore any URI to sources that contains '/sechub-examples/' }; - static ImportOption ignoreNessusAdapter = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/deprecated-sechub-adapter-nessus/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreNessusAdapter = location -> { + return !location.contains("/deprecated-sechub-adapter-nessus/"); // ignore any URI to sources that contains '/deprecated-sechub-adapter-nessus/' }; - static ImportOption ignoreNessusProduct = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/deprecated-sechub-scan-product-nessus/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreNessusProduct = location -> { + return !location.contains("/deprecated-sechub-scan-product-nessus/"); // ignore any URI to sources that contains + // '/deprecated-sechub-scan-product-nessus/' }; - static ImportOption ignoreNetsparkerAdapter = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/deprecated-sechub-adapter-netsparker/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreNetsparkerAdapter = location -> { + return !location.contains("/deprecated-sechub-adapter-netsparker/"); // ignore any URI to sources that contains + // '/deprecated-sechub-adapter-netsparker/' }; - static ImportOption ignoreNetsparkerProduct = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/deprecated-sechub-scan-product-netsparker/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreNetsparkerProduct = location -> { + return !location.contains("/deprecated-sechub-scan-product-netsparker/"); // ignore any URI to sources that contains + // '/deprecated-sechub-scan-product-netsparker/' }; - static ImportOption ignoreAnalyzerCLI = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-analyzer-cli/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreAnalyzerCLI = location -> { + return !location.contains("/sechub-analyzer-cli/"); // ignore any URI to sources that contains '/sechub-analyzer-cli/' }; - static ImportOption ignoreSechubApiJava = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-api-java/"); // ignore any URI to sources that contains '/openapi/' - } + static ImportOption ignoreSechubApiJava = location -> { + return !location.contains("/sechub-api-java/"); // ignore any URI to sources that contains '/sechub-api-java/' }; - static ImportOption ignoreSechubTest = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-test/"); // ignore any URI to sources that contains '/sechub-test/' - } + static ImportOption ignoreSechubTest = location -> { + return !location.contains("/sechub-test/"); // ignore any URI to sources that contains '/sechub-test/' }; - static ImportOption ignoreSystemTest = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/sechub-systemtest/"); // ignore any URI to sources that contains '/sechub-systemtest/' - } + static ImportOption ignoreSystemTest = location -> { + return !location.contains("/sechub-systemtest/"); // ignore any URI to sources that contains '/sechub-systemtest/' }; - static ImportOption ignoreGenApi = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("/api/internal/gen/"); // ignore any URI to sources that contains '/sechub-gen-api/' - } + static ImportOption ignoreGenApi = location -> { + return !location.contains("/api/internal/gen/"); // ignore any URI to sources that contains '/api/internal/gen/' }; /* Ignore specific classes */ - static ImportOption ignoreProductIdentifierClass = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("sharedkernel/ProductIdentifier"); // ignore any URI to sources that contains '/ProductIdentifier' - } + static ImportOption ignoreProductIdentifierClass = location -> { + return !location.contains("sharedkernel/ProductIdentifier"); // ignore any URI to sources that contains '/ProductIdentifier' + }; + + static ImportOption ignoreIntegrationTestClass = location -> { + return !location.contains("IntegrationTest"); // ignore any URI to sources that contains '/IntegrationTest' }; - static ImportOption ignoreIntegrationTestClass = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("IntegrationTest"); // ignore any URI to sources that contains '/ProductResult' - } + static ImportOption ignoreSchedulerSourcecodeUploadService = location -> { + return !location.contains("SchedulerSourcecodeUploadService"); // ignore any URI to sources that contains '/SchedulerSourcecodeUploadService' }; - static ImportOption ignoreSchedulerSourcecodeUploadService = new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains("SchedulerSourcecodeUploadService"); // ignore any URI to sources that contains '/ProductResult' - } + static ImportOption ignoreJarFiles = location -> { + return !location.contains(".jar"); // ignore jar files' }; } diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitRuntimeSupport.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitRuntimeSupport.java deleted file mode 100644 index 34ca306ebf..0000000000 --- a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitRuntimeSupport.java +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: MIT -package mercedesbenz.com.sechub.archunit; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import com.tngtech.archunit.core.importer.ImportOption; -import com.tngtech.archunit.core.importer.Location; - -/* - * This class provides build system specific import options for ArchUnit. - * Depending on the build system, the binary folder is different for gradle, intelliJ or eclipse. - * The property "sechub.archunit.buildsystem" can be set to "gradle", "intelliJ" or "eclipse" and is by default gradle - */ -class ArchUnitRuntimeSupport { - - /* @formatter:off */ - private Map buildSystemToBinaryFolder = Map.of( - "gradle", "/build/classes/", - "intelliJ", "/out/", - "eclipse", "/bin/"); - /* @formatter:on */ - - public List createImportOptionsForBuildSystem() { - String buildSystem = resolveBuildSystem(); - - Map binaryFolderToIgnoreMap = new HashMap<>(buildSystemToBinaryFolder); - binaryFolderToIgnoreMap.remove(buildSystem); - - List importOptions = new ArrayList<>(); - for (String binaryFolder : binaryFolderToIgnoreMap.values()) { - importOptions.add(new ImportOption() { - @Override - public boolean includes(Location location) { - return !location.contains(binaryFolder); - } - }); - } - - return importOptions; - } - - private String resolveBuildSystem() { - String buildSystem = System.getProperty("sechub.archunit.buildsystem"); - if (buildSystem == null || buildSystem.isBlank()) { - buildSystem = "gradle"; - } - - switch (buildSystem) { - case "gradle": - case "intelliJ": - case "eclipse": - return buildSystem; - default: - throw new IllegalStateException("Unsupported build system: " + buildSystem); - } - } -} diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/CodingRulesTest.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/CodingRulesTest.java index 2cf237ac95..57c1272f0b 100644 --- a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/CodingRulesTest.java +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/CodingRulesTest.java @@ -12,18 +12,15 @@ import com.tngtech.archunit.core.domain.JavaAccess; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; -import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; @AnalyzeClasses public class CodingRulesTest { - private JavaClasses importedClasses; - @Test void classes_should_not_throw_generic_exceptions() { /* prepare */ - ignoreTestGeneratedAndDeprecatedPackages(); + JavaClasses importedClasses = ignoreTestGeneratedAndDeprecatedPackages(); /* execute + test */ NO_CLASSES_SHOULD_THROW_GENERIC_EXCEPTIONS.check(importedClasses); @@ -33,10 +30,7 @@ void classes_should_not_throw_generic_exceptions() { void classes_should_not_use_deprecated_members() { /* prepare */ /* @formatter:off */ - importedClasses = new ClassFileImporter() - .withImportOptions(ignoreBuildFolders) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + JavaClasses importedClasses = new ClassFileImporter() .withImportOption(ignoreAllTests) .withImportOption(ignoreSechubOpenAPIJava) .withImportOption(ignoreNessusAdapter) @@ -50,7 +44,9 @@ void classes_should_not_use_deprecated_members() { .withImportOption(ignoreDevelopertools) .withImportOption(ignoreSchedulerSourcecodeUploadService) .withImportOption(ignoreSystemTest) - .importPath(SECHUB_ROOT_FOLDER); + .withImportOption(ignoreGenApi) + .withImportOption(ignoreJarFiles) + .importPath(SECHUB_ROOT_PATH); /* execute + test */ /* custom version of DEPRECATED_API_SHOULD_NOT_BE_USED */ @@ -68,7 +64,7 @@ void classes_should_not_use_deprecated_members() { @Test void assertion_error_must_have_detailed_message() { /* prepare */ - ignoreTestGeneratedAndDeprecatedPackages(); + JavaClasses importedClasses = ignoreTestGeneratedAndDeprecatedPackages(); /* execute + test */ ASSERTIONS_SHOULD_HAVE_DETAIL_MESSAGE.check(importedClasses); @@ -78,9 +74,7 @@ void assertion_error_must_have_detailed_message() { void test_classes_should_be_in_the_same_package_as_implementation() { /* prepare */ /* @formatter:off */ - importedClasses = new ClassFileImporter() - .withImportOptions(ignoreBuildFolders) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + JavaClasses importedClasses = new ClassFileImporter() .withImportOption(ignoreSechubOpenAPIJava) .withImportOption(ignoreSechubApiJava) .withImportOption(ignoreDocGen) @@ -88,7 +82,8 @@ void test_classes_should_be_in_the_same_package_as_implementation() { .withImportOption(ignoreSechubTest) .withImportOption(ignoreSystemTest) .withImportOption(ignoreGenApi) - .importPath(SECHUB_ROOT_FOLDER); + .withImportOption(ignoreJarFiles) + .importPath(SECHUB_ROOT_PATH); /* execute + test */ testClassesShouldResideInTheSamePackageAsImplementation().check(importedClasses); @@ -98,7 +93,7 @@ void test_classes_should_be_in_the_same_package_as_implementation() { @Test void classes_should_not_use_java_util_logging() { /* prepare */ - ignoreTestGeneratedAndDeprecatedPackages(); + JavaClasses importedClasses = ignoreTestGeneratedAndDeprecatedPackages(); /* execute + test */ NO_CLASSES_SHOULD_USE_JAVA_UTIL_LOGGING.check(importedClasses); @@ -108,10 +103,7 @@ void classes_should_not_use_java_util_logging() { void classes_should_not_use_standard_streams() { /* prepare */ /* @formatter:off */ - importedClasses = new ClassFileImporter() - .withImportOptions(ignoreBuildFolders) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + JavaClasses importedClasses = new ClassFileImporter() .withImportOption(ignoreAllTests) .withImportOption(ignoreSechubOpenAPIJava) .withImportOption(ignoreIntegrationTest) @@ -121,19 +113,18 @@ void classes_should_not_use_standard_streams() { .withImportOption(ignoreBuildSrc) .withImportOption(ignoreAnalyzerCLI) .withImportOption(ignoreExamples) - .importPath(SECHUB_ROOT_FOLDER); + .withImportOption(ignoreGenApi) + .withImportOption(ignoreJarFiles) + .importPath(SECHUB_ROOT_PATH); /* execute + test */ NO_CLASSES_SHOULD_ACCESS_STANDARD_STREAMS.check(importedClasses); /* @formatter:on */ } - private void ignoreTestGeneratedAndDeprecatedPackages() { + private JavaClasses ignoreTestGeneratedAndDeprecatedPackages() { /* @formatter:off */ - importedClasses = new ClassFileImporter() - .withImportOptions(ignoreBuildFolders) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + return new ClassFileImporter() .withImportOption(ignoreAllTests) .withImportOption(ignoreSechubOpenAPIJava) .withImportOption(ignoreNessusAdapter) @@ -143,7 +134,9 @@ private void ignoreTestGeneratedAndDeprecatedPackages() { .withImportOption(ignoreIntegrationTest) .withImportOption(ignoreSechubApiJava) .withImportOption(ignoreDevelopertools) - .importPath(SECHUB_ROOT_FOLDER); + .withImportOption(ignoreGenApi) + .withImportOption(ignoreJarFiles) + .importPath(SECHUB_ROOT_PATH); /* @formatter:on */ } } diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/DomainAccessRulesTest.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/DomainAccessRulesTest.java index 5e44316f5c..0e7c36aaec 100644 --- a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/DomainAccessRulesTest.java +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/DomainAccessRulesTest.java @@ -17,7 +17,6 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; -import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; @AnalyzeClasses @@ -47,11 +46,9 @@ void no_class_in_one_domain_communicate_with_another_domain(String domainToTest) /* prepare */ /* @formatter:off */ JavaClasses importedClasses = new ClassFileImporter() - .withImportOptions(ignoreBuildFolders) .withImportOption(ignoreDevelopertools) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) - .importPath(SECHUB_ROOT_FOLDER); + .withImportOption(ignoreJarFiles) + .importPath(SECHUB_ROOT_PATH); /* execute + test */ noClasses() @@ -82,8 +79,8 @@ private static String packageIdentifier(String domain) { private static class DomainDataArgumentProvider implements ArgumentsProvider { @Override - public Stream provideArguments(ExtensionContext context) throws Exception { - return allDomainsToTest.stream().map(domain -> Arguments.of(domain)); + public Stream provideArguments(ExtensionContext context) { + return allDomainsToTest.stream().map(Arguments::of); } } } diff --git a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/NamingConventionTest.java b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/NamingConventionTest.java index bdd9cc5871..d212d12372 100644 --- a/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/NamingConventionTest.java +++ b/sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/NamingConventionTest.java @@ -9,26 +9,22 @@ import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.importer.ClassFileImporter; -import com.tngtech.archunit.core.importer.ImportOption; import com.tngtech.archunit.junit.AnalyzeClasses; @AnalyzeClasses public class NamingConventionTest { - private JavaClasses importedClasses; - @Test void classes_in_test_packages_containing_test_or_assert_in_name() { /* prepare */ /* @formatter:off */ - importedClasses = new ClassFileImporter() - .withImportOptions(ignoreBuildFolders) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + JavaClasses importedClasses = new ClassFileImporter() .withImportOption(ignoreSechubOpenAPIJava) .withImportOption(ignoreSechubTestframework) .withImportOption(ignoreSharedkernelTest) .withImportOption(ignoreSechubApiJava) - .importPath(SECHUB_ROOT_FOLDER); + .withImportOption(ignoreJarFiles) + .importPath(SECHUB_ROOT_PATH); /* execute + test */ classes() @@ -49,13 +45,11 @@ void classes_in_test_packages_containing_test_or_assert_in_name() { void service_annotated_classes_contain_service_or_executor_in_name() { /* prepare */ /* @formatter:off */ - importedClasses = new ClassFileImporter() - .withImportOptions(ignoreBuildFolders) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS) - .withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_JARS) + JavaClasses importedClasses = new ClassFileImporter() .withImportOption(ignoreAllTests) .withImportOption(ignoreSechubOpenAPIJava) - .importPath("../../sechub/"); + .withImportOption(ignoreJarFiles) + .importPath(SECHUB_ROOT_PATH); /* execute + test */ classes() diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives.go index fd0e6e6b4c..5d8f46015c 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives.go @@ -8,11 +8,13 @@ import ( "io" "os" "path/filepath" + "slices" + "strings" sechubUtil "mercedes-benz.com/sechub/util" ) -// Keyword for false-posisitves json file +// Keyword for false-positives json file const falsePositivesListType = "falsePositiveJobDataList" // FalsePositivesList - structure for handling download of false-positive lists @@ -24,9 +26,10 @@ type FalsePositivesList struct { // FalsePositivesConfig - struct containing information for defining false-positives type FalsePositivesConfig struct { - APIVersion string `json:"apiVersion"` - Type string `json:"type"` - JobData []FalsePositivesJobData `json:"jobData"` + APIVersion string `json:"apiVersion"` + Type string `json:"type"` + JobData []FalsePositivesJobData `json:"jobData"` + ProjectData []FalsePositivesProjectData `json:"projectData"` } // FalsePositivesJobData - contains data related to a scan job for defining false-positives @@ -36,6 +39,20 @@ type FalsePositivesJobData struct { Comment string `json:"comment"` } +// FalsePositivesProjectData - contains data related to a project for defining false-positives +type FalsePositivesProjectData struct { + ID string `json:"id"` + Comment string `json:"comment"` + WebScan FalsePositivesProjectDataForWebScan `json:"webScan"` +} + +// FalsePositivesProjectDataForWebScan - contains the definition for false-positives in web scans +type FalsePositivesProjectDataForWebScan struct { + CweID int `json:"cweId"` + UrlPattern string `json:"urlPattern"` + Methods []string `json:"methods"` +} + // FalsePositivesDefinition - the struct that comes from SecHub server with getFalsePositives type FalsePositivesDefinition struct { Items []FalsePositiveDefinition `json:"falsePositives"` @@ -43,10 +60,11 @@ type FalsePositivesDefinition struct { // FalsePositiveDefinition - a single false-positive definition from server type FalsePositiveDefinition struct { - JobData FalsePositivesJobData `json:"jobData"` - Author string `json:"author"` - MetaData FalsePositiveDefinitionMetaData `json:"metaData"` - Created string `json:"created"` + JobData FalsePositivesJobData `json:"jobData"` + ProjectData FalsePositivesProjectData `json:"projectData"` + Author string `json:"author"` + MetaData FalsePositiveDefinitionMetaData `json:"metaData"` + Created string `json:"created"` } // FalsePositiveDefinitionMetaData - metadata part of FalsePositiveDefinition @@ -149,11 +167,11 @@ func readFileIntoContext(context *Context, fallbackFile string) { func defineFalsePositivesFromFile(context *Context) { readFileIntoContext(context, DefaultSecHubFalsePositivesJSONFile) - // read json into go struct + // Read json into go struct falsePositivesDefinitionList := newFalsePositivesListFromBytes(context.inputForContentProcessing) sechubUtil.LogDebug(context.config.debug, fmt.Sprintf("False positives to be defined: %+v", falsePositivesDefinitionList)) - // download false-positives list for project from SecHub server + // Download false-positives list for project from SecHub server jsonFPBlob := FalsePositivesList{serverResult: getFalsePositivesList(context), outputFolder: "", outputFileName: ""} var falsePositivesServerList FalsePositivesDefinition err := json.Unmarshal(jsonFPBlob.serverResult, &falsePositivesServerList) @@ -179,7 +197,7 @@ func defineFalsePositives(newFalsePositives FalsePositivesConfig, currentFalsePo falsePositivesToRemove.APIVersion = CurrentAPIVersion falsePositivesToRemove.Type = falsePositivesListType - // Loop through definition list and figure out, what to add and what to remove + // Loop through JobData definition list and figure out, what to add and what to remove for _, newFalsePositive := range newFalsePositives.JobData { matched := false for i, falsePositive := range currentFalsePositives { @@ -197,12 +215,38 @@ func defineFalsePositives(newFalsePositives FalsePositivesConfig, currentFalsePo } } + // Loop through ProjectData definition list and figure out, what to add and what to remove + for _, newFalsePositive := range newFalsePositives.ProjectData { + matched := false + for i, falsePositive := range currentFalsePositives { + if newFalsePositive.ID == falsePositive.ProjectData.ID { + // Remove item from list if ID exists + currentFalsePositives[i] = currentFalsePositives[len(currentFalsePositives)-1] // Copy last item to current position + currentFalsePositives = currentFalsePositives[:len(currentFalsePositives)-1] // Truncate slice + + // Compare alle elements to decide if an update is needed + if (newFalsePositive.Comment == falsePositive.ProjectData.Comment) && + (newFalsePositive.WebScan.CweID == falsePositive.ProjectData.WebScan.CweID) && + (newFalsePositive.WebScan.UrlPattern == falsePositive.ProjectData.WebScan.UrlPattern) && + slices.Equal(newFalsePositive.WebScan.Methods, falsePositive.ProjectData.WebScan.Methods) { + matched = true + } + break + } + } + if !matched { + // Add False positive to list (will be updated if it exists on the server) + falsePositivesToAdd.ProjectData = append(falsePositivesToAdd.ProjectData, newFalsePositive) + } + } + // currentFalsePositives now contains all false positives to remove for _, falsePositiveToBeRemoved := range currentFalsePositives { - var fp FalsePositivesJobData - fp.JobUUID = falsePositiveToBeRemoved.JobData.JobUUID - fp.FindingID = falsePositiveToBeRemoved.JobData.FindingID - falsePositivesToRemove.JobData = append(falsePositivesToRemove.JobData, fp) + if falsePositiveToBeRemoved.JobData.JobUUID != "" { + falsePositivesToRemove.JobData = append(falsePositivesToRemove.JobData, falsePositiveToBeRemoved.JobData) + } else if falsePositiveToBeRemoved.ProjectData.ID != "" { + falsePositivesToRemove.ProjectData = append(falsePositivesToRemove.ProjectData, falsePositiveToBeRemoved.ProjectData) + } } return falsePositivesToAdd, falsePositivesToRemove @@ -239,22 +283,35 @@ func uploadFalsePositives(context *Context) { // Read inputForContentProcessing into a JSON struct falsePositivesList := newFalsePositivesListFromBytes(context.inputForContentProcessing) - // Upload the list in chunks of maximal MaxChunkSizeFalsePositives items + // Upload the jobData list in chunks of maximal MaxChunkSizeFalsePositives items for i := 0; ; i++ { uploadChunk := getFalsePositivesUploadChunk(falsePositivesList, i) if len(uploadChunk.JobData) == 0 { break } - jsonBlob, err := json.Marshal(uploadChunk) - sechubUtil.HandleError(err, ExitCodeFailed) - context.inputForContentProcessing = jsonBlob - processContent(context) + uploadFalsePositivesChunk(context, uploadChunk) + } - // Send context.contentToSend to SecHub server - sendWithDefaultHeader("PUT", buildFalsePositivesAPICall(context), context) + // Upload fp projectData if present + if len(falsePositivesList.ProjectData) > 0 { + var falsePositivesProjectData FalsePositivesConfig + falsePositivesProjectData.APIVersion = falsePositivesList.APIVersion + falsePositivesProjectData.Type = falsePositivesList.Type + falsePositivesProjectData.ProjectData = falsePositivesList.ProjectData + uploadFalsePositivesChunk(context, falsePositivesProjectData) } } +func uploadFalsePositivesChunk(context *Context, uploadChunk FalsePositivesConfig) { + jsonBlob, err := json.Marshal(uploadChunk) + sechubUtil.HandleError(err, ExitCodeFailed) + context.inputForContentProcessing = jsonBlob + processContent(context) + + // Send context.contentToSend to SecHub server + sendWithDefaultHeader("PUT", buildFalsePositivesAPICall(context), context) +} + func unmarkFalsePositivesFromFile(context *Context) { sechubUtil.LogDebug(context.config.debug, fmt.Sprintf("Action %q: remove false positives - read from file: %s", context.config.action, context.config.file)) @@ -268,24 +325,40 @@ func unmarkFalsePositivesFromFile(context *Context) { } func unmarkFalsePositives(context *Context, list *FalsePositivesConfig) { - if len(list.JobData) == 0 { + if len(list.JobData) == 0 && len(list.ProjectData) == 0 { sechubUtil.Log("0 false-positives removed from project \""+context.config.projectID+"\"", context.config.quiet) return } - sechubUtil.Log("Removing as false-positives from project \""+context.config.projectID+"\":", context.config.quiet) - // Loop over list and push to SecHub server - // Url scheme: curl 'https://sechub.example.com/api/project/project1/false-positive/f1d02a9d-5e1b-4f52-99e5-401854ccf936/42' -i -X DELETE - urlPrefix := buildFalsePositiveAPICall(context) - // we don't want to send content here context.inputForContentProcessing = []byte(``) processContent(context) - for _, element := range list.JobData { - sechubUtil.Log(fmt.Sprintf("- JobUUID %s: Finding #%d", element.JobUUID, element.FindingID), context.config.quiet) - sendWithDefaultHeader("DELETE", fmt.Sprintf("%s/%s/%d", urlPrefix, element.JobUUID, element.FindingID), context) + sechubUtil.Log("Removing as false-positives from project \""+context.config.projectID+"\":", context.config.quiet) + // Loop over lists and push to SecHub server + + if len(list.JobData) > 0 { + // Iterate over JobData list: + // Url scheme: curl 'https://sechub.example.com/api/project/project1/false-positive/f1d02a9d-5e1b-4f52-99e5-401854ccf936/42' -i -X DELETE + urlPrefix := buildFalsePositiveAPICall(context) + + for _, element := range list.JobData { + sechubUtil.Log(fmt.Sprintf("- JobUUID %s: Finding #%d", element.JobUUID, element.FindingID), context.config.quiet) + sendWithDefaultHeader("DELETE", fmt.Sprintf("%s/%s/%d", urlPrefix, element.JobUUID, element.FindingID), context) + } + } + + if len(list.ProjectData) > 0 { + // Iterate over ProjectData list: + // Url scheme: curl 'https://sechub.example.com//api/project/project1/false-positive/project-data/fp-id-1' -i -X DELETE + urlPrefix := buildFalsePositiveProjectDataAPICall(context) + + for _, element := range list.ProjectData { + sechubUtil.Log(fmt.Sprintf("- project's false-positive-ID: \"%s\"", element.ID), context.config.quiet) + sendWithDefaultHeader("DELETE", fmt.Sprintf("%s/%s", urlPrefix, element.ID), context) + } } + sechubUtil.Log("Transfer completed", context.config.quiet) } @@ -353,16 +426,20 @@ func newFalsePositivesListFromConsole(context *Context) (list FalsePositivesConf } func markFalsePositives(context *Context, list *FalsePositivesConfig) { - if len(list.JobData) == 0 { + if len(list.JobData) == 0 && len(list.ProjectData) == 0 { sechubUtil.Log("0 false-positives added to project \""+context.config.projectID+"\"", context.config.quiet) return } - sechubUtil.Log("Adding as false-positives to project \""+context.config.projectID+"\":", context.config.quiet) + sechubUtil.Log("Adding/updating as false-positives in project \""+context.config.projectID+"\":", context.config.quiet) for _, element := range list.JobData { sechubUtil.Log(fmt.Sprintf("- JobUUID %s: Finding #%d, Comment: %s", element.JobUUID, element.FindingID, element.Comment), context.config.quiet) } + for _, element := range list.ProjectData { + sechubUtil.Log(fmt.Sprintf("- project's false-positive-ID: %s, Comment: %s", element.ID, element.Comment), context.config.quiet) + } + // upload to server jsonBlob, err := json.Marshal(list) sechubUtil.HandleError(err, ExitCodeFailed) @@ -390,7 +467,7 @@ func interactiveUnmarkFalsePositives(context *Context) { FalsePositivesList := newUnmarkFalsePositivesListFromConsole(context) sechubUtil.LogDebug(context.config.debug, fmt.Sprintf("False-positives unmark list for upload:\n%+v", FalsePositivesList)) - if len(FalsePositivesList.JobData) == 0 { + if len(FalsePositivesList.JobData) == 0 && len(FalsePositivesList.ProjectData) == 0 { sechubUtil.Log("No false positives to unmark.", context.config.quiet) return } @@ -413,8 +490,6 @@ func newUnmarkFalsePositivesListFromConsole(context *Context) (result FalsePosit sechubUtil.HandleError(err, ExitCodeFailed) sechubUtil.LogDebug(context.config.debug, fmt.Sprintf("Read from Server:\n%+v", list)) - // ToDo: sort report by severity,finding id - // iterate over entries and ask which to unmark var ExpectedInputs = []sechubUtil.ConsoleInputItem{ {Input: "y", ShortDescription: "Yes"}, @@ -429,8 +504,16 @@ func newUnmarkFalsePositivesListFromConsole(context *Context) (result FalsePosit sechubUtil.HandleError(err, ExitCodeFailed) if input == "y" { // append finding to list - var listEntry = FalsePositivesJobData{falsepositive.JobData.JobUUID, falsepositive.JobData.FindingID, ""} - result.JobData = append(result.JobData, listEntry) + if falsepositive.JobData.JobUUID != "" { + var listEntry = FalsePositivesJobData{ JobUUID: falsepositive.JobData.JobUUID, FindingID: falsepositive.JobData.FindingID } + result.JobData = append(result.JobData, listEntry) + } + + if falsepositive.ProjectData.ID != "" { + var listEntry = FalsePositivesProjectData{ ID: falsepositive.ProjectData.ID } + result.ProjectData = append(result.ProjectData, listEntry) + } + } else if input == "c" { os.Exit(ExitCodeOK) } else if input == "s" { @@ -442,21 +525,47 @@ func newUnmarkFalsePositivesListFromConsole(context *Context) (result FalsePosit } func printFalsePositiveDefinition(falsepositive *FalsePositiveDefinition) { - // Example output: - // ------------------------------------------------------------------ - // Creation of Temp File in Dir with Incorrect Permissions, codeScan severity: LOW - // Origin: Finding ID 3 in job f94d815c-7f69-48c3-8433-8f03d52ce32a - // File: java/com/mercedes-benz/sechub/docgen/kubernetes/KubernetesTemplateFilesGenerator.java - // Code: File secHubServer = new File("./sechub-server"); - // (Added by admin at 2020-07-10 13:41:06; comment: "Only temporary directory") - // ------------------------------------------------------------------ sechubUtil.PrintDashedLine() - fmt.Printf("%s, %s severity: %s\n", falsepositive.MetaData.Name, falsepositive.MetaData.ScanType, falsepositive.MetaData.Severity) - fmt.Printf(" Origin: Finding ID %d in job %s\n", falsepositive.JobData.FindingID, falsepositive.JobData.JobUUID) - // would be cool to have line and column in source code location - fmt.Printf(" File: %s\n", falsepositive.MetaData.Code.Start.Location) - fmt.Printf(" Code: %s\n", falsepositive.MetaData.Code.Start.SourceCode) - fmt.Printf("(Added by %s at %s; comment: %q)\n", falsepositive.Author, falsepositive.Created, falsepositive.JobData.Comment) - // added by name at date + + // Is of type JobData? + if falsepositive.JobData.JobUUID != "" { + // Example output: + // ------------------------------------------------------------------ + // Creation of Temp File in Dir with Incorrect Permissions, codeScan severity: LOW + // Origin: Finding ID 3 in job f94d815c-7f69-48c3-8433-8f03d52ce32a + // File: java/com/mercedes-benz/sechub/docgen/kubernetes/KubernetesTemplateFilesGenerator.java + // Code: File secHubServer = new File("./sechub-server"); + // (Added by admin at 2024-07-10 13:41:06; comment: "Only temporary directory") + // ------------------------------------------------------------------ + fmt.Printf("%s, %s severity: %s\n", falsepositive.MetaData.Name, falsepositive.MetaData.ScanType, falsepositive.MetaData.Severity) + fmt.Printf(" Origin: Finding ID %d in job %s\n", falsepositive.JobData.FindingID, falsepositive.JobData.JobUUID) + // would be cool to have line and column in source code location + if falsepositive.MetaData.Code.Start.Location != "" { + fmt.Printf(" File: %s\n", falsepositive.MetaData.Code.Start.Location) + fmt.Printf(" Code: %s\n", falsepositive.MetaData.Code.Start.SourceCode) + } + fmt.Printf("(Added by %s at %s; comment: %q)\n", falsepositive.Author, falsepositive.Created, falsepositive.JobData.Comment) + } + + // Is of type ProjectData? + if falsepositive.ProjectData.ID != "" { + // Example output: + // ------------------------------------------------------------------ + // Project's false-positive-ID: "my-fp-definition1" (logout url) + // urlPattern: https://myapp-*.example.com:80*/logout?* + // CWE-ID: 89, Methods: GET, PUT, POST + // (Added by admin at 2024-09-06 08:01:03) + // ------------------------------------------------------------------ + fmt.Printf("Project's false-positive-ID: %q (%s)\n", falsepositive.ProjectData.ID, falsepositive.ProjectData.Comment) + if falsepositive.ProjectData.WebScan.UrlPattern != "" { + fmt.Printf(" urlPattern: %s\n", falsepositive.ProjectData.WebScan.UrlPattern) + fmt.Printf(" CWE-ID: %d", falsepositive.ProjectData.WebScan.CweID) + if len (falsepositive.ProjectData.WebScan.Methods) > 0 { + fmt.Printf(", Methods: %s", strings.Join(falsepositive.ProjectData.WebScan.Methods, ", ")) + } + } + fmt.Printf("\n(Added by %s at %s)\n", falsepositive.Author, falsepositive.Created) + } + sechubUtil.PrintDashedLine() } diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives_test.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives_test.go index 54466ae651..65bdf8e2f6 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives_test.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives_test.go @@ -3,6 +3,7 @@ package cli import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -44,7 +45,7 @@ func TestFalsePositivesSaveWritesAFile(t *testing.T) { sechubTestUtil.AssertFileExists(expected, t) } -func Example_defineFalsePositives() { +func Example_defineFalsePositivesJobData() { /* prepare */ definedFalsePositives := []FalsePositivesJobData{ {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, @@ -68,11 +69,11 @@ func Example_defineFalsePositives() { fmt.Printf("Remove: %+v\n", falsePositivesToRemove) // Output: - // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:33333333-3333-3333-3333-333333333333 FindingID:3 Comment:test3} {JobUUID:55555555-5555-5555-5555-555555555555 FindingID:5 Comment:test5}]} - // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:44444444-4444-4444-4444-444444444444 FindingID:4 Comment:}]} + // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:33333333-3333-3333-3333-333333333333 FindingID:3 Comment:test3} {JobUUID:55555555-5555-5555-5555-555555555555 FindingID:5 Comment:test5}] ProjectData:[]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:44444444-4444-4444-4444-444444444444 FindingID:4 Comment:}] ProjectData:[]} } -func Example_defineFalsePositivesEmptyInputList() { +func Example_defineFalsePositivesJobDataEmptyInputList() { // An empty input list will remove all defined false-positives /* prepare */ @@ -92,11 +93,11 @@ func Example_defineFalsePositivesEmptyInputList() { fmt.Printf("Remove: %+v\n", falsePositivesToRemove) // Output: - // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[]} - // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:11111111-1111-1111-1111-111111111111 FindingID:1 Comment:} {JobUUID:22222222-2222-2222-2222-222222222222 FindingID:2 Comment:}]} + // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:11111111-1111-1111-1111-111111111111 FindingID:1 Comment:} {JobUUID:22222222-2222-2222-2222-222222222222 FindingID:2 Comment:}] ProjectData:[]} } -func Example_defineFalsePositivesEmptyServerList() { +func Example_defineFalsePositivesJobDataEmptyServerList() { // An empty server list will simply add all defined false-positives /* prepare */ @@ -116,8 +117,122 @@ func Example_defineFalsePositivesEmptyServerList() { fmt.Printf("Remove: %+v\n", falsePositivesToRemove) // Output: - // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:11111111-1111-1111-1111-111111111111 FindingID:1 Comment:test1} {JobUUID:22222222-2222-2222-2222-222222222222 FindingID:2 Comment:test2}]} - // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[]} + // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:11111111-1111-1111-1111-111111111111 FindingID:1 Comment:test1} {JobUUID:22222222-2222-2222-2222-222222222222 FindingID:2 Comment:test2}] ProjectData:[]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} +} + +func Example_defineFalsePositivesProjectData() { + /* prepare */ + definedFalsePositives := []FalsePositivesProjectData{ + {ID: "test1", Comment: "test1", WebScan: FalsePositivesProjectDataForWebScan{CweID: 1, UrlPattern: "https://example1/*", Methods: []string{"GET", "PUT"}}}, + {ID: "test2", Comment: "test2", WebScan: FalsePositivesProjectDataForWebScan{CweID: 2, UrlPattern: "https://example2/*"}}, + {ID: "test3", Comment: "test3", WebScan: FalsePositivesProjectDataForWebScan{CweID: 3, UrlPattern: "https://example3/*"}}, + {ID: "test5", Comment: "test5", WebScan: FalsePositivesProjectDataForWebScan{CweID: 5, UrlPattern: "https://example5/*"}}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, ProjectData: definedFalsePositives} + + falsePositivesServerList := []FalsePositiveDefinition{ + {ProjectData: FalsePositivesProjectData{ID: "test1", Comment: "test1", WebScan: FalsePositivesProjectDataForWebScan{CweID: 1, UrlPattern: "https://example1/*", Methods: []string{"GET", "POST"}}}}, + {ProjectData: FalsePositivesProjectData{ID: "test2", Comment: "test2 old", WebScan: FalsePositivesProjectDataForWebScan{CweID: 2, UrlPattern: "https://example2/*"}}}, + {ProjectData: FalsePositivesProjectData{ID: "test4", Comment: "test4", WebScan: FalsePositivesProjectDataForWebScan{CweID: 4, UrlPattern: "https://example4/*"}}}, + } + + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + + /* test */ + fmt.Printf("Add: %+v\n", falsePositivesToAdd) + fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + + // Output: + // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[{ID:test1 Comment:test1 WebScan:{CweID:1 UrlPattern:https://example1/* Methods:[GET PUT]}} {ID:test2 Comment:test2 WebScan:{CweID:2 UrlPattern:https://example2/* Methods:[]}} {ID:test3 Comment:test3 WebScan:{CweID:3 UrlPattern:https://example3/* Methods:[]}} {ID:test5 Comment:test5 WebScan:{CweID:5 UrlPattern:https://example5/* Methods:[]}}]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[{ID:test4 Comment:test4 WebScan:{CweID:4 UrlPattern:https://example4/* Methods:[]}}]} +} + +func Example_defineFalsePositivesProjectDataEmptyInputList() { + // An empty input list will remove all defined false-positives + + /* prepare */ + definedFalsePositives := []FalsePositivesProjectData{} + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, ProjectData: definedFalsePositives} + + falsePositivesServerList := []FalsePositiveDefinition{ + {ProjectData: FalsePositivesProjectData{ID: "test1"}}, + {ProjectData: FalsePositivesProjectData{ID: "test2"}}, + } + + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + + /* test */ + fmt.Printf("Add: %+v\n", falsePositivesToAdd) + fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + + // Output: + // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[{ID:test1 Comment: WebScan:{CweID:0 UrlPattern: Methods:[]}} {ID:test2 Comment: WebScan:{CweID:0 UrlPattern: Methods:[]}}]} +} + +func Example_defineFalsePositivesProjectDataEmptyServerList() { + // An empty server list will simply add all defined false-positives + + /* prepare */ + definedFalsePositives := []FalsePositivesProjectData{ + {ID: "test1", Comment: "test1", WebScan: FalsePositivesProjectDataForWebScan{CweID: 1, UrlPattern: "https://example1/*", Methods: []string{"GET", "PUT"}}}, + {ID: "test2", Comment: "test2", WebScan: FalsePositivesProjectDataForWebScan{CweID: 2, UrlPattern: "https://example2/*"}}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, ProjectData: definedFalsePositives} + + falsePositivesServerList := []FalsePositiveDefinition{} + + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + + /* test */ + fmt.Printf("Add: %+v\n", falsePositivesToAdd) + fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + + // Output: + // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[{ID:test1 Comment:test1 WebScan:{CweID:1 UrlPattern:https://example1/* Methods:[GET PUT]}} {ID:test2 Comment:test2 WebScan:{CweID:2 UrlPattern:https://example2/* Methods:[]}}]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} +} + +func Example_defineFalsePositivesWhenIdenticalToServerList() { + // When both lists are identical then no changes shall be made + + /* prepare */ + definedFalsePositivesJobData := []FalsePositivesJobData{ + {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}, + {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}, + } + definedFalsePositivesProjectData := []FalsePositivesProjectData{ + {ID: "test1", Comment: "test1", WebScan: FalsePositivesProjectDataForWebScan{CweID: 1, UrlPattern: "https://example1/*", Methods: []string{"GET", "PUT"}}}, + {ID: "test2", Comment: "test2", WebScan: FalsePositivesProjectDataForWebScan{CweID: 2, UrlPattern: "https://example2/*"}}, + } + falsePositivesDefinitionList := FalsePositivesConfig{ + APIVersion: CurrentAPIVersion, + Type: falsePositivesListType, + JobData: definedFalsePositivesJobData, + ProjectData: definedFalsePositivesProjectData, + } + + falsePositivesServerList := []FalsePositiveDefinition{ + {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, + {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, + {ProjectData: FalsePositivesProjectData{ID: "test1", Comment: "test1", WebScan: FalsePositivesProjectDataForWebScan{CweID: 1, UrlPattern: "https://example1/*", Methods: []string{"GET", "PUT"}}}}, + {ProjectData: FalsePositivesProjectData{ID: "test2", Comment: "test2", WebScan: FalsePositivesProjectDataForWebScan{CweID: 2, UrlPattern: "https://example2/*"}}}, + } + + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + + /* test */ + fmt.Printf("Add: %+v\n", falsePositivesToAdd) + fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + + // Output: + // Add: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} } func Example_getFalsePositivesUploadChunk1() { @@ -141,7 +256,103 @@ func Example_getFalsePositivesUploadChunk1() { /* test */ // Output: - // {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:11111111-1111-1111-1111-111111111111 FindingID:1 Comment:test1} {JobUUID:22222222-2222-2222-2222-222222222222 FindingID:2 Comment:test2}]} - // {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:33333333-3333-3333-3333-333333333333 FindingID:3 Comment:test3}]} - // {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[]} + // {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:11111111-1111-1111-1111-111111111111 FindingID:1 Comment:test1} {JobUUID:22222222-2222-2222-2222-222222222222 FindingID:2 Comment:test2}] ProjectData:[]} + // {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[{JobUUID:33333333-3333-3333-3333-333333333333 FindingID:3 Comment:test3}] ProjectData:[]} + // {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} +} + +func Example_newFalsePositivesListFromBytes_jobData() { + /* prepare */ + fpJSON := ` +{ + "apiVersion": "1.0", + "type": "falsePositiveDataList", + "jobData": [ + { + "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", + "findingId": 1, + "comment": "Meaningful comment" + }, + { + "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", + "findingId": 2 + } + ] +} + ` + + inputfile := []byte(fpJSON) + + /* execute */ + fpList := newFalsePositivesListFromBytes(inputfile) + + /* test */ + jsonBlob, _ := json.Marshal(fpList) + fmt.Println(string(jsonBlob)) + + // Output: + // {"apiVersion":"1.0","type":"falsePositiveDataList","jobData":[{"jobUUID":"6cfa2ccf-da13-4dee-b529-0225ed9661bd","findingId":1,"comment":"Meaningful comment"},{"jobUUID":"6cfa2ccf-da13-4dee-b529-0225ed9661bd","findingId":2,"comment":""}],"projectData":null} +} + +func Example_newFalsePositivesListFromBytes_projectData() { + /* prepare */ + fpJSON := ` +{ + "apiVersion": "1.0", + "type": "falsePositiveDataList", + "projectData": [ + { + "id": "my-id", + "comment": "text1", + "webScan": { + "cweId": 89, + "urlPattern": "https://myapp-*.example.com:80*/rest/*/search?*", + "methods": [ "GET", "DELETE" ] + } + } + ] +} + ` + + inputfile := []byte(fpJSON) + + /* execute */ + fpList := newFalsePositivesListFromBytes(inputfile) + + /* test */ + jsonBlob, _ := json.Marshal(fpList) + fmt.Println(string(jsonBlob)) + + // Output: + // {"apiVersion":"1.0","type":"falsePositiveDataList","jobData":null,"projectData":[{"id":"my-id","comment":"text1","webScan":{"cweId":89,"urlPattern":"https://myapp-*.example.com:80*/rest/*/search?*","methods":["GET","DELETE"]}}]} +} + +func Example_newFalsePositivesListFromBytes_projectDataWithoutCWE() { + /* prepare */ + fpJSON := ` +{ + "apiVersion": "1.0", + "type": "falsePositiveDataList", + "projectData": [ + { + "id": "my-id", + "webScan": { + "urlPattern": "https://myapp-*.example.com/rest/login?*" + } + } + ] +} + ` + + inputfile := []byte(fpJSON) + + /* execute */ + fpList := newFalsePositivesListFromBytes(inputfile) + + /* test */ + jsonBlob, _ := json.Marshal(fpList) + fmt.Println(string(jsonBlob)) + + // Output: + // {"apiVersion":"1.0","type":"falsePositiveDataList","jobData":null,"projectData":[{"id":"my-id","comment":"","webScan":{"cweId":0,"urlPattern":"https://myapp-*.example.com/rest/login?*","methods":null}}]} } diff --git a/sechub-cli/src/mercedes-benz.com/sechub/cli/urlbuilder.go b/sechub-cli/src/mercedes-benz.com/sechub/cli/urlbuilder.go index b1cfe1bc78..07ee3877a0 100644 --- a/sechub-cli/src/mercedes-benz.com/sechub/cli/urlbuilder.go +++ b/sechub-cli/src/mercedes-benz.com/sechub/cli/urlbuilder.go @@ -75,6 +75,11 @@ func buildFalsePositiveAPICall(context *Context) string { return buildAPIUrl(&context.config.server, &apiPart) } +func buildFalsePositiveProjectDataAPICall(context *Context) string { + apiPart := fmt.Sprintf("project/%s/false-positive/project-data", context.config.projectID) + return buildAPIUrl(&context.config.server, &apiPart) +} + func buildAPIUrl(server *string, apiPart *string) string { return fmt.Sprintf("%s/api/%s", *server, *apiPart) } diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_definition_overview.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_definition_overview.puml index c67328550f..4924af3afa 100644 --- a/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_definition_overview.puml +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_definition_overview.puml @@ -67,10 +67,7 @@ package com.mercedesbenz.sechub.domain.scan.project{ 'com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData class WebscanFalsePositiveProjectData{ -Integer : cweId - -List : hostPatterns - -List : urlPathPatterns - -List : protocols - -List : ports + -String : urlPattern -List : methods } FalsePositiveProjectData *-- WebscanFalsePositiveProjectData diff --git a/sechub-doc/src/docs/asciidoc/documents/client/02_sechub_client.adoc b/sechub-doc/src/docs/asciidoc/documents/client/02_sechub_client.adoc index 136e4ee1ed..7721c524a8 100644 --- a/sechub-doc/src/docs/asciidoc/documents/client/02_sechub_client.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/client/02_sechub_client.adoc @@ -237,7 +237,7 @@ See also < api version -<2> type - must be `falsePositiveJobDataList` -<3> job-UUID of the report where the finding was -<4> finding ID of finding in the report, which shall be marked as false positive -<5> Comment (optional) describing the reason why this is a false positive +include::../shared/false-positives/false-positives-howto-define-by-api.adoc[] IMPORTANT: `markFalsePositives` only adds new false positives. + Providing an empty list here does *NOT delete* the already declared false positives! + @@ -281,23 +270,30 @@ TIP: See also <> fo [[section-client-false-positives-unmark]] ===== unmarkFalsePositives -Remove formerly defined false positives. + +Remove formerly defined false positives. + It works similar to `markFalsePositives`: Just define a JSON file, select the file by the `-file` argument and start -action `unmarkFalsePositives` +action `unmarkFalsePositives`. **Minimum call syntax** ---- sechub -file ${json-file} unmarkFalsePositives ---- -*Example JSON:* +The JSON scheme is identical to `markFalsePositives` + +Mandatory fields for unmarkFalsePositives: + +- for jobData: `jobUUID` and `findingId` + +- for projectData: `id` + +*Example JSON with both: jobData and projectData* [source, json] ---- -include::sechub_client_falsepositive_list_example_unmark.json[] +include::sechub_client_falsepositive_list_example_unmark_jobData+projectData.json[] ---- -TIP: <> might be much easier to use +TIP: <> might be easier to use ==== Configuration overview diff --git a/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark_jobData+projectData.json b/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark_jobData+projectData.json new file mode 100644 index 0000000000..dea1e98d1d --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark_jobData+projectData.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "1.0", + "type": "falsePositiveDataList", + "jobData": [ + { + "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", + "findingId": 1 + } + ], + "projectData": [ + { + "id": "unique-id" + } + ] +} diff --git a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives.adoc b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives.adoc index 32b5996cbf..9ffd0e77f6 100644 --- a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives.adoc @@ -5,6 +5,6 @@ A user wants to mark false positives either for a finished job or with project d To mark false positives using job data the job must have been executed, finished without failure and job NOT been deleted. The user will be able to mark former job results by their given id as false positives. -To mark false positives using no job must have been run, but it will help identify findings as false positives of course. +To mark false positives using project data no job must have been run, but it will help identify findings as false positives of course. The project data are not related to any job information. diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_falsepositive_handling.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_falsepositive_handling.adoc index 53cde7695c..03a5a6b4e2 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_falsepositive_handling.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/shared/concepts/concept_falsepositive_handling.adoc @@ -35,7 +35,7 @@ Some people prefer code/API-centric way to define false positives, some prefer a [[section-concept-false-positive-general-api-centric]] ====== API centric - + include::../false-positives/false-positives-howto-define-by-api.adoc[] diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example1.json b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example1.json index 2c9796a7bf..ae76e8ae7c 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example1.json +++ b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example1.json @@ -1,29 +1,15 @@ { "apiVersion": "1.0", //<1> "type": "falsePositiveDataList", //<2> - "jobData": [ + "jobData": [ //<3> { - "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", //<3> - "findingId": 1, //<4> - "comment": "Absolute Path Traversal, can be ignored because not in deployment" //<5> + "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", //<4> + "findingId": 1, //<5> + "comment": "Can be ignored because not in deployment" //<6> }, { "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", "findingId": 15 } - ], - "projectData": [ //<6> - { - "id": "unique-id", //<7> - "comment": "It was verified that there is no SQL-injection vulnerability at this location", - "webScan": { //<8> - "cweId": 89, //<9> - "hostPatterns": [ "127.0.*.1", "api.example.com", "dev.*.example.com"], //<10> - "urlPathPatterns": [ "/rest/products/search*", "/rest/users/profile" ], //<11> - "protocols": [ "HTTPS", "WSS" ], //<12> - "methods": [ "GET", "DELETE" ], //<13> - "ports": [ "8080", "443" ] //<14> - } - } ] -} \ No newline at end of file +} diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example2.json b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example2.json new file mode 100644 index 0000000000..d7effb39f4 --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example2.json @@ -0,0 +1,15 @@ +{ + "apiVersion": "1.0", //<1> + "type": "falsePositiveDataList", //<2> + "projectData": [ //<3> + { + "id": "unique-id", //<4> + "comment": "It was verified that there is no SQL-injection", + "webScan": { //<5> + "cweId": 89, //<6> + "urlPattern": "https://*.example.com/rest/products/search?*", //<7> + "methods": [ "GET", "DELETE" ] //<8> + } + } + ] +} diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-howto-define-by-api.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-howto-define-by-api.adoc index 6e1ed3fe94..67b13c629a 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-howto-define-by-api.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-howto-define-by-api.adoc @@ -1,72 +1,66 @@ // SPDX-License-Identifier: MIT [[section-false-positives-define-by-API]] -Define false positive be done sending false positive information via `JSON` either -by referencing a former {sechub} job UUID and the corresponding finding entry (by id) or -by specifying a project data section where specific patterns that match false positive findings are declared and post it to REST API. +Defining false positives is done by declaring false positive information in a `JSON` file -*JSON* +- by referencing *<>* from former {sechub} job UUID and the corresponding finding entry (by id) and/or +- by specifying a *<>* section where specific patterns that match false positive findings are declared -[source,json] ----- -include::false-positives-REST-API-content-example1.json[] ----- -<1> API version -<2> a type identifier for false positives, so it's clear what this file represents -<3> job UUID for which the given identifiers are representative -<4> the finding id (number) in the report -<5> comment _(optional)_ are only to define why this is a false positive. -<6> projectData _(optional)_ that can be used to mark more than a single finding as false positive. -Currently only available for web scans. This is not necessarily bound to a SecHub report, -but it might be easier to create this type of false positive configuration with a SecHub report after a scan. -<7> `id` that identifies this entry. If the same `id` is used again, -the existing false positive entry will be overwritten. The `id` is also mandatory to unmark this entry. -<8> `webScan` _(optional)_ section can be used to define false positive patterns for web scans to provide more possibilities to the user. -<9> `cweId` is used to mark a certain type of finding as false positive. -When handling web scan project data this will be treated as a mandatory field, -but it can be omitted inside this configuration an will then match findings that do not have any `cweId`. -<10> `hostPatterns` are used to specify your hosts this entry shall be used for. This is a mandatory field which needs at least one entry. -Asterisks can be used as wildcards e.g. if you have different environments like '*.example.com', would match anything ending with '.example.com'. -<11> `urlPathPatterns` are also mandatory and there must be at least one entry. -Asterisks can be used here as wildcards as well. This can be useful to ignore random input of the scanner, -e.g. inside query parameters or REST API path variables. -<12> `protocols` _(optional)_ can be used to further restrict the false positive matching, to specific communication protocols, like HTTPS, WSS, etc. -Like any other _optional_ field, if this is missing it is simply ignored. -<13> `methods` _(optional)_ can be used to further restrict the false positive matching, to specific request methods protocols, like GET, POST, etc. -Like any other _optional_ field, if this is missing it is simply ignored. -<14> `ports` _(optional)_ can be used to further restrict the false positive matching, to specific ports protocols. -Like any other _optional_ field, if this is missing it is simply ignored. +and post it to the SecHub server REST API. -There are some important information on the asterisk wildcard approach, regarding web scans: - -. To be a false positive only one entry of each of the lists above must match the finding. -. Specifying wildcards only inside `hostPatterns` or `urlPathPatterns` is not allowed. -. Wildcards are only allowed inside mandatory parts, like `hostPatterns` or `urlPathPatterns`. -. Wildcards tell the false positive handling to match anything until the next NOT wildcard character (asterisk). -. Multiple wildcards can be used in one string. -. No wildcards at the beginning or the end means the beginning or the end of the given part must match exactly otherwise it will not be matched as a false positive. +[NOTE] +==== +The `jobData` approach is very generic and easy to use: It references a SecHub report. Every false-positive in any kind of scan can be handled like that. +==== [NOTE] ==== -The `jobData` approach is very easy, generic - and also future-proof: The only dependency is to the job, -`UUID`, for which the report must still exist while the definition is done. Every false-positive in -any kind of scan can be handled like that. +The `projectData` approach is more powerful for the user because wildcards can be used. + +Each entry can be updated or removed by the given `id`. + +Declaring a projectData entry with an already existing `id`, will update its content with the new data. +==== -The `REST` controller logic does read the job result data and creates internally false positive -meta data. If we delete the {sechub} job later it cannot destroy our false positive setup in {sechub}. +[[section-false-positives-defined-via-jobData]] +*Example JSON using job results* -The `projectData` approach is more powerful for the user. -Since it is more powerful with the wildcard approach it requires more intial setup from the user. +[source,json] +---- +include::false-positives-REST-API-content-example1.json[] +---- +<1> `apiVersion` _(mandatory)_ - API version +<2> `type` _(mandatory)_ - must be `falsePositiveDataList` +<3> `jobData` - List of job data that is used to mark a single finding as a false positive +<4> jobData.`jobUUID` _(mandatory)_ SecHub Job-UUID of the report where the finding was +<5> jobData.`findingId` _(mandatory)_ Finding ID which shall be marked as false positive +<6> jobData.`comment` _(optional)_ A comment describing the reason why this is a false positive -There are no dependencies because all information necessary to identify certain findings are specified via `REST`. -Each entry can be overridden or removed by the given `id`. -==== -*ID handling* + +[[section-false-positives-defined-via-projectData]] +*Example JSON using project data* -We must ensure identifiers are always correct and continue even for false positives. -An example: +[source,json] +---- +include::false-positives-REST-API-content-example2.json[] +---- +<1> `apiVersion` _(mandatory)_ - API version +<2> `type` _(mandatory)_ - must be `falsePositiveDataList` +<3> `projectData` - List that can be used to mark more than a single finding as a false positive. Currently only available for web scans. +<4> projectData.`id` that identifies this entry. If the same `id` is used again, +the existing false positive entry will be overwritten. The `id` is also mandatory to unmark this entry. +<5> projectData.`webScan` _(optional)_ section can be used to define false positive patterns for web scans (DAST). It provides more possibilities to the user than above jobData. +<6> projectData.webScan.`cweId` is used to mark a certain type of finding as false positive. + +When handling web scan project data this will be treated as a _mandatory_ field. + +Please insert here the cweId from the original report. + +If there was no cweId in the original report, then it must be omitted or set to zero `"cweId": 0`. +<7> projectData.webScan.`urlPattern` (_mandatory_) specifies an URL pattern to identify a false positive. +Asterisks can be used as wildcards e.g. if you have different environments like DEV, INT, PROD or you have variable parts like in API calls or query paramaters `https://*.example.com/rest/*/search?*`. +<8> projectData.webScan.`methods` _(optional)_ Can be used to further restrict the false positive matching, to specific request methods protocols, like GET, POST, etc. -We have a {sechub} job 1 were we mark the first finding with id 1 as a false positve. -Executing now {sechub} job 2 finding with id 1 must be filtered. But findings may no longer -start with identifier 1! Because the finding already exists. +Important information on the wildcard approach in `projectData`, regarding web scans: + +- To be marked as a false positive a finding must match the given `cweId` and the `urlPattern` + +- Wildcards (`pass:[*]`) can be used inside `urlPattern`. + +- Wildcards match anything until the next NON-wildcard character. + +- Multiple wildcards can be used in one `urlPattern`. + +- An _optional_ list of (HTTP) methods can be specified to limit the false positive entry to certain `methods`, e.g if you specify `"methods": [ "GET", "DELETE" ]` like in the example above that means even if the `cweId` and the `urlPattern` are matching, if the finding was found with a `POST` request it would not be a false positive. When leaving `methods` out, this false positive entry apply to any method. + +- An `urlPattern` which contains only wildcards (`pass:[*]`) is not allowed. diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-technical-details.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-technical-details.adoc index 564861e560..b6ec739884 100644 --- a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-technical-details.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-technical-details.adoc @@ -27,7 +27,7 @@ This approach requires more effort from a user to provide the data which identif But it can be very helpful especially for web scans, where some finding parts dynamically change, like scanner payloads in query parameters or URL paths. ==== -At this phase duplicate checks are only done for tuples of report UUID and finding id! The second approach with the additional projectData works a differently, since entries with the same `id` will be updated! + +At this phase duplicate checks are done for tuples of report UUID and finding id for the first approach. For the projectData approach duplicates are detect by the `id` the user provided! + *Meta data is only collected in definition phase, but NOT inspected!* ! [NOTE] @@ -46,7 +46,7 @@ On the second approach with the additional data for the project: - A user has specific type of finding that are all false positives - The user marks all of them by providing information to identify these false positives -- One false positive entry with this approach can be used to mark a gruop of false positives. +- One false positive entry with this approach can be used to mark a group of false positives. ==== @@ -63,35 +63,31 @@ The entry does contain a `FalsePositiveProjectConfiguration` object as JSON. ====== Data structure The `FalsePositiveProjectConfiguration` object contains a list of `FalsePositiveEntry` objects. -Every `FalsePositveEntry` object contains either +Every `FalsePositveEntry` object contains either `FalsePositiveJobData` with `FalsePositiveMetaData` or `FalsePositiveProjectData`, +but never both in one `FalsePositveEntry`. - `FalsePositiveJobData` + contains job uuid, finding id and comment - this information is provided by user. -and - - `FalsePositiveMetaData` + contains meta information about findings - this information is gathered and calculated by {sechub} internally when a user has marked a report finding as a false positive. So the meta information is independent (so when a reoprt has been deleted, we still have the false positive meta information). - - - It contains many meta information - e.g. a `cweId` - but also `FalsePositiveCodeMetaData` for code scans details. + * It contains many meta information - e.g. a `cweId` - but also `FalsePositiveCodeMetaData` for code scans details. -or - `FalsePositiveProjectData` + contains information to identify a finding or a group of findings as false positives. Currently it can only be used for web scans. In <> the example shows all __mandatory__ and __optional__ parameters. -but never both in one `FalsePositveEntry`. - + ====== Definition by user A user does define a `FalsePositiveDataList` object which contains a list of `FalsePositiveJobData` or `FalsePositiveProjectData`. Such a list will be used to add false positives. To remove a `FalsePositiveJobData` entry from the `FalsePositiveProjectConfiguration`, the user has to provide the job UUID and finding id already used to define this entry. -To remove a `FalsePositiveJobData` entry from the `FalsePositiveProjectConfiguration`, the user has to provide the id defined with the corresponding entry. +To remove a `FalsePositiveProjectData` entry from the `FalsePositiveProjectConfiguration`, the user has to provide the `id` defined with the corresponding entry. ====== Merging When a user adds or removes false positive definitions, the `FalsePositiveProjectConfiguration` will be updated by diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java index 5bc35dcb86..17a7b24f30 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/FalsePositiveRestControllerRestDocTest.java @@ -119,10 +119,7 @@ public void restdoc_mark_false_positives() throws Exception { WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); webScan.setCweId(564); webScan.setMethods(List.of("GET", "POST")); - webScan.setPorts(List.of("8443", "8080")); - webScan.setProtocols(List.of("HTTP", "HTTPS")); - webScan.setHostPatterns(List.of("sub.host.com", "*.other.host.com")); - webScan.setUrlPathPatterns(List.of("/rest/api/project/*", "/other/rest/api/")); + webScan.setUrlPattern("https://*.example.com/api/*/search"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); projectData.setId("unique-identifier"); @@ -166,11 +163,8 @@ public void restdoc_mark_false_positives() throws Exception { fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ FalsePositiveProjectData.PROPERTY_COMMENT).optional().description("A comment describing why this is a false positive."), fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN).optional().description("Defines a section for false positives which occur during webscans."), - fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN+"."+ PROPERTY_HOSTPATTERNS+"[]").description("Defines a list of host patterns for false positives which occur during webscans. At least one entry must be present. Can be used with wildcards like '*.host.com'. Each entry must contain more than just wildcards, '*.*.*' or '*' are not allowed."), - fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN+"."+ PROPERTY_URLPATHPATTERNS+"[]").description("Defines a list of urlPathPatterns for false positives which occur during webscans which make it easier e.g. to ignore query parameters. At least one entry must be present. Can be used with wildcards like '*/api/users/*'. Each entry must contain more than just wildcards, '*/*/*' or '*' are not allowed."), + fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN+"."+ PROPERTY_URLPATTERN).description("Defines a url pattern for false positives which occur during webscans. Can be used with wildcards like '*.host.com'. Each entry must contain more than just wildcards, '*.*.*' or '*' are not allowed."), fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN+"."+ PROPERTY_METHODS+"[]").optional().description("Defines a list of (HTTP) methods for false positives which occur during webscans. This is optional and if nothing is specified, the entry applies to all methods."), - fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN+"."+ PROPERTY_PORTS+"[]").optional().description("Defines a list of server ports for false positives which occur during webscans. This is optional and if nothing is specified, the entry applies to all server ports."), - fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN+"."+ PROPERTY_PROTOCOLS+"[]").optional().description("Defines a list of web request protocols like 'http', 'https', 'wss' for false positives which occur during webscans. This is optional and if nothing is specified, the entry applies to all protocols."), fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_WEBSCAN+"."+ PROPERTY_CWEID).description("Defines a CWE ID for false positives which occur during webscans. This is mandatory, but can be empty. If it is not specified it matches the findings with no CWE IDs.") ), pathParameters( @@ -297,10 +291,7 @@ public void user_fetches_false_positive_configuration() throws Exception { WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); webScan.setCweId(564); webScan.setMethods(List.of("GET", "POST")); - webScan.setPorts(List.of("8443", "8080")); - webScan.setProtocols(List.of("HTTP", "HTTPS")); - webScan.setHostPatterns(List.of("sub.host.com", "*.other.host.com")); - webScan.setUrlPathPatterns(List.of("/rest/api/project/*", "/other/rest/api/")); + webScan.setUrlPattern("https://*.example.com/api/*/search"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); projectData.setId("unique-identifier"); @@ -367,11 +358,8 @@ public void user_fetches_false_positive_configuration() throws Exception { fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ FalsePositiveProjectData.PROPERTY_COMMENT).optional().description("A comment describing why this is a false positive."), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN).optional().description("Defines a section for false positives which occur during webscans."), - fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN+"."+ PROPERTY_HOSTPATTERNS+"[]").description("Defines a list of host patterns for false positives which occur during webscans. At least one entry must be present. Can be used with wildcards like '*.host.com'. Each entry must contain more than just wildcards, '*.*.*' or '*' are not allowed."), - fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN+"."+ PROPERTY_URLPATHPATTERNS+"[]").description("Defines a list of urlPathPatterns for false positives which occur during webscans which make it easier e.g. to ignore query parameters. At least one entry must be present. Can be used with wildcards like '*/api/users/*'. Each entry must contain more than just wildcards, '*/*/*' or '*' are not allowed."), + fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN+"."+ PROPERTY_URLPATTERN).description("Defines a url pattern for false positives which occur during webscans. Can be used with wildcards like '*.host.com'. Each entry must contain more than just wildcards, '*.*.*' or '*' are not allowed."), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN+"."+ PROPERTY_METHODS+"[]").optional().description("Defines a list of (HTTP) methods for false positives which occur during webscans. This is optional and if nothing is specified, the entry applies to all methods."), - fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN+"."+ PROPERTY_PORTS+"[]").optional().description("Defines a list of server ports for false positives which occur during webscans. This is optional and if nothing is specified, the entry applies to all server ports."), - fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN+"."+ PROPERTY_PROTOCOLS+"[]").optional().description("Defines a list of web request protocols like 'http', 'https', 'wss' for false positives which occur during webscans. This is optional and if nothing is specified, the entry applies to all protocols."), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_WEBSCAN+"."+ PROPERTY_CWEID).description("Defines a CWE ID for false positives which occur during webscans. This is mandatory, but can be empty. If it is not specified it matches the findings with no CWE IDs.") ), pathParameters( diff --git a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java index 2f3039c987..12c5ba7d51 100644 --- a/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java +++ b/sechub-integrationtest/src/test/java/com/mercedesbenz/sechub/integrationtest/scenario21/PDSSolutionMockModeScenario21IntTest.java @@ -8,7 +8,6 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Iterator; -import java.util.List; import java.util.Set; import java.util.UUID; @@ -117,8 +116,7 @@ public void pds_solution_zap_mocked_report_REST_API_direct_mark_and_unmark_false WebscanFalsePositiveProjectData webscan = new WebscanFalsePositiveProjectData(); // we set only mandatory parameters webscan.setCweId(89); - webscan.setHostPatterns(List.of("localhost")); - webscan.setUrlPathPatterns(List.of("/rest/products/search*")); + webscan.setUrlPattern("http://localhost:3000/rest/products/search*"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); String projectDataId = "6a7fe94a-564f-11ef-87de-3f13a69f3e5d"; diff --git a/sechub-openapi-java/src/main/resources/openapi.yaml b/sechub-openapi-java/src/main/resources/openapi.yaml index 97d53cceb5..e88679284c 100644 --- a/sechub-openapi-java/src/main/resources/openapi.yaml +++ b/sechub-openapi-java/src/main/resources/openapi.yaml @@ -1255,43 +1255,20 @@ components: description: CWE ID for a category of findings to identify the type of false positive finding type: integer format: int32 - hostPatterns: + urlPattern: required: true description: list of host name or ip address patterns this false positive entry shall match (can contain wildcards) - type: array - items: - type: string - urlPathPatterns: - required: true - description: list of URL path patterns this false positive entry shall match (can contain wildcards) - type: array - items: - type: string - protocols: - required: false - description: list of protocols this false positive entry shall match (does not use wildcards) - type: array - items: - type: string + type: string methods: required: false description: list of (HTTP) methods this false positive entry shall match (does not use wildcards) type: array items: type: string - ports: - required: false - description: list of ports this false positive entry shall match (does not use wildcards) - type: array - items: - type: string example: cweId: 79 - hostPatterns: ["api.example.com", "*.my-example.com", "127.0.*.1"] - urlPathPatterns: ["/rest/api/users/*", "/rest/api/project/alpha"] - protocols: ["HTTPS", "WSS", "HTTP"] - methods: ["GET", "DELETE"] - ports: ["8080", "9090"] + urlPattern: https://*.example.com/api/*/search + methods: [GET, DELETE] FalsePositiveProjectData: @@ -1313,11 +1290,8 @@ components: comment: comment webScan: cweId: 79 - hostPatterns: ["api.example.com", "*.my-example.com", "127.0.*.1"] - urlPathPatterns: ["/rest/api/users/*", "/rest/api/project/alpha"] - protocols: ["HTTPS", "WSS", "HTTP"] - methods: ["GET", "DELETE"] - ports: ["8080", "9090"] + urlPattern: https://*.example.com/api/*/search + methods: [GET, DELETE] FalsePositiveEntry: title: FalsePositiveEntry @@ -1362,11 +1336,8 @@ components: comment: comment webScan: cweId: 79 - hostPatterns: ["api.example.com", "*.my-example.com", "127.0.*.1"] - urlPathPatterns: ["/rest/api/users/*", "/rest/api/project/alpha"] - protocols: ["HTTPS", "WSS", "HTTP"] - methods: ["GET", "DELETE"] - ports: ["8080", "9090"] + urlPattern: https://*.example.com/api/*/search + methods: [GET, DELETE] created: created FalsePositiveProjectConfiguration: @@ -1429,11 +1400,8 @@ components: comment: comment webScan: cweId: 79 - hostPatterns: ["api.example.com", "*.my-example.com", "127.0.*.1"] - urlPathPatterns: ["/rest/api/users/*", "/rest/api/project/alpha"] - protocols: ["HTTPS", "WSS", "HTTP"] - methods: ["GET", "DELETE"] - ports: ["8080", "9090"] + urlPattern: https://*.example.com/api/*/search + methods: [GET, DELETE] created: created author: author diff --git a/sechub-pds-solutions/gitleaks/env b/sechub-pds-solutions/gitleaks/env index f5463a23b6..fbf5863ce3 100644 --- a/sechub-pds-solutions/gitleaks/env +++ b/sechub-pds-solutions/gitleaks/env @@ -15,4 +15,4 @@ BUILD_TYPE=download # The Secret-Validation Wrapper version to use # See: https://github.com/mercedes-benz/sechub/releases -SECRETVALIDATION_WRAPPER_VERSION="1.0.0" +SECRETVALIDATION_WRAPPER_VERSION="1.1.0" diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactory.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactory.java index 71a0d8711f..bf5da02914 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactory.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactory.java @@ -13,7 +13,6 @@ import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveEntry; import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; -import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; @Component public class SerecoProjectDataPatternMapFactory { @@ -23,43 +22,25 @@ public class SerecoProjectDataPatternMapFactory { * compile regex patterns. * * @param falsePositives - * @return unmodifiable map with projectData wildcard entries as key and the - * created regex pattern as value or an empty map if no projectData - * where found. + * @return unmodifiable map with projectData ids as key and the regex pattern + * for the urlPattern as value or an empty map if no projectData where + * found. */ public Map create(List falsePositives) { notNull(falsePositives, " falsePositives may not be null"); + Map patternMap = new HashMap<>(); for (FalsePositiveEntry falsePositiveEntry : falsePositives) { FalsePositiveProjectData projectData = falsePositiveEntry.getProjectData(); if (projectData != null && projectData.getWebScan() != null) { - patternMap.putAll(createMapFromProjectDataWebScan(projectData.getWebScan())); + String id = projectData.getId(); + Pattern pattern = createCompiledPattern(projectData.getWebScan().getUrlPattern()); + patternMap.put(id, pattern); } } return Collections.unmodifiableMap(patternMap); } - private Map createMapFromProjectDataWebScan(WebscanFalsePositiveProjectData webScan) { - List hostPatterns = webScan.getHostPatterns(); - List urlPathPatterns = webScan.getUrlPathPatterns(); - notNull(hostPatterns, " hostPatterns may not be null"); - notNull(urlPathPatterns, " urlPathPatterns may not be null"); - - int mapSize = hostPatterns.size() + urlPathPatterns.size(); - Map patternMap = new HashMap<>(mapSize); - - for (String hostPattern : hostPatterns) { - Pattern compiledPattern = createCompiledPattern(hostPattern); - patternMap.put(hostPattern, compiledPattern); - } - - for (String urlPathPattern : urlPathPatterns) { - Pattern compiledPattern = createCompiledPattern(urlPathPattern); - patternMap.put(urlPathPattern, compiledPattern); - } - return patternMap; - } - private Pattern createCompiledPattern(String regexString) { String escaped = Pattern.quote(regexString); String pattern = escaped.replace("*", "\\E.*\\Q"); diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupport.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupport.java index 43a9c96662..cf2e6bd787 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupport.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupport.java @@ -7,8 +7,6 @@ import java.util.Map; import java.util.regex.Pattern; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; @@ -18,18 +16,16 @@ @Component public class SerecoProjectDataWebScanFalsePositiveSupport { - private static final Logger LOG = LoggerFactory.getLogger(SerecoProjectDataWebScanFalsePositiveSupport.class); - public boolean areBothHavingSameCweIdOrBothNoCweId(WebscanFalsePositiveProjectData webScanData, SerecoVulnerability vulnerability) { notNull(vulnerability, " vulnerability may not be null"); notNull(webScanData, " webscanProjectData may not be null"); - Integer cweIdOrNull = webScanData.getCweId(); + int cweId = webScanData.getCweId(); SerecoClassification serecoClassification = vulnerability.getClassification(); String serecoCWE = serecoClassification.getCwe(); if (serecoCWE == null || serecoCWE.isEmpty()) { - if (cweIdOrNull == null) { + if (cweId == 0) { /* * when not set in meta data and also not in vulnerability, than we assume it is * the same @@ -38,85 +34,34 @@ public boolean areBothHavingSameCweIdOrBothNoCweId(WebscanFalsePositiveProjectDa } return false; } - if (cweIdOrNull == null) { - return false; - } - try { - int serecoCWEint = Integer.parseInt(serecoCWE); - if (cweIdOrNull.intValue() != serecoCWEint) { - /* not same type of common vulnerability enumeration - so skip */ - return false; - } - - } catch (NumberFormatException e) { - LOG.error("Sereco vulnerability type:{} found CWE:{} but not expected integer format!", vulnerability.getType(), serecoCWE); - return false; - - } - return true; - } - - /** - * Iterates the given list of hostPatterns and uses each string value as key to - * get the corresponding compiled pattern from the given map. Then tries to - * match the given host against each pattern of the projectDataPatternMap. - * - * @param host - * @param hostPatterns - * @param projectDataPatternMap - * @return true if the given host matches any of the given patterns - */ - public boolean isMatchingHostPattern(String host, List hostPatterns, Map projectDataPatternMap) { - notNull(host, " host may not be null"); - notNull(hostPatterns, " hostPatterns may not be null"); - notNull(projectDataPatternMap, " projectDataPatternMap may not be null"); - - return isMatchingAnyPattern(host, hostPatterns, projectDataPatternMap); + String cweIdAsString = String.valueOf(cweId); + return cweIdAsString.equals(serecoCWE); } /** - * Iterates the given list of urlPathPatterns and uses each string value as key - * to get the corresponding compiled pattern from the given map. Then tries to - * match the given host against each pattern of the projectDataPatternMap. + * Then tries to match the given targetUrl against each pattern of the + * projectDataPatternMap. * - * @param urlFilePart - * @param urlPathPatterns + * @param targetUrl * @param projectDataPatternMap - * @return true if the given urlFilePart matches any of the given - * patterns + * @return true if the given URL matches any of the given patterns */ - public boolean isMatchingUrlPathPattern(String urlFilePart, List urlPathPatterns, Map projectDataPatternMap) { - notNull(urlFilePart, " urlFilePart may not be null"); - notNull(urlPathPatterns, " urlPathPatterns may not be null"); + public boolean isMatchingUrlPattern(String targetUrl, Map projectDataPatternMap) { + notNull(targetUrl, " host may not be null"); notNull(projectDataPatternMap, " projectDataPatternMap may not be null"); - return isMatchingAnyPattern(urlFilePart, urlPathPatterns, projectDataPatternMap); - } - - /** - * Checks if the given port is inside the list of ports. - * - * @param port - * @param ports - * @return true if the given port is inside ports or ports is empty - * or null - */ - public boolean isMatchingPortOrIgnoreIfNotSet(String port, List ports) { - notNull(port, " port may not be null"); - return listContainsTrimmedStringIgnoreCase(port.trim(), ports); - } - - /** - * Checks if the given protocol is inside the list of protocols. - * - * @param protocol - * @param protocols - * @return true if the given protocol is inside protocols or - * protocols is empty or null - */ - public boolean isMatchingProtocolOrIgnoreIfNotSet(String protocol, List protocols) { - notNull(protocol, " protocol may not be null"); - return listContainsTrimmedStringIgnoreCase(protocol.trim(), protocols); + for (String id : projectDataPatternMap.keySet()) { + Pattern pattern = projectDataPatternMap.get(id); + if (pattern == null) { + // At this point this should never happen because the map is meant to be created + // by the associated projectData + throw new IllegalStateException("Project data wildcard pattern for id: %s was not part of the pattern map.".formatted(id)); + } + if (pattern.matcher(targetUrl).matches()) { + return true; + } + } + return false; } /** @@ -132,21 +77,6 @@ public boolean isMatchingMethodOrIgnoreIfNotSet(String method, List meth return listContainsTrimmedStringIgnoreCase(method.trim(), methods); } - private boolean isMatchingAnyPattern(String stringToMatch, List wildcardPatternList, Map projectDataPatternMap) { - for (String patternEntryKey : wildcardPatternList) { - Pattern pattern = projectDataPatternMap.get(patternEntryKey); - if (pattern == null) { - // At this point this should never happen because the map is meant to be created - // by the associated projectData - throw new IllegalStateException("Project data wildcard pattern: %s was not part of the pattern map.".formatted(patternEntryKey)); - } - if (pattern.matcher(stringToMatch).matches()) { - return true; - } - } - return false; - } - private boolean listContainsTrimmedStringIgnoreCase(String trimmedString, List list) { if (list == null || list.isEmpty()) { return true; diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategy.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategy.java index a517633064..ac6b7c6e53 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategy.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategy.java @@ -3,8 +3,6 @@ import static com.mercedesbenz.sechub.sharedkernel.util.Assert.notNull; -import java.net.MalformedURLException; -import java.net.URL; import java.util.Map; import java.util.regex.Pattern; @@ -64,34 +62,15 @@ public boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveP return false; } - String target = vulnerabilityWeb.getRequest().getTarget(); - if (target == null) { - return false; - } - - URL targetUrl = null; - try { - targetUrl = new URL(target.trim()); - } catch (MalformedURLException e) { - LOG.debug("Sereco vulnerability webscan target URL: {} is not a valid URL!", target, e); + String targetUrl = vulnerabilityWeb.getRequest().getTarget(); + if (targetUrl == null) { return false; } /* ---------------------------------------------------- */ - /* -------------------SERVERS-------------------------- */ + /* ----------------------URL--------------------------- */ /* ---------------------------------------------------- */ - String host = targetUrl.getHost(); - if (!webscanFalsePositiveProjectDataSupport.isMatchingHostPattern(host, webScanData.getHostPatterns(), projectDataPatternMap)) { - return false; - } - - /* ---------------------------------------------------- */ - /* -------------------URL PATTERNS--------------------- */ - /* ---------------------------------------------------- */ - // Using targetUrl.getFile() returns the path+query, maybe getPath() without - // query would be better - String urlFilePart = targetUrl.getFile(); - if (!webscanFalsePositiveProjectDataSupport.isMatchingUrlPathPattern(urlFilePart, webScanData.getUrlPathPatterns(), projectDataPatternMap)) { + if (!webscanFalsePositiveProjectDataSupport.isMatchingUrlPattern(targetUrl, projectDataPatternMap)) { return false; } @@ -103,23 +82,6 @@ public boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveP return false; } - /* ---------------------------------------------------- */ - /* -------------------PORTS---------------------------- */ - /* ---------------------------------------------------- */ - int targetUrlPort = targetUrl.getPort(); - String port = targetUrlPort != -1 ? "" + targetUrlPort : "" + targetUrl.getDefaultPort(); - if (!webscanFalsePositiveProjectDataSupport.isMatchingPortOrIgnoreIfNotSet(port, webScanData.getPorts())) { - return false; - } - - /* ---------------------------------------------------- */ - /* -------------------PROTOCOLS------------------------ */ - /* ---------------------------------------------------- */ - String protocol = vulnerabilityWeb.getRequest().getProtocol(); - if (!webscanFalsePositiveProjectDataSupport.isMatchingProtocolOrIgnoreIfNotSet(protocol, webScanData.getProtocols())) { - return false; - } - return true; } diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarkerTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarkerTest.java index 25ddd7632a..e1b874e735 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarkerTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarkerTest.java @@ -183,8 +183,7 @@ private SerecoVulnerability addVulnerability(List all, Scan private FalsePositiveProjectData addEntryProjectDataWithWebscanAndReturnProjectData(FalsePositiveProjectConfiguration projectConfig) { WebscanFalsePositiveProjectData webscan = new WebscanFalsePositiveProjectData(); - webscan.setHostPatterns(new ArrayList<>()); - webscan.setUrlPathPatterns(new ArrayList<>()); + webscan.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); projectData.setWebScan(webscan); diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactoryTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactoryTest.java index f1c65dd1cc..6e163fb350 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactoryTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactoryTest.java @@ -62,187 +62,20 @@ void factory_returns_empty_map_if_list_of_false_positives_only_contains_jobData_ assertTrue(map.isEmpty()); } - @Test - void factory_throws_illegal_argument_exception_if_list_of_projectData_false_positives_contains_webscan_with_null_hostPatterns() { - /* prepare */ - List falsePositives = new ArrayList<>(); - - FalsePositiveEntry entry = new FalsePositiveEntry(); - FalsePositiveProjectData projectData = new FalsePositiveProjectData(); - WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(new ArrayList<>()); - projectData.setWebScan(webScan); - entry.setProjectData(projectData); - falsePositives.add(entry); - - /* execute + test */ - // This should never happen, because invalid entries should never get inside the - // database - assertThrows(IllegalArgumentException.class, () -> factoryToTest.create(falsePositives)); - } - - @Test - void factory_throws_illegal_argument_exception_if_list_of_projectData_false_positives_contains_webscan_with_null_urlPathPatterns() { - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(null, null); - - /* execute + test */ - // This should never happen, because invalid entries should never get inside the - // database - assertThrows(IllegalArgumentException.class, () -> factoryToTest.create(falsePositives)); - } - @Test void for_projectData_webscan_factory_returns_expected_map_with_patterns_for_each_mandatory_that_parameter() { /* prepare */ - List urlPathPatterns = new ArrayList<>(); - urlPathPatterns.add("/rest/api/*/users"); - urlPathPatterns.add("*/rest/api*"); - urlPathPatterns.add("/rest/api/profile/users"); - - List hostPatterns = new ArrayList<>(); - hostPatterns.add("127.0.0.1"); - hostPatterns.add("*localhost*"); - hostPatterns.add("prod.example.com"); - hostPatterns.add("*.example.com"); - hostPatterns.add("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"); - hostPatterns.add("*.*.example.com*"); - - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostPatterns); - - /* execute */ - Map patternMap = factoryToTest.create(falsePositives); - - /* test */ - assertEquals("^\\Q/rest/api/\\E.*\\Q/users\\E$", patternMap.get("/rest/api/*/users").toString()); - assertEquals("^.*\\Q/rest/api\\E.*$", patternMap.get("*/rest/api*").toString()); - assertEquals("^\\Q/rest/api/profile/users\\E$", patternMap.get("/rest/api/profile/users").toString()); - - assertEquals("^\\Q127.0.0.1\\E$", patternMap.get("127.0.0.1").toString()); - assertEquals("^.*\\Qlocalhost\\E.*$", patternMap.get("*localhost*").toString()); - assertEquals("^\\Qprod.example.com\\E$", patternMap.get("prod.example.com").toString()); - assertEquals("^.*\\Q.example.com\\E$", patternMap.get("*.example.com").toString()); - assertEquals("^\\Q2001:db8:ffff:ffff:ffff:ffff:ffff:ffff\\E$", patternMap.get("2001:db8:ffff:ffff:ffff:ffff:ffff:ffff").toString()); - assertEquals("^.*\\Q.\\E.*\\Q.example.com\\E.*$", patternMap.get("*.*.example.com*").toString()); - } - - /*---------------------------TEST THE CREATED HOST PATTERNS FOR EXPECTED BEHAVIOUR---------------------------------*/ - - @Test - void host_patterns_ipv4_without_wildcards_match_expected_strings() { - /* prepare */ - List urlPathPatterns = new ArrayList<>(); - - String ipv4PatternAsString = "127.0.0.1"; - List hostPatterns = List.of(ipv4PatternAsString); - - List mustMatchHostnames = List.of(ipv4PatternAsString); - List mustNotMatchHostnames = List.of("127.0.0.2", "127.0.0.11", "1127.0.0.1", "localhost"); - - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostPatterns); - - /* execute */ - Map patternMap = factoryToTest.create(falsePositives); - - /* test */ - Pattern pattern = patternMap.get(ipv4PatternAsString); - - assertMatches(mustMatchHostnames, pattern); - assertDoesNotMatch(mustNotMatchHostnames, pattern); - } - - @Test - void host_patterns_hostname_without_wildcards_match_expected_strings() { - /* prepare */ - List urlPathPatterns = new ArrayList<>(); - - String hostnamePattern = "example.com"; - List hostPatterns = List.of(hostnamePattern); - - List mustMatchHostnames = List.of(hostnamePattern); - List mustNotMatchHostnames = List.of("api.example.cor", "api.example.comm", "aapi.example.com", "api.example.com"); - - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostPatterns); - - /* execute */ - Map patternMap = factoryToTest.create(falsePositives); - - /* test */ - Pattern pattern = patternMap.get(hostnamePattern); - - assertMatches(mustMatchHostnames, pattern); - assertDoesNotMatch(mustNotMatchHostnames, pattern); - } - - @Test - void host_patterns_ipv6_without_wildcards_match_expected_strings() { - /* prepare */ - List urlPathPatterns = new ArrayList<>(); - - String ipv6PatternAsString = "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"; - List hostPatterns = List.of(ipv6PatternAsString); - - List mustMatchHostnames = List.of(ipv6PatternAsString); - List mustNotMatchHostnames = List.of("2001:db8:ffff:ffff:ffff:ffff:ffff:fffa", "2001:db8:ffff:ffff:ffff:ffff:ffff:fffff", - "22001:db8:ffff:ffff:ffff:ffff:ffff:ffff", "::::"); - - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostPatterns); - - /* execute */ - Map patternMap = factoryToTest.create(falsePositives); - - /* test */ - Pattern pattern = patternMap.get(ipv6PatternAsString); - - assertMatches(mustMatchHostnames, pattern); - assertDoesNotMatch(mustNotMatchHostnames, pattern); - } - - @Test - void host_patterns_with_wildcards_match_expected_strings() { - /* prepare */ - List urlPathPatterns = new ArrayList<>(); - - String hostPattern = "*.*.0.1"; - List hostPatterns = List.of(hostPattern); + String projectDataId = "unique"; + String urlPattern = "https://myapp-*.example.com:80*/rest/*/search?*"; - List mustMatchHostnames = List.of("127.0.0.1", "127..0..0.1", "192.89.0.1", "1127.0.0.1", "prod.host.0.1", "prod1.host3.0.1", - "longer-host.name-for-testing.0.1"); - List mustNotMatchHostnames = List.of("127.0.0.2", "127.0.0.11", "localhost", "127.0.1.1", "127.0.0..1"); - - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostPatterns); + List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(projectDataId, urlPattern); /* execute */ Map patternMap = factoryToTest.create(falsePositives); /* test */ - Pattern pattern = patternMap.get(hostPattern); + assertEquals("^\\Qhttps://myapp-\\E.*\\Q.example.com:80\\E.*\\Q/rest/\\E.*\\Q/search?\\E.*$", patternMap.get(projectDataId).toString()); - assertMatches(mustMatchHostnames, pattern); - assertDoesNotMatch(mustNotMatchHostnames, pattern); - } - - @Test - void host_patterns_pv6_separator_with_wildcards_match_expected_strings() { - /* prepare */ - List urlPathPatterns = new ArrayList<>(); - - String hostPattern = "2001:*:ffff:ffff:ffff:ffff:*:*"; - List hostPatterns = List.of(hostPattern); - - List mustMatchHostnames = List.of("2001::db8:ffff:ffff:ffff:ffff:ffff:fffa", "2001:db8:ffff:ffff:ffff:ffff:ffff:fffff", - "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"); - List mustNotMatchHostnames = List.of("::::", "2001:db8:ffff:ffff::ffff:ffff:ffff:fffff", "2001:db8:ffff:ffff::ffff:ffff::ffff:fffff"); - - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostPatterns); - - /* execute */ - Map patternMap = factoryToTest.create(falsePositives); - - /* test */ - Pattern pattern = patternMap.get(hostPattern); - - assertMatches(mustMatchHostnames, pattern); - assertDoesNotMatch(mustNotMatchHostnames, pattern); } /*---------------------------TEST THE CREATED URL PATH PATTERNS FOR EXPECTED BEHAVIOUR---------------------------------*/ @@ -250,59 +83,31 @@ void host_patterns_pv6_separator_with_wildcards_match_expected_strings() { @Test void url_path_patterns_without_wildcards_match_expected_strings() { /* prepare */ - List hostNames = new ArrayList<>(); - - String urlPathPattern = "/rest/api/user/profile"; - List urlPathPatterns = List.of(urlPathPattern); - - List mustMatchUrlPaths = List.of(urlPathPattern); - List mustNotMatchUrlPaths = List.of("a/rest/api/user/profile", "/rest/api/user/profile/", "/rest/api/user/profile/b", "/rest/api/user/profilee", - "//rest/api/user/profile"); + String projectDataId = "unique"; + String urlPattern = "https://myapp-*.example.com:80*/rest/*/search?*"; - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostNames); + List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(projectDataId, urlPattern); /* execute */ Map patternMap = factoryToTest.create(falsePositives); /* test */ - Pattern pattern = patternMap.get(urlPathPattern); + Pattern pattern = patternMap.get(projectDataId); - assertMatches(mustMatchUrlPaths, pattern); - assertDoesNotMatch(mustNotMatchUrlPaths, pattern); - } + assertTrue(pattern.matcher("https://myapp-dev.example.com:80*/rest/profile/search?q=test").matches()); - @Test - void url_path_patterns_with_wildcards_match_expected_strings() { - /* prepare */ - List hostNames = new ArrayList<>(); - - String urlPathPattern = "*/rest/api/*/profile"; - List urlPathPatterns = List.of(urlPathPattern); - - List mustMatchUrlPaths = List.of("a/rest/api/user/profile", "//rest/api/user/profile", "/rest/api/user12/profile", "dev/rest/api/user/profile"); - List mustNotMatchUrlPaths = List.of("/rest/api/user/profile/", "/rest/api/user/profile/b", "/rest/api/user/profilee"); - - List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostNames); - - /* execute */ - Map patternMap = factoryToTest.create(falsePositives); - - /* test */ - Pattern pattern = patternMap.get(urlPathPattern); - - assertMatches(mustMatchUrlPaths, pattern); - assertDoesNotMatch(mustNotMatchUrlPaths, pattern); + assertFalse(pattern.matcher("https://myapp.example.com:80/rest/search?").matches()); } - /*----------------------------------------------HELPERS------------------------------------------------------*/ + /*----------------------------------------------HELPER------------------------------------------------------*/ - private List createFalsePositiveEntriesForProjectDataWebscan(List urlPathPatterns, List hostPatterns) { + private List createFalsePositiveEntriesForProjectDataWebscan(String id, String urlPattern) { List falsePositives = new ArrayList<>(); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setId(id); WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setUrlPathPatterns(urlPathPatterns); - webScan.setHostPatterns(hostPatterns); + webScan.setUrlPattern(urlPattern); projectData.setWebScan(webScan); FalsePositiveEntry entry = new FalsePositiveEntry(); @@ -311,19 +116,4 @@ private List createFalsePositiveEntriesForProjectDataWebscan return falsePositives; } - private void assertMatches(List mustMatchList, Pattern pattern) { - for (String mustMatch : mustMatchList) { - if (!pattern.matcher(mustMatch).matches()) { - fail("Expected pattern: " + pattern.toString() + " to match: " + mustMatch); - } - } - } - - private void assertDoesNotMatch(List mustNotMatchList, Pattern pattern) { - for (String mustNotMatch : mustNotMatchList) { - if (pattern.matcher(mustNotMatch).matches()) { - fail("Expected pattern: " + pattern.toString() + " to NOT match: " + mustNotMatch); - } - } - } } diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupportTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupportTest.java new file mode 100644 index 0000000000..785433bffe --- /dev/null +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupportTest.java @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; + +class SerecoProjectDataWebScanFalsePositiveSupportTest { + + private static final String MATCHING_URL = "https://prod.example.com/rest/profile/search"; + + private static final Pattern MOCKED_PATTERN = mock(); + private static final Matcher MOCKED_MATCHER = mock(); + + private SerecoProjectDataWebScanFalsePositiveSupport supportToTest; + private Map patternMap; + + @BeforeEach + void beforeEach() { + Mockito.reset(MOCKED_PATTERN, MOCKED_MATCHER); + + supportToTest = new SerecoProjectDataWebScanFalsePositiveSupport(); + + patternMap = new HashMap<>(); + patternMap.put("id", MOCKED_PATTERN); + + when(MOCKED_PATTERN.matcher(MATCHING_URL)).thenReturn(MOCKED_MATCHER); + } + + /*-------------------------------------CWE-IDs----------------------------------------------*/ + @ParameterizedTest + @ValueSource(strings = { "1", "-1", "0", "4711" }) + void both_having_the_same_cwe_id_returns_true(String cweId) { + /* prepare */ + WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); + webScanData.setCweId(createIntegerFromString(cweId)); + SerecoVulnerability vulnerability = new SerecoVulnerability(); + vulnerability.getClassification().setCwe(cweId); + + /* execute */ + boolean result = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); + + /* test */ + assertTrue(result); + } + + @ParameterizedTest + @ValueSource(strings = { "1", "-1", "0", "4711" }) + void cwe_id_of_webscan_data_is_one_more_returns_false(String cweId) { + /* prepare */ + WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); + webScanData.setCweId(createAsIntButPlusOne(cweId)); + + SerecoVulnerability vulnerability = new SerecoVulnerability(); + vulnerability.getClassification().setCwe(cweId); + + /* execute */ + boolean areBothHavingSameCweIdOrBothNoCweId = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); + + /* test */ + assertFalse(areBothHavingSameCweIdOrBothNoCweId); + } + + @ParameterizedTest + @ValueSource(ints = { 1, -1, 0, 4711 }) + void cwe_id_of_vulnerability_is_one_more_returns_false(int cweId) { + /* prepare */ + WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); + webScanData.setCweId(cweId); + + SerecoVulnerability vulnerability = new SerecoVulnerability(); + vulnerability.getClassification().setCwe(createIntAsStringButPlusOne(cweId)); + + /* execute */ + boolean areBothHavingSameCweIdOrBothNoCweId = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); + + /* test */ + assertFalse(areBothHavingSameCweIdOrBothNoCweId); + } + + @ParameterizedTest + @NullSource + @EmptySource + void cwe_id_of_projectData_is_zero_and_cwe_of_sereco_vulnerability_is_unset_returns_true(String serecoCweId) { + /* prepare */ + WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); + webScanData.setCweId(0); + + SerecoVulnerability vulnerability = new SerecoVulnerability(); + vulnerability.getClassification().setCwe(serecoCweId); + + /* execute */ + boolean areBothHavingSameCweIdOrBothNoCweId = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); + + /* test */ + assertTrue(areBothHavingSameCweIdOrBothNoCweId); + } + + /*-----------------------------------------------METHODS-----------------------------------------------*/ + + @Test + void methods_not_set_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("anything", null); + + /* test */ + assertTrue(result); + } + + @Test + void methods_empty_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("anything", Collections.emptyList()); + + /* test */ + assertTrue(result); + } + + @Test + void methods_containing_required_string_returns_true() { + /* prepare */ + List methods = List.of("POST", "GET", "DELETE"); + + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("GET", methods); + + /* test */ + assertTrue(result); + } + + @Test + void methods_not_containing_required_string_returns_false() { + /* prepare */ + List methods = List.of("POST", "GET", "DELETE"); + + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("no-in-list", methods); + + /* test */ + assertFalse(result); + } + + /*----------------------------------------URLPATTERNS-----------------------------------------------*/ + + @Test + void for_urlPattern_not_matching_returns_false() { + /* prepare */ + when(MOCKED_MATCHER.matches()).thenReturn(false); + + /* execute */ + boolean result = supportToTest.isMatchingUrlPattern(MATCHING_URL, patternMap); + + /* test */ + assertFalse(result); + } + + @Test + void for_urlPattern_is_matching_returns_true() { + /* prepare */ + when(MOCKED_MATCHER.matches()).thenReturn(true); + + /* execute */ + boolean result = supportToTest.isMatchingUrlPattern(MATCHING_URL, patternMap); + + /* test */ + assertTrue(result); + } + + /*-------------------------------------HELPERS----------------------------------------------*/ + + private int createIntegerFromString(String cweId) { + if (cweId == null) { + return 0; + } + if (cweId.isEmpty()) { + return 0; + } + return Integer.parseInt(cweId); + } + + private String createIntAsStringButPlusOne(int cweId) { + return String.valueOf(cweId + 1); + } + + private int createAsIntButPlusOne(String cweId) { + int intvalue = createIntegerFromString(cweId); + return intvalue + 1; + } + +} diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoWebScanFalsePositiveProjectDataSupportTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoWebScanFalsePositiveProjectDataSupportTest.java deleted file mode 100644 index a937914a78..0000000000 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoWebScanFalsePositiveProjectDataSupportTest.java +++ /dev/null @@ -1,343 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan.product.sereco; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.EmptySource; -import org.junit.jupiter.params.provider.NullSource; -import org.junit.jupiter.params.provider.ValueSource; - -import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; -import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; - -class SerecoWebScanFalsePositiveProjectDataSupportTest { - - private static List urlPathPatterns = List.of("*/rest/api/", "/rest/api/user/profile"); - private static String matchingUrlPathPattern = "/rest/api/user/profile"; - - private static List hostPatterns = List.of("*.example.com", "test.exampleapp.com"); - private static String matchingHost = "prod.example.com"; - - private static Pattern mockedPattern = mock(Pattern.class); - private static Matcher mockedMatcher = mock(Matcher.class); - - private SerecoProjectDataWebScanFalsePositiveSupport supportToTest; - private Map patternMap; - - @BeforeEach - void beforeEach() { - supportToTest = new SerecoProjectDataWebScanFalsePositiveSupport(); - patternMap = createPatternMapWithMocks(); - - when(mockedPattern.matcher(matchingHost)).thenReturn(mockedMatcher); - when(mockedPattern.matcher(matchingUrlPathPattern)).thenReturn(mockedMatcher); - } - - /*-------------------------------------CWE-IDs----------------------------------------------*/ - @ParameterizedTest - @EmptySource - @NullSource - @ValueSource(strings = { "1", "-1", "0", "4711" }) - void both_having_the_same_cwe_id_returns_true(String cweId) { - /* prepare */ - WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); - webScanData.setCweId(createIntegerFromString(cweId)); - SerecoVulnerability vulnerability = new SerecoVulnerability(); - vulnerability.getClassification().setCwe(cweId); - - /* execute */ - boolean result = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); - - /* test */ - assertTrue(result); - } - - @ParameterizedTest - @EmptySource - @NullSource - @ValueSource(strings = { "1", "-1", "0", "4711" }) - void cwe_id_of_webscan_data_is_one_more_returns_false(String cweId) { - /* prepare */ - WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); - webScanData.setCweId(createAsIntButPlusOne(cweId)); - - SerecoVulnerability vulnerability = new SerecoVulnerability(); - vulnerability.getClassification().setCwe(cweId); - - /* execute */ - boolean areBothHavingSameCweIdOrBothNoCweId = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); - - /* test */ - assertFalse(areBothHavingSameCweIdOrBothNoCweId); - } - - @ParameterizedTest - @NullSource - @ValueSource(ints = { 1, -1, 0, 4711 }) - void cwe_id_of_vulnerability_is_one_more_returns_false(Integer cweId) { - /* prepare */ - WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); - webScanData.setCweId(cweId); - - SerecoVulnerability vulnerability = new SerecoVulnerability(); - vulnerability.getClassification().setCwe(createIntAsStringButPlusOne(cweId)); - - /* execute */ - boolean areBothHavingSameCweIdOrBothNoCweId = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); - - /* test */ - assertFalse(areBothHavingSameCweIdOrBothNoCweId); - } - - /*-----------------------------------------------METHODS-----------------------------------------------*/ - - @Test - void methods_not_set_in_webscan_data_being_null_returns_true() { - /* execute */ - boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("anything", null); - - /* test */ - assertTrue(result); - } - - @Test - void methods_empty_in_webscan_data_being_null_returns_true() { - /* execute */ - boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("anything", Collections.emptyList()); - - /* test */ - assertTrue(result); - } - - @Test - void methods_containing_required_string_returns_true() { - /* prepare */ - List methods = List.of("POST", "GET", "DELETE"); - - /* execute */ - boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("GET", methods); - - /* test */ - assertTrue(result); - } - - @Test - void methods_not_containing_required_string_returns_false() { - /* prepare */ - List methods = List.of("POST", "GET", "DELETE"); - - /* execute */ - boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("no-in-list", methods); - - /* test */ - assertFalse(result); - } - - /*--------------------------------------------PORTS-----------------------------------------------*/ - - @Test - void ports_not_set_in_webscan_data_being_null_returns_true() { - /* execute */ - boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("anything", null); - - /* test */ - assertTrue(result); - } - - @Test - void ports_empty_in_webscan_data_being_null_returns_true() { - /* execute */ - boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("anything", Collections.emptyList()); - - /* test */ - assertTrue(result); - } - - @Test - void port_containing_required_string_returns_true() { - /* prepare */ - List ports = List.of("8080", "443", "80"); - - /* execute */ - boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("80", ports); - - /* test */ - assertTrue(result); - } - - @Test - void ports_not_containing_required_string_returns_false() { - /* prepare */ - List ports = List.of("8080", "443", "80"); - - /* execute */ - boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("no-in-list", ports); - - /* test */ - assertFalse(result); - } - - /*----------------------------------------PROTOCOLS-----------------------------------------------*/ - - @Test - void protocols_not_set_in_webscan_data_being_null_returns_true() { - /* execute */ - boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("anything", null); - - /* test */ - assertTrue(result); - } - - @Test - void protocols_empty_in_webscan_data_being_null_returns_true() { - /* execute */ - boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("anything", Collections.emptyList()); - - /* test */ - assertTrue(result); - } - - @Test - void protocols_containing_required_string_returns_true() { - /* prepare */ - List protocols = List.of("wss", "https"); - - /* execute */ - boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("https", protocols); - - /* test */ - assertTrue(result); - } - - @Test - void protocols_not_containing_required_string_returns_false() { - /* prepare */ - List protocols = List.of("wss", "https"); - - /* execute */ - boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("no-in-list", protocols); - - /* test */ - assertFalse(result); - } - - /*----------------------------------------HOSTPATTERNS-----------------------------------------------*/ - - @Test - void for_hostPatterns_pattern_in_map_is_null_throws_exception() { - /* execute + test */ - // At this point this should never happen because the map is meant to be created - // by the associated projectData - assertThrows(IllegalStateException.class, () -> supportToTest.isMatchingHostPattern(matchingHost, hostPatterns, new HashMap<>())); - } - - @Test - void for_hostPatterns_not_matching_returns_false() { - /* prepare */ - when(mockedMatcher.matches()).thenReturn(false); - - /* execute */ - boolean result = supportToTest.isMatchingHostPattern(matchingHost, hostPatterns, patternMap); - - /* test */ - assertFalse(result); - } - - @Test - void for_hostPatterns_is_matching_returns_true() { - /* prepare */ - when(mockedMatcher.matches()).thenReturn(true); - - /* execute */ - boolean result = supportToTest.isMatchingHostPattern(matchingHost, hostPatterns, patternMap); - - /* test */ - assertTrue(result); - } - - /*---------------------------------------URLPATHPATTERNS--------------------------------------------*/ - - @Test - void for_urlPathPatterns_pattern_in_map_is_null_throws_exception() { - /* execute + test */ - // At this point this should never happen because the map is meant to be created - // by the associated projectData - assertThrows(IllegalStateException.class, () -> supportToTest.isMatchingHostPattern(matchingUrlPathPattern, urlPathPatterns, new HashMap<>())); - } - - @Test - void for_urlPathPatterns_not_matching_returns_false() { - /* prepare */ - when(mockedMatcher.matches()).thenReturn(false); - - /* execute */ - boolean result = supportToTest.isMatchingHostPattern(matchingUrlPathPattern, urlPathPatterns, patternMap); - - /* test */ - assertFalse(result); - } - - @Test - void for_urlPathPatterns_is_matching_returns_true() { - /* prepare */ - when(mockedMatcher.matches()).thenReturn(true); - - /* execute */ - boolean result = supportToTest.isMatchingHostPattern(matchingUrlPathPattern, urlPathPatterns, patternMap); - - /* test */ - assertTrue(result); - } - - /*-------------------------------------HELPERS----------------------------------------------*/ - - private Integer createIntegerFromString(String cweId) { - if (cweId == null) { - return null; - } - if (cweId.isEmpty()) { - return null; - } - return Integer.parseInt(cweId); - } - - private String createIntAsStringButPlusOne(Integer cweId) { - if (cweId == null) { - return "1"; - } - int next = cweId.intValue() + 1; - return String.valueOf(next); - } - - private Integer createAsIntButPlusOne(String cweId) { - Integer intvalue = createIntegerFromString(cweId); - if (intvalue == null) { - return 1; - } - return intvalue + 1; - } - - private Map createPatternMapWithMocks() { - Map patternMap = new HashMap<>(); - for (String urlPathPattern : urlPathPatterns) { - patternMap.put(urlPathPattern, mockedPattern); - } - - for (String hostPattern : hostPatterns) { - patternMap.put(hostPattern, mockedPattern); - } - return patternMap; - } - -} diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategyTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategyTest.java index 1da8c9da05..7a169350fe 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategyTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategyTest.java @@ -1,8 +1,14 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.product.sereco; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.util.HashMap; import java.util.List; @@ -130,25 +136,21 @@ void cwe_comparison_fails_results_in_result_being_false() { verify(webscanFalsePositiveProjectDataSupport, times(1)).areBothHavingSameCweIdOrBothNoCweId(testDataContainer.projectData.getWebScan(), testDataContainer.vulnerability); // nothing else is called - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingHostPattern(any(), any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingUrlPathPattern(any(), any(), any()); + verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingUrlPattern(any(), any()); verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingMethodOrIgnoreIfNotSet(any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingProtocolOrIgnoreIfNotSet(any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingPortOrIgnoreIfNotSet(any(), any()); assertFalse(isFalsePositive); } @Test - void host_pattern_comparison_fails_results_in_result_being_false() { + void url_pattern_comparison_fails_results_in_result_being_false() { /* prepare */ FalsePositiveTestDataContainer testDataContainer = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); WebscanFalsePositiveProjectData webScan = testDataContainer.projectData.getWebScan(); when(webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(false); + when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPattern(TARGET1, testDataContainer.projectDataPatternMap)).thenReturn(false); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, @@ -156,44 +158,9 @@ void host_pattern_comparison_fails_results_in_result_being_false() { /* test */ verify(webscanFalsePositiveProjectDataSupport, times(1)).areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap); - // nothing else is called - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingUrlPathPattern(any(), any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingMethodOrIgnoreIfNotSet(any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingProtocolOrIgnoreIfNotSet(any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingPortOrIgnoreIfNotSet(any(), any()); - - assertFalse(isFalsePositive); - } - - @Test - void url_path_pattern_comparison_fails_results_in_result_being_false() { - /* prepare */ - FalsePositiveTestDataContainer testDataContainer = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); - - WebscanFalsePositiveProjectData webScan = testDataContainer.projectData.getWebScan(); - when(webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - - when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(false); - - /* execute */ - boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, - testDataContainer.projectDataPatternMap); - - /* test */ - verify(webscanFalsePositiveProjectDataSupport, times(1)).areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap); + verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPattern(TARGET1, testDataContainer.projectDataPatternMap); // nothing else is called verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingMethodOrIgnoreIfNotSet(any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingProtocolOrIgnoreIfNotSet(any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingPortOrIgnoreIfNotSet(any(), any()); assertFalse(isFalsePositive); } @@ -205,45 +172,10 @@ void method_comparison_fails_results_in_result_being_false() { WebscanFalsePositiveProjectData webScan = testDataContainer.projectData.getWebScan(); when(webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - - when(webscanFalsePositiveProjectDataSupport.isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods())).thenReturn(false); - - /* execute */ - boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, - testDataContainer.projectDataPatternMap); - - /* test */ - verify(webscanFalsePositiveProjectDataSupport, times(1)).areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods()); - // nothing else is called - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingProtocolOrIgnoreIfNotSet(any(), any()); - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingPortOrIgnoreIfNotSet(any(), any()); - assertFalse(isFalsePositive); - } - - @Test - void port_comparison_fails_results_in_result_being_false() { - /* prepare */ - FalsePositiveTestDataContainer testDataContainer = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); - - WebscanFalsePositiveProjectData webScan = testDataContainer.projectData.getWebScan(); - when(webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods())).thenReturn(true); + when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPattern(TARGET1, testDataContainer.projectDataPatternMap)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingPortOrIgnoreIfNotSet("443", webScan.getPorts())).thenReturn(false); + when(webscanFalsePositiveProjectDataSupport.isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods())).thenReturn(false); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, @@ -251,65 +183,23 @@ void port_comparison_fails_results_in_result_being_false() { /* test */ verify(webscanFalsePositiveProjectDataSupport, times(1)).areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap); + verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPattern(TARGET1, testDataContainer.projectDataPatternMap); verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods()); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingPortOrIgnoreIfNotSet("443", webScan.getPorts()); - // nothing else is called - verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingProtocolOrIgnoreIfNotSet(PROTOCOL1, null); assertFalse(isFalsePositive); } @Test - void protocol_comparison_fails_results_in_result_being_false() { + void all_conditions_are_satisfied_results_in_result_being_true() { /* prepare */ FalsePositiveTestDataContainer testDataContainer = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); WebscanFalsePositiveProjectData webScan = testDataContainer.projectData.getWebScan(); when(webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods())).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingPortOrIgnoreIfNotSet("443", webScan.getPorts())).thenReturn(true); - - when(webscanFalsePositiveProjectDataSupport.isMatchingProtocolOrIgnoreIfNotSet(PROTOCOL1, webScan.getProtocols())).thenReturn(false); - - /* execute */ - boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, - testDataContainer.projectDataPatternMap); - - /* test */ - verify(webscanFalsePositiveProjectDataSupport, times(1)).areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods()); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingPortOrIgnoreIfNotSet("443", webScan.getPorts()); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingProtocolOrIgnoreIfNotSet(PROTOCOL1, webScan.getProtocols()); - - assertFalse(isFalsePositive); - } - @Test - void alle_conditions_are_satisfied_results_in_result_being_true() { - /* prepare */ - FalsePositiveTestDataContainer testDataContainer = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); + when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPattern(TARGET1, testDataContainer.projectDataPatternMap)).thenReturn(true); - WebscanFalsePositiveProjectData webScan = testDataContainer.projectData.getWebScan(); - when(webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap)).thenReturn(true); when(webscanFalsePositiveProjectDataSupport.isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods())).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingPortOrIgnoreIfNotSet("443", webScan.getPorts())).thenReturn(true); - when(webscanFalsePositiveProjectDataSupport.isMatchingProtocolOrIgnoreIfNotSet(PROTOCOL1, webScan.getProtocols())).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, @@ -317,13 +207,8 @@ void alle_conditions_are_satisfied_results_in_result_being_true() { /* test */ verify(webscanFalsePositiveProjectDataSupport, times(1)).areBothHavingSameCweIdOrBothNoCweId(webScan, testDataContainer.vulnerability); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingHostPattern("api.example.com", webScan.getHostPatterns(), - testDataContainer.projectDataPatternMap); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPathPattern("/rest/users/projects", webScan.getUrlPathPatterns(), - testDataContainer.projectDataPatternMap); + verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingUrlPattern(TARGET1, testDataContainer.projectDataPatternMap); verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingMethodOrIgnoreIfNotSet(METHOD1, webScan.getMethods()); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingPortOrIgnoreIfNotSet("443", webScan.getPorts()); - verify(webscanFalsePositiveProjectDataSupport, times(1)).isMatchingProtocolOrIgnoreIfNotSet(PROTOCOL1, webScan.getProtocols()); assertTrue(isFalsePositive); } @@ -344,11 +229,8 @@ private FalsePositiveProjectData createValidTestFalsePositiveProjectData() { WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); webScan.setCweId(CWE_ID_4711); - webScan.setHostPatterns(List.of("*.example.com")); webScan.setMethods(List.of("GET", "POST")); - webScan.setPorts(List.of("80", "443")); - webScan.setProtocols(List.of("http", "https")); - webScan.setUrlPathPatterns(List.of("/rest/users/projects")); + webScan.setUrlPattern("https://api.example.com"); projectData.setWebScan(webScan); diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMerger.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMerger.java index 0aa9dd36bd..6e88b1915d 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMerger.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMerger.java @@ -67,7 +67,7 @@ public void addFalsePositiveProjectDataEntryOrUpdateExisting(FalsePositiveProjec continue; } if (projectDataFromEntry.getId().equals(projectData.getId())) { - LOG.warn("False positive project data entry with id: '{}', will be overwritten with new data!", projectData.getId()); + LOG.info("False positive project data entry with id: '{}', will be updated with new data!", projectData.getId()); falsePositives.set(index, projectDataEntry); return; } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataList.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataList.java index fada506771..6fac3c7a08 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataList.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataList.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.annotation.JsonSetter; import com.mercedesbenz.sechub.commons.core.MustBeKeptStable; import com.mercedesbenz.sechub.commons.model.JSONable; @@ -68,4 +69,14 @@ public static FalsePositiveDataList fromString(String json) { return CONVERTER.fromJSON(json); } + @JsonSetter + public void setJobData(List jobData) { + this.jobData = (jobData != null) ? jobData : new ArrayList<>(); + } + + @JsonSetter + public void setProjectData(List projectData) { + this.projectData = (projectData != null) ? projectData : new ArrayList<>(); + } + } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImpl.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImpl.java index 903894c4f7..3b83698e1f 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImpl.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImpl.java @@ -79,7 +79,6 @@ protected void validate(ValidationContext context) { validateJobData(context, target.getJobData()); validateProjectData(context, target.getProjectData()); - } private void validateProjectData(ValidationContext context, List projectDataList) { @@ -104,14 +103,14 @@ private void validateJobDataAndProjectDataSize(ValidationContext jobDataList = target.getJobData(); List projectDataList = target.getProjectData(); - validateNotNull(context, jobDataList, "projectDataList"); + validateNotNull(context, jobDataList, "jobDataList"); validateNotNull(context, projectDataList, "projectDataList"); if (context.isInValid()) { return; } validateMinSize(context, jobDataList, getConfig().minLength, "jobDataList"); - validateMinSize(context, jobDataList, getConfig().minLength, "projectDataList"); + validateMinSize(context, projectDataList, getConfig().minLength, "projectDataList"); int combinedSize = jobDataList.size() + projectDataList.size(); diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectData.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectData.java index 69904361c1..932696c8c2 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectData.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectData.java @@ -7,57 +7,27 @@ public class WebscanFalsePositiveProjectData implements ProjectData { public static final String PROPERTY_CWEID = "cweId"; - public static final String PROPERTY_PORTS = "ports"; - public static final String PROPERTY_PROTOCOLS = "protocols"; - public static final String PROPERTY_URLPATHPATTERNS = "urlPathPatterns"; - public static final String PROPERTY_HOSTPATTERNS = "hostPatterns"; + public static final String PROPERTY_URLPATTERN = "urlPattern"; public static final String PROPERTY_METHODS = "methods"; - private Integer cweId; - private List ports; - private List protocols; - private List urlPathPatterns; - private List hostPatterns; + private int cweId; + private String urlPattern; private List methods; - public Integer getCweId() { + public int getCweId() { return cweId; } - public void setCweId(Integer cweId) { + public void setCweId(int cweId) { this.cweId = cweId; } - public List getPorts() { - return ports; + public String getUrlPattern() { + return urlPattern; } - public void setPorts(List ports) { - this.ports = ports; - } - - public List getProtocols() { - return protocols; - } - - public void setProtocols(List protocols) { - this.protocols = protocols; - } - - public List getUrlPathPatterns() { - return urlPathPatterns; - } - - public void setUrlPathPatterns(List urlPathPatterns) { - this.urlPathPatterns = urlPathPatterns; - } - - public List getHostPatterns() { - return hostPatterns; - } - - public void setHostPatterns(List hostPatterns) { - this.hostPatterns = hostPatterns; + public void setUrlPattern(String urlPatterns) { + this.urlPattern = urlPatterns; } public List getMethods() { @@ -70,13 +40,12 @@ public void setMethods(List methods) { @Override public String toString() { - return "WebscanFalsePositiveProjectData [cweId=" + cweId + ", ports=" + ports + ", protocols=" + protocols + ", urlPatterns=" + urlPathPatterns - + ", servers=" + hostPatterns + ", methods=" + methods + "]"; + return "WebscanFalsePositiveProjectData [cweId=" + cweId + ", urlPatterns=" + urlPattern + ", methods=" + methods + "]"; } @Override public int hashCode() { - return Objects.hash(cweId, methods, ports, protocols, hostPatterns, urlPathPatterns); + return Objects.hash(cweId, methods, urlPattern); } @Override @@ -88,9 +57,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; WebscanFalsePositiveProjectData other = (WebscanFalsePositiveProjectData) obj; - return Objects.equals(cweId, other.cweId) && Objects.equals(methods, other.methods) && Objects.equals(ports, other.ports) - && Objects.equals(protocols, other.protocols) && Objects.equals(hostPatterns, other.hostPatterns) - && Objects.equals(urlPathPatterns, other.urlPathPatterns); + return Objects.equals(cweId, other.cweId) && Objects.equals(methods, other.methods) && Objects.equals(urlPattern, other.urlPattern); } } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImpl.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImpl.java index 75bcbca76f..8f111246a2 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImpl.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImpl.java @@ -13,15 +13,13 @@ public class WebscanFalsePositiveProjectDataValidationImpl extends AbstractValidation implements WebscanFalsePositiveProjectDataValidation { + private static final String BACKSLASH = "\\"; private static final String VALIDATOR_NAME = "webscan false positive project data validation"; - private static final String WILDCARD_ONLY_REGEX = "^[\\.\\*/:]+$"; - private static final Pattern WILDCARD_ONLY_PATTERN = Pattern.compile(WILDCARD_ONLY_REGEX); + private static final String WILDCARD_AND_URL_SEPARATOR_REGEX = "^[\\.\\*/:]+$"; + private static final Pattern WILDCARD_AND_URL_SEPARATOR_PATTERN = Pattern.compile(WILDCARD_AND_URL_SEPARATOR_REGEX); - private static final int WEBSCAN_PROJECT_DATA_LIST_MAX_SIZE = 50; - private static final int WEBSCAN_PROJECT_DATA_LIST_ENTRY_MAX_SIZE = 300; - - private static final String[] HOSTNAME_OR_IP_SEPARATORS = { ".", ":" }; - private static final String[] URL_PATH_SEPARATORS = { "/" }; + private static final int WEBSCAN_PROJECT_DATA_LIST_MAX_SIZE = 30; + private static final int WEBSCAN_PROJECT_DATA_LIST_ENTRY_MAX_SIZE = 40; @Override protected void setup(AbstractValidation.ValidationConfig config) { @@ -41,13 +39,10 @@ protected void validate(ValidationContext conte } /* validate mandatory parts */ validateCweId(context, webScan.getCweId()); - validateHostPatterns(context, webScan.getHostPatterns()); - validateUrlPathPatterns(context, webScan.getUrlPathPatterns()); + validateUrlPattern(context, webScan.getUrlPattern()); /* validate optional parts */ validateMethods(context, webScan.getMethods()); - validatePorts(context, webScan.getPorts()); - validateProtocols(context, webScan.getProtocols()); } private void validateCweId(ValidationContext context, Integer cweId) { @@ -60,65 +55,26 @@ private void validateCweId(ValidationContext co .formatted(name)); } - private void validateHostPatterns(ValidationContext context, List hostPatterns) { - String name = "%s.%s[]".formatted(FalsePositiveProjectData.PROPERTY_WEBSCAN, WebscanFalsePositiveProjectData.PROPERTY_HOSTPATTERNS); - if (hostPatterns == null || hostPatterns.isEmpty()) { - context.addError(getValidatorName(), ": The list of '%s' must contain at least one entry!".formatted(name)); + private void validateUrlPattern(ValidationContext context, String urlPattern) { + String name = "%s.%s".formatted(FalsePositiveProjectData.PROPERTY_WEBSCAN, WebscanFalsePositiveProjectData.PROPERTY_URLPATTERN); + if (urlPattern == null || urlPattern.isBlank()) { + context.addError(getValidatorName(), ": The '%s' is mandatory and must be present!".formatted(name)); return; } - validateSize(context, hostPatterns, name); - - // separators for host names, ipv4 addresses '.' and ipv6 addresses ':' - validateRequirementsForMandatoryListWithWildcards(context, hostPatterns, name, HOSTNAME_OR_IP_SEPARATORS); - } - - private void validateUrlPathPatterns(ValidationContext context, List urlPathPatterns) { - String name = "%s.%s[]".formatted(FalsePositiveProjectData.PROPERTY_WEBSCAN, WebscanFalsePositiveProjectData.PROPERTY_URLPATHPATTERNS); - - if (urlPathPatterns == null || urlPathPatterns.isEmpty()) { - context.addError(getValidatorName(), ": The list of '%s' must contain at least one entry!".formatted(name)); - return; + if (urlPattern.contains(BACKSLASH)) { + context.addError(getValidatorName(), ": Inside '%s' no backslashes are allowed!".formatted(name)); + } + if (WILDCARD_AND_URL_SEPARATOR_PATTERN.matcher(name).matches()) { + context.addError(getValidatorName(), ": The '%s': '%s' must consist of more than just wildcards!".formatted(name, urlPattern)); } - validateSize(context, urlPathPatterns, name); - - // separator for url path patterns '/' - validateRequirementsForMandatoryListWithWildcards(context, urlPathPatterns, name, URL_PATH_SEPARATORS); } private void validateMethods(ValidationContext context, List methods) { String name = "%s.%s[]".formatted(FalsePositiveProjectData.PROPERTY_WEBSCAN, WebscanFalsePositiveProjectData.PROPERTY_METHODS); - validateRequirementsForOptionalList(context, methods, name); - } - - private void validatePorts(ValidationContext context, List ports) { - String name = "%s.%s[]".formatted(FalsePositiveProjectData.PROPERTY_WEBSCAN, WebscanFalsePositiveProjectData.PROPERTY_PORTS); - validateRequirementsForOptionalList(context, ports, name); - } - - private void validateProtocols(ValidationContext context, List protocols) { - String name = "%s.%s[]".formatted(FalsePositiveProjectData.PROPERTY_WEBSCAN, WebscanFalsePositiveProjectData.PROPERTY_PROTOCOLS); - validateRequirementsForOptionalList(context, protocols, name); - } - - private void validateRequirementsForMandatoryListWithWildcards(ValidationContext context, List list, String name, - String... allowedSeparators) { - for (String entry : list) { - if (entry.contains("\\")) { - context.addError(getValidatorName(), ": Inside '%s' no backslashes are allowed!".formatted(name)); - continue; - } - if (WILDCARD_ONLY_PATTERN.matcher(name).matches()) { - context.addError(getValidatorName(), ": Inside '%s' each element must consist of more than just wildcards!".formatted(name)); - continue; - } - } - } - - private void validateRequirementsForOptionalList(ValidationContext context, List list, String name) { - if (list == null || list.isEmpty()) { + if (methods == null || methods.isEmpty()) { return; } - validateSize(context, list, name); + validateSize(context, methods, name); } private void validateSize(ValidationContext context, List list, String name) { diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMergerTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMergerTest.java index 2aa7c91412..9de95d5dcb 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMergerTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMergerTest.java @@ -184,8 +184,7 @@ void add_one_project_data_entry_results_in_one_entry_in_config() { /* prepare */ String id = "unique-id"; WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(List.of("*.host.com")); - webScan.setUrlPathPatterns(List.of("/rest/api/project/*")); + webScan.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); projectData.setId(id); @@ -208,12 +207,11 @@ void add_one_project_data_entry_results_in_one_entry_in_config() { } @Test - void add_one_project_data_entry_which_already_exists_results_in_one_updated_entry_in_config() { + void add_one_project_data_entry_with_id_which_already_exists_results_in_entry_being_updated() { /* prepare */ String id = "unique-id"; WebscanFalsePositiveProjectData webScan1 = new WebscanFalsePositiveProjectData(); - webScan1.setHostPatterns(List.of("*.host.com")); - webScan1.setUrlPathPatterns(List.of("/rest/api/project/*")); + webScan1.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData1 = new FalsePositiveProjectData(); projectData1.setId(id); @@ -228,8 +226,7 @@ void add_one_project_data_entry_which_already_exists_results_in_one_updated_entr config.getFalsePositives().add(falsePositiveEntry); WebscanFalsePositiveProjectData webScan2 = new WebscanFalsePositiveProjectData(); - webScan2.setHostPatterns(List.of("*.other.host.com")); - webScan2.setUrlPathPatterns(List.of("/rest/api/project/*", "/other/rest/api/")); + webScan2.setUrlPattern("https://another-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData2 = new FalsePositiveProjectData(); projectData2.setId(id); @@ -243,12 +240,12 @@ void add_one_project_data_entry_which_already_exists_results_in_one_updated_entr List falsePositives = config.getFalsePositives(); assertEquals(1, falsePositives.size()); - FalsePositiveEntry updatedFalsePositiveEntry = falsePositives.get(0); - assertNull(updatedFalsePositiveEntry.getJobData()); - assertNull(updatedFalsePositiveEntry.getMetaData()); + FalsePositiveEntry existingEntry = falsePositives.get(0); + assertNull(existingEntry.getJobData()); + assertNull(existingEntry.getMetaData()); - assertEquals(TEST_AUTHOR, updatedFalsePositiveEntry.getAuthor()); - assertEquals(projectData2, updatedFalsePositiveEntry.getProjectData()); + assertEquals(TEST_AUTHOR, existingEntry.getAuthor()); + assertEquals(projectData2, existingEntry.getProjectData()); } @Test @@ -256,8 +253,7 @@ void add_a_second_project_data_entry_which_has_an_unique_id_results_in_two_entri /* prepare */ String id1 = "unique-id"; WebscanFalsePositiveProjectData webScan1 = new WebscanFalsePositiveProjectData(); - webScan1.setHostPatterns(List.of("*.host.com")); - webScan1.setUrlPathPatterns(List.of("/rest/api/project/*")); + webScan1.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData1 = new FalsePositiveProjectData(); projectData1.setId(id1); @@ -273,8 +269,7 @@ void add_a_second_project_data_entry_which_has_an_unique_id_results_in_two_entri String id2 = "other-unique-id"; WebscanFalsePositiveProjectData webScan2 = new WebscanFalsePositiveProjectData(); - webScan2.setHostPatterns(List.of("*.other.host.com")); - webScan2.setUrlPathPatterns(List.of("/rest/api/project/*", "/other/rest/api/")); + webScan2.setUrlPattern("https://another-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData2 = new FalsePositiveProjectData(); projectData2.setId(id2); @@ -308,8 +303,7 @@ void remove_one_project_data_entry_which_already_exists_results_in_empty_config( /* prepare */ String id = "unique-id"; WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(List.of("*.host.com")); - webScan.setUrlPathPatterns(List.of("/rest/api/project/*")); + webScan.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); projectData.setId(id); @@ -339,8 +333,7 @@ void remove_one_project_data_entry_which_does_not_exist_results_in_unchanged_con /* prepare */ String id = "unique-id"; WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(List.of("*.host.com")); - webScan.setUrlPathPatterns(List.of("/rest/api/project/*")); + webScan.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); projectData.setId(id); @@ -413,8 +406,7 @@ void remove_one_job_data_entry_when_only_project_data_available_results_in_uncha /* prepare */ String id = "unique-id"; WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(List.of("*.host.com")); - webScan.setUrlPathPatterns(List.of("/rest/api/project/*")); + webScan.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); FalsePositiveProjectData projectData = new FalsePositiveProjectData(); projectData.setId(id); diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListTest.java index 1b39a3e9a3..86eb6c53d4 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListTest.java @@ -1,19 +1,20 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.project; -import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; +import static org.junit.jupiter.api.Assertions.*; import java.util.Iterator; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import com.mercedesbenz.sechub.domain.scan.ScanDomainTestFileSupport; public class FalsePositiveDataListTest { @Test - public void json_content_as_described_in_example_of_documentation() { + void json_content_as_described_in_example_of_documentation() { /* prepare */ String json = ScanDomainTestFileSupport.getTestfileSupport() .loadTestFileFromRoot("/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example1.json"); @@ -30,10 +31,30 @@ public void json_content_as_described_in_example_of_documentation() { FalsePositiveJobData jd2 = it.next(); assertEquals(1, jd1.getFindingId()); assertEquals("6cfa2ccf-da13-4dee-b529-0225ed9661bd", jd1.getJobUUID().toString()); - assertEquals("Absolute Path Traversal, can be ignored because not in deployment", jd1.getComment()); + assertEquals("Can be ignored because not in deployment", jd1.getComment()); assertEquals(15, jd2.getFindingId()); assertEquals("6cfa2ccf-da13-4dee-b529-0225ed9661bd", jd2.getJobUUID().toString()); assertNull(jd2.getComment()); } + @Test + void jobData_and_projectData_must_never_be_null() { + /* prepare */ + String json = """ + { + "apiVersion": "1.0", + "type": "falsePositiveDataList", + "jobData": null, + "projectData": null + } + """; + + /* execute */ + FalsePositiveDataList dataList = FalsePositiveDataList.fromString(json); + + /* test */ + assertTrue(dataList.getJobData().isEmpty()); + assertTrue(dataList.getProjectData().isEmpty()); + } + } diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImplTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImplTest.java index 3b395b787c..5cfc9c44a9 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImplTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImplTest.java @@ -16,14 +16,8 @@ class WebscanFalsePositiveProjectDataValidationImplTest { - private static final String methodsFieldName = FalsePositiveProjectData.PROPERTY_WEBSCAN + "." + WebscanFalsePositiveProjectData.PROPERTY_METHODS + "[]"; - private static final String portsFieldName = FalsePositiveProjectData.PROPERTY_WEBSCAN + "." + WebscanFalsePositiveProjectData.PROPERTY_PORTS + "[]"; - private static final String protocolsFieldName = FalsePositiveProjectData.PROPERTY_WEBSCAN + "." + WebscanFalsePositiveProjectData.PROPERTY_PROTOCOLS - + "[]"; - private static final String hostPatternsFieldName = FalsePositiveProjectData.PROPERTY_WEBSCAN + "." + WebscanFalsePositiveProjectData.PROPERTY_HOSTPATTERNS - + "[]"; - private static final String urlPatternsFieldName = FalsePositiveProjectData.PROPERTY_WEBSCAN + "." - + WebscanFalsePositiveProjectData.PROPERTY_URLPATHPATTERNS + "[]"; + private static final String METHODS_FIELD_NAME = FalsePositiveProjectData.PROPERTY_WEBSCAN + "." + WebscanFalsePositiveProjectData.PROPERTY_METHODS + "[]"; + private static final String URL_PATTERN_FIELD_NAME = FalsePositiveProjectData.PROPERTY_WEBSCAN + "." + WebscanFalsePositiveProjectData.PROPERTY_URLPATTERN; private WebscanFalsePositiveProjectDataValidationImpl validationToTest; @@ -58,19 +52,16 @@ void without_optional_parts_returns_valid_result() { /* test */ assertTrue(result.isValid()); + assertEquals(0, webScan.getCweId()); } @Test void too_long_optional_lists_returns_invalid_result() { /* prepare */ List methods = createTooLongListOfStrings(); - List ports = createTooLongListOfStrings(); - List protocols = createTooLongListOfStrings(); WebscanFalsePositiveProjectData webScan = createWebscanFalsePositiveProjectDataWithValidMandatoryParts(); webScan.setMethods(methods); - webScan.setPorts(ports); - webScan.setProtocols(protocols); /* execute */ ValidationResult result = validationToTest.validate(webScan); @@ -78,29 +69,21 @@ void too_long_optional_lists_returns_invalid_result() { /* test */ assertFalse(result.isValid()); List errors = result.getErrors(); - assertEquals(3, errors.size()); + assertEquals(1, errors.size()); - assertTrue(errors.get(0).contains(methodsFieldName)); - assertTrue(errors.get(1).contains(portsFieldName)); - assertTrue(errors.get(2).contains(protocolsFieldName)); + assertTrue(errors.get(0).contains(METHODS_FIELD_NAME)); } @Test void too_long_list_entry_in_optional_lists_returns_invalid_result() { /* prepare */ - String tooLongEntry = "a".repeat(301); + String tooLongEntry = "a".repeat(41); List methods = new ArrayList<>(); methods.add(tooLongEntry); - List ports = new ArrayList<>(); - ports.add(tooLongEntry); - List protocols = new ArrayList<>(); - protocols.add(tooLongEntry); WebscanFalsePositiveProjectData webScan = createWebscanFalsePositiveProjectDataWithValidMandatoryParts(); webScan.setMethods(methods); - webScan.setPorts(ports); - webScan.setProtocols(protocols); /* execute */ ValidationResult result = validationToTest.validate(webScan); @@ -108,19 +91,16 @@ void too_long_list_entry_in_optional_lists_returns_invalid_result() { /* test */ assertFalse(result.isValid()); List errors = result.getErrors(); - assertEquals(3, errors.size()); + assertEquals(1, errors.size()); - assertTrue(errors.get(0).contains(methodsFieldName)); - assertTrue(errors.get(1).contains(portsFieldName)); - assertTrue(errors.get(2).contains(protocolsFieldName)); + assertTrue(errors.get(0).contains(METHODS_FIELD_NAME)); } @Test - void mandatory_lists_null_returns_invalid_result() { + void mandatory_urlPattern_null_returns_invalid_result() { /* prepare */ WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(null); - webScan.setUrlPathPatterns(null); + webScan.setUrlPattern(null); /* execute */ ValidationResult result = validationToTest.validate(webScan); @@ -128,18 +108,16 @@ void mandatory_lists_null_returns_invalid_result() { /* test */ assertFalse(result.isValid()); List errors = result.getErrors(); - assertEquals(2, errors.size()); + assertEquals(1, errors.size()); - assertTrue(errors.get(0).contains(hostPatternsFieldName)); - assertTrue(errors.get(1).contains(urlPatternsFieldName)); + assertTrue(errors.get(0).contains(URL_PATTERN_FIELD_NAME)); } @Test - void mandatory_lists_empty_returns_invalid_result() { + void mandatory_mandatory_urlPattern_blank_returns_invalid_result() { /* prepare */ WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(new ArrayList<>()); - webScan.setUrlPathPatterns(new ArrayList<>()); + webScan.setUrlPattern(" "); /* execute */ ValidationResult result = validationToTest.validate(webScan); @@ -147,67 +125,16 @@ void mandatory_lists_empty_returns_invalid_result() { /* test */ assertFalse(result.isValid()); List errors = result.getErrors(); - assertEquals(2, errors.size()); + assertEquals(1, errors.size()); - assertTrue(errors.get(0).contains(hostPatternsFieldName)); - assertTrue(errors.get(1).contains(urlPatternsFieldName)); + assertTrue(errors.get(0).contains(URL_PATTERN_FIELD_NAME)); } @Test - void too_long_entry_in_mandatory_lists_returns_invalid_result() { - /* prepare */ - String tooLongServerEntry = "a".repeat(290) + ".*.host.com"; - String tooLongUrlPatternEntry = "a".repeat(290) + "/rest/api/*"; - - List hostPatterns = new ArrayList<>(); - hostPatterns.add(tooLongServerEntry); - List urlPatterns = new ArrayList<>(); - urlPatterns.add(tooLongUrlPatternEntry); - - WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setHostPatterns(hostPatterns); - webScan.setUrlPathPatterns(urlPatterns); - - /* execute */ - ValidationResult result = validationToTest.validate(webScan); - - /* test */ - assertFalse(result.isValid()); - List errors = result.getErrors(); - assertEquals(2, errors.size()); - - assertTrue(errors.get(0).contains(hostPatternsFieldName)); - assertTrue(errors.get(1).contains(urlPatternsFieldName)); - } - - @Test - void too_long_mandatory_lists_returns_invalid_result() { - /* prepare */ - WebscanFalsePositiveProjectData webScan = createWebscanFalsePositiveProjectDataWithTooManyMandatoryListEntries(); - - /* execute */ - ValidationResult result = validationToTest.validate(webScan); - - /* test */ - assertFalse(result.isValid()); - List errors = result.getErrors(); - assertEquals(2, errors.size()); - - assertTrue(errors.get(0).contains(hostPatternsFieldName)); - assertTrue(errors.get(1).contains(urlPatternsFieldName)); - } - - @Test - void urlPathPattern_and_hostPattern_cotnaining_backslashes_return_invalid_result() { + void urlPattern_containing_backslashes_return_invalid_result() { /* prepare */ WebscanFalsePositiveProjectData webScan = createWebscanFalsePositiveProjectDataWithValidMandatoryParts(); - List urlPatterns = new ArrayList<>(); - urlPatterns.add("/rest/ap\\Ei/use\\Qrs/*"); - webScan.setUrlPathPatterns(urlPatterns); - - List hostPatterns = new ArrayList<>(); - hostPatterns.add("*.su\\Eb.ho\\Qst.com"); - webScan.setHostPatterns(hostPatterns); + webScan.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?\\*"); /* execute */ ValidationResult result = validationToTest.validate(webScan); @@ -215,45 +142,20 @@ void urlPathPattern_and_hostPattern_cotnaining_backslashes_return_invalid_result /* test */ assertFalse(result.isValid()); List errors = result.getErrors(); - assertEquals(2, errors.size()); + assertEquals(1, errors.size()); - String error1 = errors.get(0); - assertTrue(error1.contains("no backslashes are allowed")); - - String error2 = errors.get(1); - assertTrue(error2.contains("no backslashes are allowed")); + assertTrue(errors.get(0).contains("no backslashes are allowed")); } private WebscanFalsePositiveProjectData createWebscanFalsePositiveProjectDataWithValidMandatoryParts() { - List urlPatterns = new ArrayList<>(); - urlPatterns.add("api/admin/test/*"); - List hostPatterns = new ArrayList<>(); - hostPatterns.add("*.host.com"); WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setUrlPathPatterns(urlPatterns); - webScan.setHostPatterns(hostPatterns); - - return webScan; - } - - private WebscanFalsePositiveProjectData createWebscanFalsePositiveProjectDataWithTooManyMandatoryListEntries() { - List urlPatterns = new ArrayList<>(); - List hostPatterns = new ArrayList<>(); - - for (int i = 0; i < 51; i++) { - urlPatterns.add("api/admin/test/*"); - hostPatterns.add("*.host.com"); - } - WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); - webScan.setUrlPathPatterns(urlPatterns); - webScan.setHostPatterns(hostPatterns); - + webScan.setUrlPattern("https://myapp-*.example.com:80*/rest/*/search?*"); return webScan; } private List createTooLongListOfStrings() { List list = new LinkedList<>(); - for (int i = 0; i < 51; i++) { + for (int i = 0; i < 31; i++) { list.add("a"); } return list; diff --git a/sechub-server-core/src/main/java/com/mercedesbenz/sechub/server/core/build.gradle b/sechub-server-core/src/main/java/com/mercedesbenz/sechub/server/core/build.gradle deleted file mode 100644 index 2cd5882fcb..0000000000 --- a/sechub-server-core/src/main/java/com/mercedesbenz/sechub/server/core/build.gradle +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -dependencies { - - implementation 'org.springframework.boot:spring-boot-starter-actuator' - implementation 'io.micrometer:micrometer-registry-prometheus' - - api project(':sechub-shared-kernel') - api project(':sechub-schedule') - api project(':sechub-authorization') - api project(':sechub-administration') - api project(':sechub-notification') - api project(':sechub-scan') - api project(':sechub-scan-product-sereco') - api project(':sechub-scan-product-checkmarx') - api project(':sechub-scan-product-pds') - api project(':sechub-sereco') - - api project(':deprecated-sechub-scan-product-netsparker') - api project(':deprecated-sechub-scan-product-nessus') - - testImplementation project(':sechub-testframework') -} \ No newline at end of file diff --git a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptor.java b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptor.java index 602528fcda..2cb68bf947 100644 --- a/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptor.java +++ b/sechub-server/src/main/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptor.java @@ -74,7 +74,10 @@ private void handleJobParameter(String part) { break; } if ("job".equals(split) || "false-positive".equals(split)) { - useNext = true; + // the new alternative way to handle false positives does not contain a job uuid + if (!part.contains("false-positive/project-data")) { + useNext = true; + } } } if (uuid == null) { diff --git a/sechub-server/src/test/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptorTest.java b/sechub-server/src/test/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptorTest.java index 01dd7dae5b..29d8dae2bc 100644 --- a/sechub-server/src/test/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptorTest.java +++ b/sechub-server/src/test/java/com/mercedesbenz/sechub/server/SecHubServerMDCAsyncHandlerInterceptorTest.java @@ -131,6 +131,20 @@ public void when_url_is_user_removes_false_positives_from_project_job_uuid_is_se assertEquals("myprojectId", MDC.get(LogConstants.MDC_SECHUB_PROJECT_ID)); } + @Test + public void when_url_is_user_removes_project_data_false_positives_from_project_no_job_uuid_is_expected() throws Exception { + /* prepare */ + when(request.getRequestURI()) + .thenReturn("https://localhost/api/project/myprojectId/false-positive/project-data/unique-id"); + + /* execute */ + interceptorToTest.preHandle(request, response, handler); + + /* test */ + assertNull(MDC.get(LogConstants.MDC_SECHUB_JOB_UUID)); + assertEquals("myprojectId", MDC.get(LogConstants.MDC_SECHUB_PROJECT_ID)); + } + @Test public void when_url_is_user_buildApproveJobUrluuid_is_set_and_projectId_as_well() throws Exception { /* prepare */