From 12b0c215f14fa95e4c4fb36b7c24459e709d2301 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 23 Aug 2024 15:56:58 +0200 Subject: [PATCH 01/30] updated wrapper version to latest released #3387 --- sechub-pds-solutions/gitleaks/env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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" From 55f43e6ea103abd53be1d1576282725491687b99 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Mon, 26 Aug 2024 12:37:31 +0200 Subject: [PATCH 02/30] documentation regarding fp declaration improved #3390 --- .../documents/client/02_sechub_client.adoc | 12 +-- .../concept_falsepositive_handling.adoc | 2 +- ...e-positives-REST-API-content-example1.json | 30 +++--- .../false-positives-howto-define-by-api.adoc | 97 +++++++++---------- 4 files changed, 63 insertions(+), 78 deletions(-) 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..d9cf25eea6 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 @@ -260,17 +260,7 @@ Select this file by the `-file` argument and start action `markFalsePositives`. sechub -file ${json-file} markFalsePositives ---- -*Example JSON:* - -[source, json] ----- -include::../shared/false-positives/false-positives-REST-API-content-example1.json[] ----- -<1> 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! + 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..23cc5bb3fc 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,29 @@ { "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": "Absolute Path Traversal, can be ignored because not in deployment" //<6> }, { "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", "findingId": 15 } ], - "projectData": [ //<6> - { - "id": "unique-id", //<7> + "projectData": [ //<7> + { + "id": "unique-id", //<8> "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> + "webScan": { //<9> + "cweId": 89, //<10> + "hostPatterns": [ "127.0.*.1", "api.example.com", "dev.*.example.com"], //<11> + "urlPathPatterns": [ "/rest/products/search*", "/rest/users/profile" ], //<12> + "protocols": [ "HTTPS", "WSS" ], //<13> + "methods": [ "GET", "DELETE" ], //<14> + "ports": [ "8080", "443" ] //<15> } - } + } ] } \ No newline at end of file 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..4bf848cb7b 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,49 +1,11 @@ // 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 sending false positive information via `JSON` either -*JSON* +- by referencing *job results* from 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 -[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. - -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. +and post it to the SecHub server REST API. [NOTE] ==== @@ -51,9 +13,12 @@ The `jobData` approach is very easy, generic - and also future-proof: The only d `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 `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}. +The `REST` controller logic reads the job result data and creates internally false positive +meta data. +==== +[NOTE] +==== 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. @@ -61,12 +26,42 @@ There are no dependencies because all information necessary to identify certain Each entry can be overridden or removed by the given `id`. ==== -*ID handling* + - -We must ensure identifiers are always correct and continue even for false positives. -An example: +*Example JSON* -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. +[source,json] +---- +include::false-positives-REST-API-content-example1.json[] +---- +<1> API version +<2> type - must be `falsePositiveJobDataList` +<3> List of jobData _(optional)_ that is used to mark a single finding as false positive +<4> SecHub Job-UUID of the report where the finding was +<5> finding-ID which shall be marked as false positive +<6> A comment (optional) describing the reason why this is a false positive +<7> List of 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. +<8> `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. +<9> `webScan` _(optional)_ section can be used to define false positive patterns for web scans to provide more possibilities to the user. +<10> `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`. +<11> `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'. +<12> `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. +<13> `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. +<14> `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. +<15> `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. +Important information on the wildcard approach in `projectData`, regarding web scans: + +- To be a false positive a finding must match any single entry from one of the lists above. + +- Wildcards (`pass:[*]`) can be used inside mandatory parts, like `hostPatterns` or `urlPathPatterns`. + +- Wildcards match anything until the next NON-wildcard character. + +- Multiple wildcards can be used in one string. + +- In `hostPatterns` or `urlPathPatterns` specifying only a wildcard (`pass:[*]`) is not allowed. From 60b5f91b3d918d169f11a37d93d8424972e16ef0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Aug 2024 22:55:59 +0000 Subject: [PATCH 03/30] Bump gradle/actions from 4.0.0 to 4.0.1 Bumps [gradle/actions](https://github.com/gradle/actions) from 4.0.0 to 4.0.1. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/af1da67850ed9a4cedd57bfd976089dd991e2582...16bf8bc8fe830fa669c3c9f914d3eb147c629707) --- updated-dependencies: - dependency-name: gradle/actions dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/documentation-build.yml | 2 +- .github/workflows/gradle.yml | 2 +- .github/workflows/publish-libraries.yml | 2 +- .github/workflows/release-client-server-pds.yml | 2 +- .github/workflows/release-pds-tools.yml | 2 +- .github/workflows/release-webui.yml | 2 +- .github/workflows/release-wrapper-checkmarx.yml | 2 +- .github/workflows/release-wrapper-owaspzap.yml | 2 +- .github/workflows/release-wrapper-prepare.yml | 2 +- .github/workflows/release-wrapper-validation.yml | 2 +- .github/workflows/release-wrapper-xray.yml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) 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 From 95c601a119cd86781dd86c7c26d3aff15c721358 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Thu, 29 Aug 2024 11:17:49 +0200 Subject: [PATCH 04/30] Remove gradle file which seems unnecessary #3397 --- .../sechub/server/core/build.gradle | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 sechub-server-core/src/main/java/com/mercedesbenz/sechub/server/core/build.gradle 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 From 5e59dfdb28bcba8694d7bdeeb3c93cb32140c2f7 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Mon, 2 Sep 2024 15:26:53 +0200 Subject: [PATCH 05/30] Simplify projectData fp JSON #3400 - simplify data model - change tests to match simplified data model - update documentation parts - update openapi.yaml --- ...am_falsepositives_definition_overview.puml | 5 +- ...e-positives-REST-API-content-example1.json | 7 +- .../false-positives-howto-define-by-api.adoc | 28 +-- .../false-positives-technical-details.adoc | 18 +- ...alsePositiveRestControllerRestDocTest.java | 76 +++--- .../PDSSolutionMockModeScenario21IntTest.java | 4 +- .../src/main/resources/openapi.yaml | 52 +--- .../SerecoProjectDataPatternMapFactory.java | 33 +-- ...rojectDataWebScanFalsePositiveSupport.java | 83 ++---- ...bScanProjectDataFalsePositiveStrategy.java | 44 +--- .../sereco/SerecoFalsePositiveMarkerTest.java | 3 +- ...erecoProjectDataPatternMapFactoryTest.java | 238 ++---------------- ...anFalsePositiveProjectDataSupportTest.java | 164 ++---------- ...nProjectDataFalsePositiveStrategyTest.java | 156 ++---------- .../FalsePositiveDataConfigMerger.java | 28 +-- .../project/FalsePositiveDataService.java | 2 +- .../WebscanFalsePositiveProjectData.java | 51 +--- ...alsePositiveProjectDataValidationImpl.java | 77 ++---- .../FalsePositiveDataConfigMergerTest.java | 42 ++-- ...PositiveProjectDataValidationImplTest.java | 141 ++--------- 20 files changed, 226 insertions(+), 1026 deletions(-) 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/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 23cc5bb3fc..f246830d6e 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 @@ -18,11 +18,8 @@ "comment": "It was verified that there is no SQL-injection vulnerability at this location", "webScan": { //<9> "cweId": 89, //<10> - "hostPatterns": [ "127.0.*.1", "api.example.com", "dev.*.example.com"], //<11> - "urlPathPatterns": [ "/rest/products/search*", "/rest/users/profile" ], //<12> - "protocols": [ "HTTPS", "WSS" ], //<13> - "methods": [ "GET", "DELETE" ], //<14> - "ports": [ "8080", "443" ] //<15> + "urlPattern": [ "/rest/products/search*", "/rest/users/profile" ], //<11> + "methods": [ "GET", "DELETE" ] //<12> } } ] 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 4bf848cb7b..21cedf119b 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 @@ -23,7 +23,7 @@ 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. 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`. +Each entry can be removed by the given `id`. ==== *Example JSON* @@ -33,7 +33,7 @@ Each entry can be overridden or removed by the given `id`. include::false-positives-REST-API-content-example1.json[] ---- <1> API version -<2> type - must be `falsePositiveJobDataList` +<2> type - must be `falsePositiveDataList` or the deprecated type `falsePositiveJobDataList`. <3> List of jobData _(optional)_ that is used to mark a single finding as false positive <4> SecHub Job-UUID of the report where the finding was <5> finding-ID which shall be marked as false positive @@ -45,23 +45,17 @@ but it might be easier to create this type of false positive configuration with the existing false positive entry will be overwritten. The `id` is also mandatory to unmark this entry. <9> `webScan` _(optional)_ section can be used to define false positive patterns for web scans to provide more possibilities to the user. <10> `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, +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`. -<11> `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'. -<12> `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. -<13> `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. -<14> `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. -<15> `ports` _(optional)_ can be used to further restrict the false positive matching, to specific ports protocols. +<11> `urlPattern` specifies a URL pattern to identify a false positive. This is a `mandatory` field. +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`. +<12> `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. + Important information on the wildcard approach in `projectData`, regarding web scans: + -- To be a false positive a finding must match any single entry from one of the lists above. + -- Wildcards (`pass:[*]`) can be used inside mandatory parts, like `hostPatterns` or `urlPathPatterns`. + +- To be a false positive a finding the `cweId` and the `urlPattern`. A list of (HTTP) `methods` can be specified to limit the false positive. If no `methods` are specified, this false positive entry will not be restricted to any method. + +- Wildcards (`pass:[*]`) can be used inside `urlPattern`. + - Wildcards match anything until the next NON-wildcard character. + -- Multiple wildcards can be used in one string. + -- In `hostPatterns` or `urlPathPatterns` specifying only a wildcard (`pass:[*]`) is not allowed. +- Multiple wildcards can be used in one `urlPattern`. + +- A `urlPattern` which consists of 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..80c1e0dc4d 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 @@ -1,20 +1,35 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.restdoc; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.*; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData.*; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration.*; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData.*; -import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.*; -import static com.mercedesbenz.sechub.restdoc.RestDocumentation.*; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.*; -import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.*; -import static org.mockito.Mockito.*; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_API_VERSION; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_JOBDATA; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_PROJECTDATA; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_TYPE; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData.PROPERTY_FINDINGID; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData.PROPERTY_JOBUUID; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration.PROPERTY_FALSE_POSITIVES; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData.PROPERTY_ID; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData.PROPERTY_WEBSCAN; +import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.PROPERTY_CWEID; +import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.PROPERTY_METHODS; +import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.PROPERTY_URLPATTERN; +import static com.mercedesbenz.sechub.restdoc.RestDocumentation.defineRestService; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.FINDING_ID; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.JOB_UUID; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.PROJECT_DATA_ID; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.PROJECT_ID; +import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.https; +import static org.mockito.Mockito.when; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; -import static org.springframework.restdocs.payload.PayloadDocumentation.*; -import static org.springframework.restdocs.request.RequestDocumentation.*; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; +import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.lang.annotation.Annotation; import java.util.Date; @@ -42,7 +57,20 @@ import com.mercedesbenz.sechub.commons.model.Severity; import com.mercedesbenz.sechub.docgen.util.RestDocFactory; import com.mercedesbenz.sechub.domain.scan.ScanAssertService; -import com.mercedesbenz.sechub.domain.scan.project.*; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveCodeMetaData; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveCodePartMetaData; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataConfigMerger; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataListValidation; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataService; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveEntry; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveRestController; +import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigService; +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; import com.mercedesbenz.sechub.domain.scan.report.ScanReportRepository; import com.mercedesbenz.sechub.sharedkernel.Profiles; import com.mercedesbenz.sechub.sharedkernel.RoleConstants; @@ -119,10 +147,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 +191,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 +319,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 +386,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-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..176f45ad85 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 @@ -61,62 +61,26 @@ public boolean areBothHavingSameCweIdOrBothNoCweId(WebscanFalsePositiveProjectDa * 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 targetUrl * @param projectDataPatternMap - * @return true if the given host matches any of the given patterns + * @return */ - public boolean isMatchingHostPattern(String host, List hostPatterns, Map projectDataPatternMap) { - notNull(host, " host may not be null"); - notNull(hostPatterns, " hostPatterns 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(host, hostPatterns, projectDataPatternMap); - } - - /** - * 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. - * - * @param urlFilePart - * @param urlPathPatterns - * @param projectDataPatternMap - * @return true if the given urlFilePart 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"); - 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 +96,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..52b3ccbca0 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-------------------------- */ /* ---------------------------------------------------- */ - 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/SerecoWebScanFalsePositiveProjectDataSupportTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoWebScanFalsePositiveProjectDataSupportTest.java index a937914a78..e70050ff0d 100644 --- 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 @@ -17,31 +17,31 @@ 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 SerecoWebScanFalsePositiveProjectDataSupportTest { - private static List urlPathPatterns = List.of("*/rest/api/", "/rest/api/user/profile"); - private static String matchingUrlPathPattern = "/rest/api/user/profile"; + private static String matchingUrl = "https://prod.example.com/rest/profile/search"; - 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 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 = createPatternMapWithMocks(); - when(mockedPattern.matcher(matchingHost)).thenReturn(mockedMatcher); - when(mockedPattern.matcher(matchingUrlPathPattern)).thenReturn(mockedMatcher); + patternMap = new HashMap<>(); + patternMap.put("id", MOCKED_PATTERN); + + when(MOCKED_PATTERN.matcher(matchingUrl)).thenReturn(MOCKED_MATCHER); } /*-------------------------------------CWE-IDs----------------------------------------------*/ @@ -144,94 +144,6 @@ void methods_not_containing_required_string_returns_false() { 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 @@ -239,16 +151,16 @@ 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<>())); + assertThrows(IllegalStateException.class, () -> supportToTest.isMatchingUrlPattern(matchingUrl, new HashMap<>())); } @Test void for_hostPatterns_not_matching_returns_false() { /* prepare */ - when(mockedMatcher.matches()).thenReturn(false); + when(MOCKED_MATCHER.matches()).thenReturn(false); /* execute */ - boolean result = supportToTest.isMatchingHostPattern(matchingHost, hostPatterns, patternMap); + boolean result = supportToTest.isMatchingUrlPattern(matchingUrl, patternMap); /* test */ assertFalse(result); @@ -257,44 +169,10 @@ void for_hostPatterns_not_matching_returns_false() { @Test void for_hostPatterns_is_matching_returns_true() { /* prepare */ - when(mockedMatcher.matches()).thenReturn(true); + when(MOCKED_MATCHER.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); + boolean result = supportToTest.isMatchingUrlPattern(matchingUrl, patternMap); /* test */ assertTrue(result); @@ -328,16 +206,4 @@ private Integer createAsIntButPlusOne(String cweId) { 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..fed3eca693 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 @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.project; -import java.util.List; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -52,27 +50,19 @@ public void addJobDataWithMetaDataToConfig(ScanSecHubReport report, FalsePositiv } - public void addFalsePositiveProjectDataEntryOrUpdateExisting(FalsePositiveProjectConfiguration config, FalsePositiveProjectData projectData, - String userId) { + public void addFalsePositiveProjectDataEntry(FalsePositiveProjectConfiguration config, FalsePositiveProjectData projectData, String userId) { + FalsePositiveEntry existingEntry = findExistingProjectDataFalsePositiveEntryInConfig(config, projectData); + + if (existingEntry != null) { + LOG.warn("False positive projectData entry:'{}' not added, because already existing", projectData.getId()); + return; + } + FalsePositiveEntry projectDataEntry = new FalsePositiveEntry(); projectDataEntry.setAuthor(userId); projectDataEntry.setProjectData(projectData); - List falsePositives = config.getFalsePositives(); - for (int index = 0; index < falsePositives.size(); index++) { - FalsePositiveEntry existingFPEntry = falsePositives.get(index); - FalsePositiveProjectData projectDataFromEntry = existingFPEntry.getProjectData(); - if (projectDataFromEntry == null) { - LOG.debug("The entry is a jobData entry with metaData so no projectData"); - continue; - } - if (projectDataFromEntry.getId().equals(projectData.getId())) { - LOG.warn("False positive project data entry with id: '{}', will be overwritten with new data!", projectData.getId()); - falsePositives.set(index, projectDataEntry); - return; - } - } - falsePositives.add(projectDataEntry); + config.getFalsePositives().add(projectDataEntry); } public void removeJobDataWithMetaDataFromConfig(FalsePositiveProjectConfiguration config, FalsePositiveJobData jobDataToRemove) { diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java index 3a4ef42ebe..af4141cf44 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java @@ -156,7 +156,7 @@ private void addJobDataListToConfiguration(FalsePositiveProjectConfiguration con List projectDataList = dataList.getProjectData(); for (FalsePositiveProjectData projectData : projectDataList) { - merger.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData, userContextService.getUserId()); + merger.addFalsePositiveProjectDataEntry(config, projectData, userContextService.getUserId()); } } 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..501fcbbbb8 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,17 +7,11 @@ 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 String urlPattern; private List methods; public Integer getCweId() { @@ -28,36 +22,12 @@ public void setCweId(Integer 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..1cb49692cf 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 @@ -14,14 +14,11 @@ public class WebscanFalsePositiveProjectDataValidationImpl extends AbstractValid implements WebscanFalsePositiveProjectDataValidation { 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 +38,10 @@ protected void validate(ValidationContext conte } /* validate mandatory parts */ validateCweId(context, webScan.getCweId()); - validateHostPatterns(context, webScan.getHostPatterns()); - validateUrlPathPatterns(context, webScan.getUrlPathPatterns()); + validateUrlPatterns(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 +54,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 validateUrlPatterns(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 not be empty!".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("\\")) { + 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' each element 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..00bb809a94 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); @@ -193,7 +192,7 @@ void add_one_project_data_entry_results_in_one_entry_in_config() { projectData.setWebScan(webScan); /* execute */ - toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData, TEST_AUTHOR); + toTest.addFalsePositiveProjectDataEntry(config, projectData, TEST_AUTHOR); /* test */ List falsePositives = config.getFalsePositives(); @@ -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_which_already_exists_results_in_entry_with_same_id_not_added_again() { /* 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); @@ -237,18 +234,18 @@ void add_one_project_data_entry_which_already_exists_results_in_one_updated_entr projectData2.setWebScan(webScan2); /* execute */ - toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData2, TEST_AUTHOR); + toTest.addFalsePositiveProjectDataEntry(config, projectData2, TEST_AUTHOR); /* test */ 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(projectData1, 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); @@ -282,7 +277,7 @@ void add_a_second_project_data_entry_which_has_an_unique_id_results_in_two_entri projectData2.setWebScan(webScan2); /* execute */ - toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData2, TEST_AUTHOR); + toTest.addFalsePositiveProjectDataEntry(config, projectData2, TEST_AUTHOR); /* test */ List falsePositives = config.getFalsePositives(); @@ -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/WebscanFalsePositiveProjectDataValidationImplTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImplTest.java index 3b395b787c..5a1d4ac2ab 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; @@ -64,13 +58,9 @@ void without_optional_parts_returns_valid_result() { 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 +68,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 +90,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 +107,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 +124,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 +141,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; From 8d62e5a07bffb2794fc09a4d61200a9db98b38e5 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Mon, 2 Sep 2024 15:42:56 +0200 Subject: [PATCH 06/30] Refactor and documentation #3401 --- ...alsePositiveRestControllerRestDocTest.java | 56 +++++-------------- ...rojectDataWebScanFalsePositiveSupport.java | 2 +- 2 files changed, 15 insertions(+), 43 deletions(-) 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 80c1e0dc4d..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 @@ -1,35 +1,20 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.restdoc; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_API_VERSION; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_JOBDATA; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_PROJECTDATA; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.PROPERTY_TYPE; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData.PROPERTY_FINDINGID; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData.PROPERTY_JOBUUID; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration.PROPERTY_FALSE_POSITIVES; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData.PROPERTY_ID; -import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData.PROPERTY_WEBSCAN; -import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.PROPERTY_CWEID; -import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.PROPERTY_METHODS; -import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.PROPERTY_URLPATTERN; -import static com.mercedesbenz.sechub.restdoc.RestDocumentation.defineRestService; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.FINDING_ID; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.JOB_UUID; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.PROJECT_DATA_ID; -import static com.mercedesbenz.sechub.test.RestDocPathParameter.PROJECT_ID; -import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.https; -import static org.mockito.Mockito.when; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList.*; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData.*; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration.*; +import static com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData.*; +import static com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData.*; +import static com.mercedesbenz.sechub.restdoc.RestDocumentation.*; +import static com.mercedesbenz.sechub.test.RestDocPathParameter.*; +import static com.mercedesbenz.sechub.test.SecHubTestURLBuilder.*; +import static org.mockito.Mockito.*; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; -import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.put; -import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; -import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; -import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; -import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName; -import static org.springframework.restdocs.request.RequestDocumentation.pathParameters; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.*; +import static org.springframework.restdocs.payload.PayloadDocumentation.*; +import static org.springframework.restdocs.request.RequestDocumentation.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import java.lang.annotation.Annotation; import java.util.Date; @@ -57,20 +42,7 @@ import com.mercedesbenz.sechub.commons.model.Severity; import com.mercedesbenz.sechub.docgen.util.RestDocFactory; import com.mercedesbenz.sechub.domain.scan.ScanAssertService; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveCodeMetaData; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveCodePartMetaData; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataConfigMerger; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataListValidation; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataService; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveEntry; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveRestController; -import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigService; -import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; +import com.mercedesbenz.sechub.domain.scan.project.*; import com.mercedesbenz.sechub.domain.scan.report.ScanReportRepository; import com.mercedesbenz.sechub.sharedkernel.Profiles; import com.mercedesbenz.sechub.sharedkernel.RoleConstants; 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 176f45ad85..63b2bb6994 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 @@ -63,7 +63,7 @@ public boolean areBothHavingSameCweIdOrBothNoCweId(WebscanFalsePositiveProjectDa * * @param targetUrl * @param projectDataPatternMap - * @return + * @return true if the given host matches any of the given patterns */ public boolean isMatchingUrlPattern(String targetUrl, Map projectDataPatternMap) { notNull(targetUrl, " host may not be null"); From b157ee010b9408114618b33a37e9d00d1f3a4202 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Mon, 2 Sep 2024 15:49:36 +0200 Subject: [PATCH 07/30] Fix failing test #3401 --- ...WebScanFalsePositiveProjectDataSupportTest.java | 14 +++----------- ...scanFalsePositiveProjectDataValidationImpl.java | 4 ++-- 2 files changed, 5 insertions(+), 13 deletions(-) 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 index e70050ff0d..b9e863f1d4 100644 --- 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 @@ -144,18 +144,10 @@ void methods_not_containing_required_string_returns_false() { assertFalse(result); } - /*----------------------------------------HOSTPATTERNS-----------------------------------------------*/ + /*----------------------------------------URLPATTERNS-----------------------------------------------*/ @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.isMatchingUrlPattern(matchingUrl, new HashMap<>())); - } - - @Test - void for_hostPatterns_not_matching_returns_false() { + void for_urlPattern_not_matching_returns_false() { /* prepare */ when(MOCKED_MATCHER.matches()).thenReturn(false); @@ -167,7 +159,7 @@ void for_hostPatterns_not_matching_returns_false() { } @Test - void for_hostPatterns_is_matching_returns_true() { + void for_urlPattern_is_matching_returns_true() { /* prepare */ when(MOCKED_MATCHER.matches()).thenReturn(true); 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 1cb49692cf..4b547bec3b 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 @@ -38,7 +38,7 @@ protected void validate(ValidationContext conte } /* validate mandatory parts */ validateCweId(context, webScan.getCweId()); - validateUrlPatterns(context, webScan.getUrlPattern()); + validateUrlPattern(context, webScan.getUrlPattern()); /* validate optional parts */ validateMethods(context, webScan.getMethods()); @@ -54,7 +54,7 @@ private void validateCweId(ValidationContext co .formatted(name)); } - private void validateUrlPatterns(ValidationContext context, String urlPattern) { + 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 not be empty!".formatted(name)); From 9bdfedfeba83700cd31d4a291a8f00426b665959 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Mon, 2 Sep 2024 16:19:05 +0200 Subject: [PATCH 08/30] fix failing unit test #3401 --- .../false-positives-REST-API-content-example1.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f246830d6e..df2d2553bc 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 @@ -18,7 +18,7 @@ "comment": "It was verified that there is no SQL-injection vulnerability at this location", "webScan": { //<9> "cweId": 89, //<10> - "urlPattern": [ "/rest/products/search*", "/rest/users/profile" ], //<11> + "urlPattern": "https://*.example.com/rest/products/search*", //<11> "methods": [ "GET", "DELETE" ] //<12> } } From 836b1689facb1967c066c94ebd2a80f163277192 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Tue, 3 Sep 2024 09:19:58 +0200 Subject: [PATCH 09/30] minor changes #3401 --- .../WebscanFalsePositiveProjectDataValidationImpl.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 4b547bec3b..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,6 +13,7 @@ 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_AND_URL_SEPARATOR_REGEX = "^[\\.\\*/:]+$"; private static final Pattern WILDCARD_AND_URL_SEPARATOR_PATTERN = Pattern.compile(WILDCARD_AND_URL_SEPARATOR_REGEX); @@ -57,14 +58,14 @@ private void validateCweId(ValidationContext co 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 not be empty!".formatted(name)); + context.addError(getValidatorName(), ": The '%s' is mandatory and must be present!".formatted(name)); return; } - if (urlPattern.contains("\\")) { + 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' each element must consist of more than just wildcards!".formatted(name, urlPattern)); + context.addError(getValidatorName(), ": The '%s': '%s' must consist of more than just wildcards!".formatted(name, urlPattern)); } } From 64a3b1f8428d00862110d9fd31b657d34638d7ba Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Tue, 3 Sep 2024 10:06:24 +0200 Subject: [PATCH 10/30] PR review changes #3401 --- .../code2doc/usecases/user/mark_false_positives.adoc | 2 +- .../false-positives-howto-define-by-api.adoc | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) 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/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 21cedf119b..f8cceeb14b 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 @@ -54,8 +54,11 @@ Like any other _optional_ field, if this is missing it is simply ignored. Important information on the wildcard approach in `projectData`, regarding web scans: + -- To be a false positive a finding the `cweId` and the `urlPattern`. A list of (HTTP) `methods` can be specified to limit the false positive. If no `methods` are specified, this false positive entry will not be restricted to any method. + +- To be a false positive a finding must match the `cweId` and the `urlPattern` of at least one of the false positive entries. + 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. + If no `methods` are specified, this false positive entry will not be restricted to any method. + - Wildcards (`pass:[*]`) can be used inside `urlPattern`. + - Wildcards match anything until the next NON-wildcard character. + - Multiple wildcards can be used in one `urlPattern`. + -- A `urlPattern` which consists of only wildcards (`pass:[*]`) is not allowed. +- An `urlPattern` which contains only wildcards (`pass:[*]`) is not allowed. From a92eb13d6a3195231ee24ce82231c3653bee8316 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Tue, 3 Sep 2024 17:28:19 +0200 Subject: [PATCH 11/30] new projectData structure added #3390 - projectData structure added - tests extended and adapted - docs example extended --- .../sechub/cli/false-positives.go | 21 +- .../sechub/cli/false-positives_test.go | 289 ++++++++++++------ ...e-positives-REST-API-content-example1.json | 2 +- .../false-positives-howto-define-by-api.adoc | 2 +- 4 files changed, 213 insertions(+), 101 deletions(-) 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..30a6b7c31e 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 @@ -24,9 +24,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 +37,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 - define pattern for false-posisitves 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"` 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..670e893920 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" @@ -12,136 +13,232 @@ import ( ) func TestFalsePositivesFilePathCorrectCreated(t *testing.T) { - /* prepare */ - list := FalsePositivesList{serverResult: []byte("content"), outputFolder: "path1", outputFileName: "fileName1"} + /* prepare */ + list := FalsePositivesList{serverResult: []byte("content"), outputFolder: "path1", outputFileName: "fileName1"} - /* execute */ - result := list.createFilePath(false) + /* execute */ + result := list.createFilePath(false) - /* test */ - expected := filepath.Join("path1", "fileName1") - if result != expected { - t.Fatalf("Strings differ:\nExpected:%s\nGot :%s", expected, result) - } + /* test */ + expected := filepath.Join("path1", "fileName1") + if result != expected { + t.Fatalf("Strings differ:\nExpected:%s\nGot :%s", expected, result) + } } func TestFalsePositivesSaveWritesAFile(t *testing.T) { - /* prepare */ - tempDir := sechubTestUtil.InitializeTestTempDir(t) - defer os.RemoveAll(tempDir) + /* prepare */ + tempDir := sechubTestUtil.InitializeTestTempDir(t) + defer os.RemoveAll(tempDir) - list := FalsePositivesList{serverResult: []byte("content"), outputFolder: tempDir, outputFileName: "a.out"} - fmt.Printf("list: %q\n", list) + list := FalsePositivesList{serverResult: []byte("content"), outputFolder: tempDir, outputFileName: "a.out"} + fmt.Printf("list: %q\n", list) - var config Config - context := NewContext(&config) + var config Config + context := NewContext(&config) - /* execute */ - list.save(context) + /* execute */ + list.save(context) - /* test */ - expected := filepath.Join(tempDir, "a.out") - sechubTestUtil.AssertFileExists(expected, t) + /* test */ + expected := filepath.Join(tempDir, "a.out") + sechubTestUtil.AssertFileExists(expected, t) } func Example_defineFalsePositives() { - /* prepare */ - definedFalsePositives := []FalsePositivesJobData{ - {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, - {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, - {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, - {JobUUID: "55555555-5555-5555-5555-555555555555", FindingID: 5, Comment: "test5"}, - } - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - - falsePositivesServerList := []FalsePositiveDefinition{ - {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, - {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, - {JobData: FalsePositivesJobData{JobUUID: "44444444-4444-4444-4444-444444444444", FindingID: 4}}, - } - - /* 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:[{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:}]} + /* prepare */ + definedFalsePositives := []FalsePositivesJobData{ + {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, + {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, + {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, + {JobUUID: "55555555-5555-5555-5555-555555555555", FindingID: 5, Comment: "test5"}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + + falsePositivesServerList := []FalsePositiveDefinition{ + {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, + {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, + {JobData: FalsePositivesJobData{JobUUID: "44444444-4444-4444-4444-444444444444", FindingID: 4}}, + } + + /* 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:[{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() { - // An empty input list will remove all defined false-positives + // An empty input list will remove all defined false-positives - /* prepare */ - definedFalsePositives := []FalsePositivesJobData{} - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + /* prepare */ + definedFalsePositives := []FalsePositivesJobData{} + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - falsePositivesServerList := []FalsePositiveDefinition{ - {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, - {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, - } + falsePositivesServerList := []FalsePositiveDefinition{ + {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, + {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, + } - /* execute */ - falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) - /* test */ - fmt.Printf("Add: %+v\n", falsePositivesToAdd) - fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + /* test */ + fmt.Printf("Add: %+v\n", falsePositivesToAdd) + 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:}]} + // Output: + // 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() { - // An empty server list will simply add all defined false-positives + // An empty server list will simply add all defined false-positives - /* prepare */ - definedFalsePositives := []FalsePositivesJobData{ - {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, - {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, - } - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + /* prepare */ + definedFalsePositives := []FalsePositivesJobData{ + {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, + {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - falsePositivesServerList := []FalsePositiveDefinition{} + falsePositivesServerList := []FalsePositiveDefinition{} - /* execute */ - falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) - /* test */ - fmt.Printf("Add: %+v\n", falsePositivesToAdd) - fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + /* test */ + fmt.Printf("Add: %+v\n", falsePositivesToAdd) + 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:[]} + // 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}] ProjectData:[]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} } func Example_getFalsePositivesUploadChunk1() { - /* prepare */ + /* prepare */ - // Override global MaxChunkSizeFalsePositives to get reproducable results - MaxChunkSizeFalsePositives = 2 + // Override global MaxChunkSizeFalsePositives to get reproducable results + MaxChunkSizeFalsePositives = 2 - definedFalsePositives := []FalsePositivesJobData{ - {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, - {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, - {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, - } - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + definedFalsePositives := []FalsePositivesJobData{ + {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, + {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, + {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - /* execute */ - fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 0)) - fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 1)) - fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 2)) + /* execute */ + fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 0)) + fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 1)) + fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 2)) - /* test */ + /* 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:[]} + // 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}] 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-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 df2d2553bc..5cf6b9a033 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 @@ -18,7 +18,7 @@ "comment": "It was verified that there is no SQL-injection vulnerability at this location", "webScan": { //<9> "cweId": 89, //<10> - "urlPattern": "https://*.example.com/rest/products/search*", //<11> + "urlPattern": "https://*.example.com/rest/products/search?*", //<11> "methods": [ "GET", "DELETE" ] //<12> } } 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 f8cceeb14b..95c48f06e3 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 @@ -48,7 +48,7 @@ the existing false positive entry will be overwritten. The `id` is also mandator 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`. <11> `urlPattern` specifies a URL pattern to identify a false positive. This is a `mandatory` field. -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`. +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?*`. <12> `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. From c957342df7c433a8979ca37e9bf021a09a4a348d Mon Sep 17 00:00:00 2001 From: Laura Date: Tue, 3 Sep 2024 17:29:50 +0200 Subject: [PATCH 12/30] Refactored ArchUnit root import path (#3396) * Refactored import path #3395 * Removed SystemSupport for ArchUnit #3395 * Removed predefined import options #3395 * Replaced package import through path import and ignore JAR import #3395 * Ignore generated API in coding rules #3395 --- .../archunit/ArchUnitImportOptions.java | 170 ++++++------------ .../archunit/ArchUnitRuntimeSupport.java | 60 ------- .../com/sechub/archunit/CodingRulesTest.java | 45 ++--- .../archunit/DomainAccessRulesTest.java | 11 +- .../sechub/archunit/NamingConventionTest.java | 18 +- 5 files changed, 82 insertions(+), 222 deletions(-) delete mode 100644 sechub-archunit-test/src/test/java/mercedesbenz/com/sechub/archunit/ArchUnitRuntimeSupport.java 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() From e6533065bd2e196799a612bdc999e43340293dc4 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Wed, 4 Sep 2024 12:50:24 +0200 Subject: [PATCH 13/30] Improve default value handling #3408 - use int for cweId type with default zero - make sure jobData and projectData lists are empty when missing - update documentation --- .../false-positives-howto-define-by-api.adoc | 1 + ...rojectDataWebScanFalsePositiveSupport.java | 33 +++-------- ...bScanProjectDataFalsePositiveStrategy.java | 2 +- ...tDataWebScanFalsePositiveSupportTest.java} | 56 ++++++++++--------- .../scan/project/FalsePositiveDataList.java | 11 ++++ .../FalsePositiveDataListValidationImpl.java | 5 +- .../WebscanFalsePositiveProjectData.java | 6 +- .../project/FalsePositiveDataListTest.java | 27 ++++++++- ...PositiveProjectDataValidationImplTest.java | 1 + 9 files changed, 81 insertions(+), 61 deletions(-) rename sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{SerecoWebScanFalsePositiveProjectDataSupportTest.java => SerecoProjectDataWebScanFalsePositiveSupportTest.java} (79%) 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 95c48f06e3..18075cc384 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 @@ -47,6 +47,7 @@ the existing false positive entry will be overwritten. The `id` is also mandator <10> `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`. +Since it is the integer ID of the CWE, fetching the false positive configuration will contain `"cweId": 0` since cweId zero does not exist. <11> `urlPattern` specifies a URL pattern to identify a false positive. This is a `mandatory` field. 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?*`. <12> `methods` _(optional)_ can be used to further restrict the false positive matching, to specific request methods protocols, like GET, POST, etc. 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 63b2bb6994..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,32 +34,17 @@ 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; + String cweIdAsString = String.valueOf(cweId); + return cweIdAsString.equals(serecoCWE); } /** - * 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. + * Then tries to match the given targetUrl against each pattern of the + * projectDataPatternMap. * * @param targetUrl * @param projectDataPatternMap - * @return true if the given host matches any of the given patterns + * @return true if the given URL matches any of the given patterns */ public boolean isMatchingUrlPattern(String targetUrl, Map projectDataPatternMap) { notNull(targetUrl, " host may not be null"); 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 52b3ccbca0..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 @@ -68,7 +68,7 @@ public boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveP } /* ---------------------------------------------------- */ - /* -------------------SERVERS-------------------------- */ + /* ----------------------URL--------------------------- */ /* ---------------------------------------------------- */ if (!webscanFalsePositiveProjectDataSupport.isMatchingUrlPattern(targetUrl, projectDataPatternMap)) { return false; 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/SerecoProjectDataWebScanFalsePositiveSupportTest.java similarity index 79% rename from sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoWebScanFalsePositiveProjectDataSupportTest.java rename to sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupportTest.java index b9e863f1d4..785433bffe 100644 --- 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/SerecoProjectDataWebScanFalsePositiveSupportTest.java @@ -22,9 +22,9 @@ import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; -class SerecoWebScanFalsePositiveProjectDataSupportTest { +class SerecoProjectDataWebScanFalsePositiveSupportTest { - private static String matchingUrl = "https://prod.example.com/rest/profile/search"; + 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(); @@ -41,13 +41,11 @@ void beforeEach() { patternMap = new HashMap<>(); patternMap.put("id", MOCKED_PATTERN); - when(MOCKED_PATTERN.matcher(matchingUrl)).thenReturn(MOCKED_MATCHER); + when(MOCKED_PATTERN.matcher(MATCHING_URL)).thenReturn(MOCKED_MATCHER); } /*-------------------------------------CWE-IDs----------------------------------------------*/ @ParameterizedTest - @EmptySource - @NullSource @ValueSource(strings = { "1", "-1", "0", "4711" }) void both_having_the_same_cwe_id_returns_true(String cweId) { /* prepare */ @@ -64,8 +62,6 @@ void both_having_the_same_cwe_id_returns_true(String cweId) { } @ParameterizedTest - @EmptySource - @NullSource @ValueSource(strings = { "1", "-1", "0", "4711" }) void cwe_id_of_webscan_data_is_one_more_returns_false(String cweId) { /* prepare */ @@ -83,9 +79,8 @@ void cwe_id_of_webscan_data_is_one_more_returns_false(String cweId) { } @ParameterizedTest - @NullSource @ValueSource(ints = { 1, -1, 0, 4711 }) - void cwe_id_of_vulnerability_is_one_more_returns_false(Integer cweId) { + void cwe_id_of_vulnerability_is_one_more_returns_false(int cweId) { /* prepare */ WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); webScanData.setCweId(cweId); @@ -100,6 +95,24 @@ void cwe_id_of_vulnerability_is_one_more_returns_false(Integer cweId) { 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 @@ -152,7 +165,7 @@ void for_urlPattern_not_matching_returns_false() { when(MOCKED_MATCHER.matches()).thenReturn(false); /* execute */ - boolean result = supportToTest.isMatchingUrlPattern(matchingUrl, patternMap); + boolean result = supportToTest.isMatchingUrlPattern(MATCHING_URL, patternMap); /* test */ assertFalse(result); @@ -164,7 +177,7 @@ void for_urlPattern_is_matching_returns_true() { when(MOCKED_MATCHER.matches()).thenReturn(true); /* execute */ - boolean result = supportToTest.isMatchingUrlPattern(matchingUrl, patternMap); + boolean result = supportToTest.isMatchingUrlPattern(MATCHING_URL, patternMap); /* test */ assertTrue(result); @@ -172,29 +185,22 @@ void for_urlPattern_is_matching_returns_true() { /*-------------------------------------HELPERS----------------------------------------------*/ - private Integer createIntegerFromString(String cweId) { + private int createIntegerFromString(String cweId) { if (cweId == null) { - return null; + return 0; } if (cweId.isEmpty()) { - return null; + return 0; } return Integer.parseInt(cweId); } - private String createIntAsStringButPlusOne(Integer cweId) { - if (cweId == null) { - return "1"; - } - int next = cweId.intValue() + 1; - return String.valueOf(next); + private String createIntAsStringButPlusOne(int cweId) { + return String.valueOf(cweId + 1); } - private Integer createAsIntButPlusOne(String cweId) { - Integer intvalue = createIntegerFromString(cweId); - if (intvalue == null) { - return 1; - } + private int createAsIntButPlusOne(String cweId) { + int intvalue = createIntegerFromString(cweId); return intvalue + 1; } 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 501fcbbbb8..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 @@ -10,15 +10,15 @@ public class WebscanFalsePositiveProjectData implements ProjectData { public static final String PROPERTY_URLPATTERN = "urlPattern"; public static final String PROPERTY_METHODS = "methods"; - private Integer cweId; + 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; } 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..ab8bf5bf1c 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"); @@ -36,4 +37,24 @@ public void json_content_as_described_in_example_of_documentation() { 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 5a1d4ac2ab..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 @@ -52,6 +52,7 @@ void without_optional_parts_returns_valid_result() { /* test */ assertTrue(result.isValid()); + assertEquals(0, webScan.getCweId()); } @Test From b22b33f91deef34cedbbe6e195aa801c99d27414 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Wed, 4 Sep 2024 12:56:58 +0200 Subject: [PATCH 14/30] added projectData handling #3390 --- .../sechub/cli/false-positives.go | 27 ++++++++++++++----- .../sechub/cli/false-positives_test.go | 8 +++--- 2 files changed, 24 insertions(+), 11 deletions(-) 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 30a6b7c31e..5f088e75b6 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 @@ -254,22 +254,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)) 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 670e893920..d8179c6741 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 @@ -191,9 +191,9 @@ func Example_newFalsePositivesListFromBytes_projectData() { "id": "my-id", "comment": "text1", "webScan": { - "cweId": 89, - "urlPattern": "https://myapp-*.example.com:80*/rest/*/search?*", - "methods": [ "GET", "DELETE" ] + "cweId": 89, + "urlPattern": "https://myapp-*.example.com:80*/rest/*/search?*", + "methods": [ "GET", "DELETE" ] } } ] @@ -223,7 +223,7 @@ func Example_newFalsePositivesListFromBytes_projectDataWithoutCWE() { { "id": "my-id", "webScan": { - "urlPattern": "https://myapp-*.example.com/rest/login?*" + "urlPattern": "https://myapp-*.example.com/rest/login?*" } } ] From c5e53c1ac19757f7a16cb34af879a9a42774e6b1 Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Wed, 4 Sep 2024 15:29:22 +0200 Subject: [PATCH 15/30] Change behaviour to update fp projectData entry when same id is sent again #3408 --- .../false-positives-howto-define-by-api.adoc | 2 +- .../FalsePositiveDataConfigMerger.java | 28 +++++++++++++------ .../project/FalsePositiveDataService.java | 2 +- .../FalsePositiveDataConfigMergerTest.java | 10 +++---- 4 files changed, 26 insertions(+), 16 deletions(-) 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 18075cc384..67cd2f4a6b 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 @@ -23,7 +23,7 @@ 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. There are no dependencies because all information necessary to identify certain findings are specified via `REST`. -Each entry can be removed by the given `id`. +Each entry can be updated or removed by the given `id`. Marking a projectData entry with a already existing `id` again, will update its content with the new data. ==== *Example JSON* 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 fed3eca693..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 @@ -1,6 +1,8 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.project; +import java.util.List; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -50,19 +52,27 @@ public void addJobDataWithMetaDataToConfig(ScanSecHubReport report, FalsePositiv } - public void addFalsePositiveProjectDataEntry(FalsePositiveProjectConfiguration config, FalsePositiveProjectData projectData, String userId) { - FalsePositiveEntry existingEntry = findExistingProjectDataFalsePositiveEntryInConfig(config, projectData); - - if (existingEntry != null) { - LOG.warn("False positive projectData entry:'{}' not added, because already existing", projectData.getId()); - return; - } - + public void addFalsePositiveProjectDataEntryOrUpdateExisting(FalsePositiveProjectConfiguration config, FalsePositiveProjectData projectData, + String userId) { FalsePositiveEntry projectDataEntry = new FalsePositiveEntry(); projectDataEntry.setAuthor(userId); projectDataEntry.setProjectData(projectData); - config.getFalsePositives().add(projectDataEntry); + List falsePositives = config.getFalsePositives(); + for (int index = 0; index < falsePositives.size(); index++) { + FalsePositiveEntry existingFPEntry = falsePositives.get(index); + FalsePositiveProjectData projectDataFromEntry = existingFPEntry.getProjectData(); + if (projectDataFromEntry == null) { + LOG.debug("The entry is a jobData entry with metaData so no projectData"); + continue; + } + if (projectDataFromEntry.getId().equals(projectData.getId())) { + LOG.info("False positive project data entry with id: '{}', will be updated with new data!", projectData.getId()); + falsePositives.set(index, projectDataEntry); + return; + } + } + falsePositives.add(projectDataEntry); } public void removeJobDataWithMetaDataFromConfig(FalsePositiveProjectConfiguration config, FalsePositiveJobData jobDataToRemove) { diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java index af4141cf44..3a4ef42ebe 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java @@ -156,7 +156,7 @@ private void addJobDataListToConfiguration(FalsePositiveProjectConfiguration con List projectDataList = dataList.getProjectData(); for (FalsePositiveProjectData projectData : projectDataList) { - merger.addFalsePositiveProjectDataEntry(config, projectData, userContextService.getUserId()); + merger.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData, userContextService.getUserId()); } } 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 00bb809a94..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 @@ -192,7 +192,7 @@ void add_one_project_data_entry_results_in_one_entry_in_config() { projectData.setWebScan(webScan); /* execute */ - toTest.addFalsePositiveProjectDataEntry(config, projectData, TEST_AUTHOR); + toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData, TEST_AUTHOR); /* test */ List falsePositives = config.getFalsePositives(); @@ -207,7 +207,7 @@ void add_one_project_data_entry_results_in_one_entry_in_config() { } @Test - void add_one_project_data_entry_which_already_exists_results_in_entry_with_same_id_not_added_again() { + 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(); @@ -234,7 +234,7 @@ void add_one_project_data_entry_which_already_exists_results_in_entry_with_same_ projectData2.setWebScan(webScan2); /* execute */ - toTest.addFalsePositiveProjectDataEntry(config, projectData2, TEST_AUTHOR); + toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData2, TEST_AUTHOR); /* test */ List falsePositives = config.getFalsePositives(); @@ -245,7 +245,7 @@ void add_one_project_data_entry_which_already_exists_results_in_entry_with_same_ assertNull(existingEntry.getMetaData()); assertEquals(TEST_AUTHOR, existingEntry.getAuthor()); - assertEquals(projectData1, existingEntry.getProjectData()); + assertEquals(projectData2, existingEntry.getProjectData()); } @Test @@ -277,7 +277,7 @@ void add_a_second_project_data_entry_which_has_an_unique_id_results_in_two_entri projectData2.setWebScan(webScan2); /* execute */ - toTest.addFalsePositiveProjectDataEntry(config, projectData2, TEST_AUTHOR); + toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData2, TEST_AUTHOR); /* test */ List falsePositives = config.getFalsePositives(); From 48ae5cc9c41b2812b16a59607c8384a7957614ec Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Wed, 4 Sep 2024 17:23:13 +0200 Subject: [PATCH 16/30] added unmark false-positives for projectData #3390 --- .../sechub/cli/false-positives.go | 34 ++++++++++++++----- .../sechub/cli/urlbuilder.go | 5 +++ 2 files changed, 30 insertions(+), 9 deletions(-) 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 5f088e75b6..fdbac9d39e 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 @@ -296,24 +296,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 JobData 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) } 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) } From 83da7e4f7e2ac5585543dea78b439f0cbf5b46c0 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Thu, 5 Sep 2024 10:04:22 +0200 Subject: [PATCH 17/30] change to tab indentation instead of blanks #3390 --- .../sechub/cli/false-positives_test.go | 330 +++++++++--------- 1 file changed, 165 insertions(+), 165 deletions(-) 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 d8179c6741..e6165f9ef4 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 @@ -13,232 +13,232 @@ import ( ) func TestFalsePositivesFilePathCorrectCreated(t *testing.T) { - /* prepare */ - list := FalsePositivesList{serverResult: []byte("content"), outputFolder: "path1", outputFileName: "fileName1"} + /* prepare */ + list := FalsePositivesList{serverResult: []byte("content"), outputFolder: "path1", outputFileName: "fileName1"} - /* execute */ - result := list.createFilePath(false) + /* execute */ + result := list.createFilePath(false) - /* test */ - expected := filepath.Join("path1", "fileName1") - if result != expected { - t.Fatalf("Strings differ:\nExpected:%s\nGot :%s", expected, result) - } + /* test */ + expected := filepath.Join("path1", "fileName1") + if result != expected { + t.Fatalf("Strings differ:\nExpected:%s\nGot :%s", expected, result) + } } func TestFalsePositivesSaveWritesAFile(t *testing.T) { - /* prepare */ - tempDir := sechubTestUtil.InitializeTestTempDir(t) - defer os.RemoveAll(tempDir) + /* prepare */ + tempDir := sechubTestUtil.InitializeTestTempDir(t) + defer os.RemoveAll(tempDir) - list := FalsePositivesList{serverResult: []byte("content"), outputFolder: tempDir, outputFileName: "a.out"} - fmt.Printf("list: %q\n", list) + list := FalsePositivesList{serverResult: []byte("content"), outputFolder: tempDir, outputFileName: "a.out"} + fmt.Printf("list: %q\n", list) - var config Config - context := NewContext(&config) + var config Config + context := NewContext(&config) - /* execute */ - list.save(context) + /* execute */ + list.save(context) - /* test */ - expected := filepath.Join(tempDir, "a.out") - sechubTestUtil.AssertFileExists(expected, t) + /* test */ + expected := filepath.Join(tempDir, "a.out") + sechubTestUtil.AssertFileExists(expected, t) } func Example_defineFalsePositives() { - /* prepare */ - definedFalsePositives := []FalsePositivesJobData{ - {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, - {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, - {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, - {JobUUID: "55555555-5555-5555-5555-555555555555", FindingID: 5, Comment: "test5"}, - } - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - - falsePositivesServerList := []FalsePositiveDefinition{ - {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, - {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, - {JobData: FalsePositivesJobData{JobUUID: "44444444-4444-4444-4444-444444444444", FindingID: 4}}, - } - - /* 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:[{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:[]} + /* prepare */ + definedFalsePositives := []FalsePositivesJobData{ + {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, + {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, + {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, + {JobUUID: "55555555-5555-5555-5555-555555555555", FindingID: 5, Comment: "test5"}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + + falsePositivesServerList := []FalsePositiveDefinition{ + {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, + {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, + {JobData: FalsePositivesJobData{JobUUID: "44444444-4444-4444-4444-444444444444", FindingID: 4}}, + } + + /* 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:[{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() { - // An empty input list will remove all defined false-positives + // An empty input list will remove all defined false-positives - /* prepare */ - definedFalsePositives := []FalsePositivesJobData{} - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + /* prepare */ + definedFalsePositives := []FalsePositivesJobData{} + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - falsePositivesServerList := []FalsePositiveDefinition{ - {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, - {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, - } + falsePositivesServerList := []FalsePositiveDefinition{ + {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1}}, + {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2}}, + } - /* execute */ - falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) - /* test */ - fmt.Printf("Add: %+v\n", falsePositivesToAdd) - fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + /* 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:[{JobUUID:11111111-1111-1111-1111-111111111111 FindingID:1 Comment:} {JobUUID:22222222-2222-2222-2222-222222222222 FindingID:2 Comment:}] ProjectData:[]} + // Output: + // 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() { - // An empty server list will simply add all defined false-positives + // An empty server list will simply add all defined false-positives - /* prepare */ - definedFalsePositives := []FalsePositivesJobData{ - {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, - {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, - } - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + /* prepare */ + definedFalsePositives := []FalsePositivesJobData{ + {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, + {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - falsePositivesServerList := []FalsePositiveDefinition{} + falsePositivesServerList := []FalsePositiveDefinition{} - /* execute */ - falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) + /* execute */ + falsePositivesToAdd, falsePositivesToRemove := defineFalsePositives(falsePositivesDefinitionList, falsePositivesServerList) - /* test */ - fmt.Printf("Add: %+v\n", falsePositivesToAdd) - fmt.Printf("Remove: %+v\n", falsePositivesToRemove) + /* test */ + fmt.Printf("Add: %+v\n", falsePositivesToAdd) + 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}] ProjectData:[]} - // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} + // 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}] ProjectData:[]} + // Remove: {APIVersion:1.0 Type:falsePositiveJobDataList JobData:[] ProjectData:[]} } func Example_getFalsePositivesUploadChunk1() { - /* prepare */ + /* prepare */ - // Override global MaxChunkSizeFalsePositives to get reproducable results - MaxChunkSizeFalsePositives = 2 + // Override global MaxChunkSizeFalsePositives to get reproducable results + MaxChunkSizeFalsePositives = 2 - definedFalsePositives := []FalsePositivesJobData{ - {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, - {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, - {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, - } - falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} + definedFalsePositives := []FalsePositivesJobData{ + {JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}, + {JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}, + {JobUUID: "33333333-3333-3333-3333-333333333333", FindingID: 3, Comment: "test3"}, + } + falsePositivesDefinitionList := FalsePositivesConfig{APIVersion: CurrentAPIVersion, Type: falsePositivesListType, JobData: definedFalsePositives} - /* execute */ - fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 0)) - fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 1)) - fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 2)) + /* execute */ + fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 0)) + fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 1)) + fmt.Printf("%+v\n", getFalsePositivesUploadChunk(falsePositivesDefinitionList, 2)) - /* test */ + /* 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}] 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:[]} + // 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}] 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 := ` + /* 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 - } - ] + "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) + inputfile := []byte(fpJSON) - /* execute */ - fpList := newFalsePositivesListFromBytes(inputfile) + /* execute */ + fpList := newFalsePositivesListFromBytes(inputfile) - /* test */ - jsonBlob, _ := json.Marshal(fpList) - fmt.Println(string(jsonBlob)) + /* 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} + // 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 := ` + /* 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" ] - } - } - ] + "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) + inputfile := []byte(fpJSON) - /* execute */ - fpList := newFalsePositivesListFromBytes(inputfile) + /* execute */ + fpList := newFalsePositivesListFromBytes(inputfile) - /* test */ - jsonBlob, _ := json.Marshal(fpList) - fmt.Println(string(jsonBlob)) + /* 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"]}}]} + // 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 := ` + /* prepare */ + fpJSON := ` { - "apiVersion": "1.0", - "type": "falsePositiveDataList", - "projectData": [ - { - "id": "my-id", - "webScan": { - "urlPattern": "https://myapp-*.example.com/rest/login?*" - } - } - ] + "apiVersion": "1.0", + "type": "falsePositiveDataList", + "projectData": [ + { + "id": "my-id", + "webScan": { + "urlPattern": "https://myapp-*.example.com/rest/login?*" + } + } + ] } - ` + ` - inputfile := []byte(fpJSON) + inputfile := []byte(fpJSON) - /* execute */ - fpList := newFalsePositivesListFromBytes(inputfile) + /* execute */ + fpList := newFalsePositivesListFromBytes(inputfile) - /* test */ - jsonBlob, _ := json.Marshal(fpList) - fmt.Println(string(jsonBlob)) + /* 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}}]} + // 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}}]} } From 94deb0ad1f43ddcf0eba527a8ded4119c641046f Mon Sep 17 00:00:00 2001 From: Jan Winz Date: Thu, 5 Sep 2024 10:21:50 +0200 Subject: [PATCH 18/30] Handle logging for project data false positive handling #3408 --- .../SecHubServerMDCAsyncHandlerInterceptor.java | 5 ++++- ...SecHubServerMDCAsyncHandlerInterceptorTest.java | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) 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 */ From b844cb9691d27b900e25739757dc5a817cd0e036 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Thu, 5 Sep 2024 17:05:04 +0200 Subject: [PATCH 19/30] defineFalsePositives implemented #3390 - now projectData is properlyprocessed - added tests --- .../sechub/cli/false-positives.go | 62 ++++++--- .../sechub/cli/false-positives_test.go | 120 +++++++++++++++++- .../documents/client/02_sechub_client.adoc | 2 +- 3 files changed, 165 insertions(+), 19 deletions(-) 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 fdbac9d39e..efa99dacd7 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,12 @@ import ( "io" "os" "path/filepath" + "slices" sechubUtil "mercedes-benz.com/sechub/util" ) -// Keyword for false-posisitves json file +// Keyword for false-posisitives json file const falsePositivesListType = "falsePositiveJobDataList" // FalsePositivesList - structure for handling download of false-positive lists @@ -44,7 +45,7 @@ type FalsePositivesProjectData struct { WebScan FalsePositivesProjectDataForWebScan `json:"webScan"` } -// FalsePositivesProjectDataForWebScan - define pattern for false-posisitves in web scans +// FalsePositivesProjectDataForWebScan - contains the definition for false-posisitives in web scans type FalsePositivesProjectDataForWebScan struct { CweID int `json:"cweId"` UrlPattern string `json:"urlPattern"` @@ -58,10 +59,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 @@ -164,11 +166,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) @@ -194,7 +196,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 { @@ -212,12 +214,38 @@ func defineFalsePositives(newFalsePositives FalsePositivesConfig, currentFalsePo } } + 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) + } + if falsePositiveToBeRemoved.ProjectData.ID != "" { + falsePositivesToRemove.ProjectData = append(falsePositivesToRemove.ProjectData, falsePositiveToBeRemoved.ProjectData) + } } return falsePositivesToAdd, falsePositivesToRemove @@ -397,16 +425,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) 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 e6165f9ef4..c29cf7c0a2 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 @@ -45,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"}, @@ -73,7 +73,7 @@ func Example_defineFalsePositives() { // 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 */ @@ -97,7 +97,7 @@ func Example_defineFalsePositivesEmptyInputList() { // 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 */ @@ -121,6 +121,120 @@ func Example_defineFalsePositivesEmptyServerList() { // 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, Comment: "test1"}}, + {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}}, + {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() { /* prepare */ 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 d9cf25eea6..0d5a70e7e9 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 < Date: Fri, 6 Sep 2024 10:43:40 +0200 Subject: [PATCH 20/30] interactiveUnmarkFalsePositives implemented #3390 --- .../sechub/cli/false-positives.go | 73 ++++++++++++++----- .../sechub/cli/false-positives_test.go | 4 +- 2 files changed, 55 insertions(+), 22 deletions(-) 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 efa99dacd7..42e7ad184e 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 @@ -9,6 +9,7 @@ import ( "os" "path/filepath" "slices" + "strings" sechubUtil "mercedes-benz.com/sechub/util" ) @@ -466,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 } @@ -489,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"}, @@ -505,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" { @@ -518,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 c29cf7c0a2..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 @@ -217,8 +217,8 @@ func Example_defineFalsePositivesWhenIdenticalToServerList() { } falsePositivesServerList := []FalsePositiveDefinition{ - {JobData: FalsePositivesJobData{JobUUID: "11111111-1111-1111-1111-111111111111", FindingID: 1, Comment: "test1"}}, - {JobData: FalsePositivesJobData{JobUUID: "22222222-2222-2222-2222-222222222222", FindingID: 2, Comment: "test2"}}, + {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/*"}}}, } From e3b218136cdb7c660efb281360abb8da12210927 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 10:53:49 +0200 Subject: [PATCH 21/30] branch logic improved #3390 --- sechub-cli/src/mercedes-benz.com/sechub/cli/false-positives.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 42e7ad184e..87c90c811c 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 @@ -243,8 +243,7 @@ func defineFalsePositives(newFalsePositives FalsePositivesConfig, currentFalsePo for _, falsePositiveToBeRemoved := range currentFalsePositives { if falsePositiveToBeRemoved.JobData.JobUUID != "" { falsePositivesToRemove.JobData = append(falsePositivesToRemove.JobData, falsePositiveToBeRemoved.JobData) - } - if falsePositiveToBeRemoved.ProjectData.ID != "" { + } else if falsePositiveToBeRemoved.ProjectData.ID != "" { falsePositivesToRemove.ProjectData = append(falsePositivesToRemove.ProjectData, falsePositiveToBeRemoved.ProjectData) } } From b4bc0796460977244971931e62d6e4cc7aa9b437 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 12:11:27 +0200 Subject: [PATCH 22/30] documentation improved #3390 --- .../sechub/cli/false-positives.go | 2 +- .../false-positives-howto-define-by-api.adoc | 46 ++++++++----------- 2 files changed, 21 insertions(+), 27 deletions(-) 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 87c90c811c..e72ff23a26 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 @@ -348,7 +348,7 @@ func unmarkFalsePositives(context *Context, list *FalsePositivesConfig) { } if len(list.ProjectData) > 0 { - // Iterate over JobData list: + // 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) 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 67cd2f4a6b..1bd8abe2bb 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 @@ -19,11 +19,10 @@ meta data. [NOTE] ==== -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. +The `projectData` approach is more powerful for the user because wildcardscan be used. -There are no dependencies because all information necessary to identify certain findings are specified via `REST`. -Each entry can be updated or removed by the given `id`. Marking a projectData entry with a already existing `id` again, will update its content with the new data. +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. ==== *Example JSON* @@ -32,34 +31,29 @@ Each entry can be updated or removed by the given `id`. Marking a projectData en ---- include::false-positives-REST-API-content-example1.json[] ---- -<1> API version -<2> type - must be `falsePositiveDataList` or the deprecated type `falsePositiveJobDataList`. -<3> List of jobData _(optional)_ that is used to mark a single finding as false positive -<4> SecHub Job-UUID of the report where the finding was -<5> finding-ID which shall be marked as false positive -<6> A comment (optional) describing the reason why this is a false positive -<7> List of 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. -<8> `id` that identifies this entry. If the same `id` is used again, +<1> `apiVersion` _(mandatory)_ - API version +<2> `type` _(mandatory)_ - must be `falsePositiveDataList` +<3> `jobData` _(optional)_ List of that is used to mark a single finding as 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 +<7> `projectData` _(optional)_ List that can be used to mark more than a single finding as false positive. Currently only available for web scans. +<8> 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. -<9> `webScan` _(optional)_ section can be used to define false positive patterns for web scans to provide more possibilities to the user. -<10> `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`. -Since it is the integer ID of the CWE, fetching the false positive configuration will contain `"cweId": 0` since cweId zero does not exist. -<11> `urlPattern` specifies a URL pattern to identify a false positive. This is a `mandatory` field. +<9> 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. +<10> 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`. +<11> 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?*`. -<12> `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. +<12> projectData.webScan.`methods` _(optional)_ Can be used to further restrict the false positive matching, to specific request methods protocols, like GET, POST, etc. Important information on the wildcard approach in `projectData`, regarding web scans: + -- To be a false positive a finding must match the `cweId` and the `urlPattern` of at least one of the false positive entries. - 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. - If no `methods` are specified, this false positive entry will not be restricted to any method. + +- To be a false positive a finding must match the `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. From de7b612cc987714aba03fc07c4ce3ad12b29e6ae Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 12:36:15 +0200 Subject: [PATCH 23/30] documentation improved #3390 --- ...e-positives-REST-API-content-example1.json | 13 +----- ...e-positives-REST-API-content-example2.json | 15 +++++++ .../false-positives-howto-define-by-api.adoc | 45 +++++++++++-------- 3 files changed, 42 insertions(+), 31 deletions(-) create mode 100644 sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example2.json 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 5cf6b9a033..8c9eca2fef 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 @@ -11,16 +11,5 @@ "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", "findingId": 15 } - ], - "projectData": [ //<7> - { - "id": "unique-id", //<8> - "comment": "It was verified that there is no SQL-injection vulnerability at this location", - "webScan": { //<9> - "cweId": 89, //<10> - "urlPattern": "https://*.example.com/rest/products/search?*", //<11> - "methods": [ "GET", "DELETE" ] //<12> - } - } ] -} \ 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..885511fac4 --- /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 vulnerability at this location", + "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 1bd8abe2bb..30d736f2a2 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,31 +1,27 @@ // SPDX-License-Identifier: MIT [[section-false-positives-define-by-API]] -Defining false positives is done by sending false positive information via `JSON` either +Defining false positives is done by declaring false positive information in a `JSON` file - either -- by referencing *job results* from 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 +- by referencing *<>* from former {sechub} job UUID and the corresponding finding entry (by id) or +- by specifying a *<>* section where specific patterns that match false positive findings are declared and post it to the SecHub server REST API. [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 `REST` controller logic reads the job result data and creates internally false positive -meta data. +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 `projectData` approach is more powerful for the user because wildcardscan be used. +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`. +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. ==== -*Example JSON* +[[section-false-positives-defined-via-jobData]] +*Example JSON using job results* [source,json] ---- @@ -33,21 +29,32 @@ include::false-positives-REST-API-content-example1.json[] ---- <1> `apiVersion` _(mandatory)_ - API version <2> `type` _(mandatory)_ - must be `falsePositiveDataList` -<3> `jobData` _(optional)_ List of that is used to mark a single finding as false positive +<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 -<7> `projectData` _(optional)_ List that can be used to mark more than a single finding as false positive. Currently only available for web scans. -<8> projectData.`id` that identifies this entry. If the same `id` is used again, + + +[[section-false-positives-defined-via-projectData]] +*Example JSON using project data* + +[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. -<9> 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. -<10> projectData.webScan.`cweId` is used to mark a certain type of finding as false positive. + +<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`. -<11> projectData.webScan.`urlPattern` (_mandatory_) specifies an URL pattern to identify a false positive. +<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?*`. -<12> projectData.webScan.`methods` _(optional)_ Can be used to further restrict the false positive matching, to specific request methods protocols, like GET, POST, etc. +<8> projectData.webScan.`methods` _(optional)_ Can be used to further restrict the false positive matching, to specific request methods protocols, like GET, POST, etc. Important information on the wildcard approach in `projectData`, regarding web scans: + From bb9717e684e25691a166b1070dd8ad5793c00421 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 12:41:42 +0200 Subject: [PATCH 24/30] typo fixed #3390 --- .../src/mercedes-benz.com/sechub/cli/false-positives.go | 4 ++-- .../src/docs/asciidoc/documents/client/02_sechub_client.adoc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 e72ff23a26..049b7738c6 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 @@ -14,7 +14,7 @@ import ( sechubUtil "mercedes-benz.com/sechub/util" ) -// Keyword for false-posisitives json file +// Keyword for false-positives json file const falsePositivesListType = "falsePositiveJobDataList" // FalsePositivesList - structure for handling download of false-positive lists @@ -46,7 +46,7 @@ type FalsePositivesProjectData struct { WebScan FalsePositivesProjectDataForWebScan `json:"webScan"` } -// FalsePositivesProjectDataForWebScan - contains the definition for false-posisitives in web scans +// FalsePositivesProjectDataForWebScan - contains the definition for false-positives in web scans type FalsePositivesProjectDataForWebScan struct { CweID int `json:"cweId"` UrlPattern string `json:"urlPattern"` 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 0d5a70e7e9..e4d8ce766d 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 < Date: Fri, 6 Sep 2024 15:23:17 +0200 Subject: [PATCH 25/30] documentation improved #3390 --- .../sechub/cli/false-positives.go | 1 + .../documents/client/02_sechub_client.adoc | 15 +++++++++++---- ..._client_falsepositive_list_example_unmark.json | 11 ++++++----- ...false-positives-REST-API-content-example1.json | 2 +- ...false-positives-REST-API-content-example2.json | 2 +- 5 files changed, 20 insertions(+), 11 deletions(-) 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 049b7738c6..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 @@ -215,6 +215,7 @@ 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 { 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 e4d8ce766d..1528b6effb 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 @@ -271,23 +271,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[] ---- -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.json b/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark.json index 46893fe5b4..dea1e98d1d 100644 --- a/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark.json +++ b/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark.json @@ -1,14 +1,15 @@ { "apiVersion": "1.0", - "type": "falsePositiveJobDataList", + "type": "falsePositiveDataList", "jobData": [ { "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", "findingId": 1 - }, + } + ], + "projectData": [ { - "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", - "findingId": 15 + "id": "unique-id" } ] -} \ No newline at end of file +} 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 8c9eca2fef..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 @@ -5,7 +5,7 @@ { "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", //<4> "findingId": 1, //<5> - "comment": "Absolute Path Traversal, can be ignored because not in deployment" //<6> + "comment": "Can be ignored because not in deployment" //<6> }, { "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", 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 index 885511fac4..d7effb39f4 100644 --- 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 @@ -4,7 +4,7 @@ "projectData": [ //<3> { "id": "unique-id", //<4> - "comment": "It was verified that there is no SQL-injection vulnerability at this location", + "comment": "It was verified that there is no SQL-injection", "webScan": { //<5> "cweId": 89, //<6> "urlPattern": "https://*.example.com/rest/products/search?*", //<7> From 842a0c3825cae71194ea00d1a71632f2dd566dbb Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 15:26:14 +0200 Subject: [PATCH 26/30] PR suggestion added #3390 --- .../false-positives/false-positives-howto-define-by-api.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 30d736f2a2..12d81c91ed 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 @@ -58,7 +58,7 @@ Asterisks can be used as wildcards e.g. if you have different environments like Important information on the wildcard approach in `projectData`, regarding web scans: + -- To be a false positive a finding must match the `cweId` and the `urlPattern` + +- 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`. + From 0b0ee5aa63de8ed1fb81decd245f512f1e728641 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 15:46:43 +0200 Subject: [PATCH 27/30] fixed failing integrationtest #3390 --- .../documents/client/02_sechub_client.adoc | 2 +- ..._client_falsepositive_list_example_unmark.json | 11 +++++------ ...e_list_example_unmark_jobData+projectData.json | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark_jobData+projectData.json 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 1528b6effb..ce480dd86c 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 @@ -291,7 +291,7 @@ Mandatory fields for unmarkFalsePositives: + [source, json] ---- -include::sechub_client_falsepositive_list_example_unmark.json[] +include::sechub_client_falsepositive_list_example_unmark_jobData+projectData.json[] ---- TIP: <> might be easier to use diff --git a/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark.json b/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark.json index dea1e98d1d..46893fe5b4 100644 --- a/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark.json +++ b/sechub-doc/src/docs/asciidoc/documents/client/sechub_client_falsepositive_list_example_unmark.json @@ -1,15 +1,14 @@ { "apiVersion": "1.0", - "type": "falsePositiveDataList", + "type": "falsePositiveJobDataList", "jobData": [ { "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", "findingId": 1 - } - ], - "projectData": [ + }, { - "id": "unique-id" + "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", + "findingId": 15 } ] -} +} \ No newline at end of file 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" + } + ] +} From e8ce9da2a89d790e76dee3d8790a25cb0ac60c3a Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 16:00:21 +0200 Subject: [PATCH 28/30] documentation improved #3390 --- .../docs/asciidoc/documents/client/02_sechub_client.adoc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) 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 ce480dd86c..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 @@ -250,10 +250,9 @@ See also < Date: Fri, 6 Sep 2024 16:11:16 +0200 Subject: [PATCH 29/30] fixed failing integrationtest #3390 --- .../sechub/domain/scan/project/FalsePositiveDataListTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 ab8bf5bf1c..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 @@ -31,7 +31,7 @@ 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()); From 7c9c244db2d9fef20b3a532cb7816496c6e23ac7 Mon Sep 17 00:00:00 2001 From: Sven Dolderer Date: Fri, 6 Sep 2024 16:25:56 +0200 Subject: [PATCH 30/30] documentation improved #3390 --- .../false-positives/false-positives-howto-define-by-api.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 12d81c91ed..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,8 +1,8 @@ // SPDX-License-Identifier: MIT [[section-false-positives-define-by-API]] -Defining false positives is done by declaring false positive information in a `JSON` file - either +Defining false positives is done by declaring false positive information in a `JSON` file -- by referencing *<>* from former {sechub} job UUID and the corresponding finding entry (by id) or +- 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 and post it to the SecHub server REST API.