From d30f1047541de44dbbb0daf768e96088a68c23cf Mon Sep 17 00:00:00 2001 From: winzj <86345915+winzj@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:45:02 +0200 Subject: [PATCH] Feature 2633 improve false positive handling for webscans (#3356) * Add mark/unmark project data false positives #2633 - add use case for deleting project data - add model for project data (currently only for webscan) - add validators with unti tests - add restdoc tests - introduced new finder to false positive marker which can handle projectData - added new interface and web false positive strategy to handle projectData - add support classes to make pattern matching easier - add unit tests for newly introduced parts - extended unit tests for updated parts - add integratiotest - extend helpers and TestApi where it was necessary - fixed smaller issues inside validation detected during integration testing --- .../admin/DeveloperAdministration.java | 2 +- ...am_falsepositives_definition_overview.puml | 66 ++- ...agram_falsepositives_runtime_overview.puml | 53 +- .../usecases/user/mark_false_positives.adoc | 10 + .../user/mark_false_positives_for_job.adoc | 6 - ...oc => unmark_false_positives_jobdata.adoc} | 2 +- .../unmark_false_positives_projectdata.adoc | 8 + ...e-positives-REST-API-content-example1.json | 16 +- .../false-positives-howto-define-by-api.adoc | 45 +- .../false-positives-technical-details.adoc | 46 +- ...alsePositiveRestControllerRestDocTest.java | 151 ++++-- .../sechub/restdoc/OpenApiSchema.java | 4 +- .../sechub/integrationtest/api/AsUser.java | 60 ++- .../scenario21/Scenario21.java | 2 +- .../PDSSolutionMockModeScenario21IntTest.java | 83 +++- .../src/main/resources/openapi.yaml | 145 +++++- ...tSourceCodeBasedFalsePositiveStrategy.java | 4 +- ...CodeScanJobDataFalsePositiveStrategy.java} | 2 +- ...cretScanJobDataFalsePositiveStrategy.java} | 2 +- .../sereco/SerecoFalsePositiveMarker.java | 65 ++- ... => SerecoJobDataFalsePositiveFinder.java} | 14 +- ...> SerecoJobDataFalsePositiveStrategy.java} | 2 +- ...=> SerecoJobDataFalsePositiveSupport.java} | 6 +- .../SerecoProjectDataFalsePositiveFinder.java | 53 ++ ...erecoProjectDataFalsePositiveStrategy.java | 22 + .../SerecoProjectDataPatternMapFactory.java | 75 +++ ...rojectDataWebScanFalsePositiveSupport.java | 162 +++++++ ... WebScanJobDataFalsePositiveStrategy.java} | 6 +- ...bScanProjectDataFalsePositiveStrategy.java | 126 +++++ ...ScanJobDataFalsePositiveStrategyTest.java} | 28 +- ...ScanJobDataFalsePositiveStrategyTest.java} | 28 +- .../sereco/SerecoFalsePositiveMarkerTest.java | 130 ++++- .../SerecoFalsePositiveSupportTest.java | 4 +- ...SerecoJobDataFalsePositiveFinderTest.java} | 32 +- ...ecoProjectDataFalsePositiveFinderTest.java | 139 ++++++ ...erecoProjectDataPatternMapFactoryTest.java | 329 +++++++++++++ ...anFalsePositiveProjectDataSupportTest.java | 343 +++++++++++++ ...ScanJobDataFalsePositiveStrategyTest.java} | 40 +- ...nProjectDataFalsePositiveStrategyTest.java | 379 +++++++++++++++ ...ava => FalsePositiveDataConfigMerger.java} | 64 ++- ...taList.java => FalsePositiveDataList.java} | 23 +- ...a => FalsePositiveDataListValidation.java} | 2 +- .../FalsePositiveDataListValidationImpl.java | 133 +++++ ...ice.java => FalsePositiveDataService.java} | 116 +++-- .../scan/project/FalsePositiveEntry.java | 11 + ...alsePositiveJobDataListValidationImpl.java | 90 ---- .../project/FalsePositiveProjectData.java | 62 +++ .../FalsePositiveProjectDataIdValidation.java | 8 + ...sePositiveProjectDataIdValidationImpl.java | 36 ++ .../FalsePositiveProjectDataValidation.java | 8 + ...alsePositiveProjectDataValidationImpl.java | 54 +++ .../project/FalsePositiveRestController.java | 37 +- .../domain/scan/project/ProjectData.java | 6 + .../WebscanFalsePositiveProjectData.java | 96 ++++ ...canFalsePositiveProjectDataValidation.java | 8 + ...alsePositiveProjectDataValidationImpl.java | 132 +++++ .../FalsePositiveDataConfigMergerTest.java | 458 ++++++++++++++++++ ...st.java => FalsePositiveDataListTest.java} | 6 +- ...lsePositiveDataListValidationImplTest.java | 169 +++++++ ...java => FalsePositiveDataServiceTest.java} | 55 ++- .../FalsePositiveJobDataConfigMergerTest.java | 183 ------- ...PositiveJobDataListValidationImplTest.java | 103 ---- ...FalsePositiveProjectConfigurationTest.java | 1 - ...sitiveProjectDataIdValidationImplTest.java | 93 ++++ ...PositiveProjectDataValidationImplTest.java | 58 +++ ...PositiveProjectDataValidationImplTest.java | 261 ++++++++++ .../usecases/UseCaseIdentifier.java | 4 +- ...va => UseCaseUserMarksFalsePositives.java} | 10 +- ...aseUserUnmarksFalsePositiveByJobData.java} | 4 +- ...UserUnmarksFalsePositiveByProjectData.java | 27 ++ .../AbstractSimpleStringValidation.java | 2 +- .../sechub/test/RestDocPathParameter.java | 2 + .../sechub/test/SecHubTestURLBuilder.java | 6 +- 73 files changed, 4287 insertions(+), 731 deletions(-) create mode 100644 sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives.adoc delete mode 100644 sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives_for_job.adoc rename sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/{unmark_false_positives.adoc => unmark_false_positives_jobdata.adoc} (84%) create mode 100644 sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives_projectdata.adoc rename sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{CodeScanFalsePositiveStrategy.java => CodeScanJobDataFalsePositiveStrategy.java} (82%) rename sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{SecretScanFalsePositiveStrategy.java => SecretScanJobDataFalsePositiveStrategy.java} (81%) rename sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{SerecoFalsePositiveFinder.java => SerecoJobDataFalsePositiveFinder.java} (77%) rename sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{SerecoFalsePositiveStrategy.java => SerecoJobDataFalsePositiveStrategy.java} (90%) rename sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{SerecoFalsePositiveSupport.java => SerecoJobDataFalsePositiveSupport.java} (91%) create mode 100644 sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinder.java create mode 100644 sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveStrategy.java create mode 100644 sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactory.java create mode 100644 sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupport.java rename sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{WebScanFalsePositiveStrategy.java => WebScanJobDataFalsePositiveStrategy.java} (94%) create mode 100644 sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategy.java rename sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{CodeScanFalsePositiveStrategyTest.java => CodeScanJobDataFalsePositiveStrategyTest.java} (79%) rename sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{SecretScanFalsePositiveStrategyTest.java => SecretScanJobDataFalsePositiveStrategyTest.java} (79%) rename sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{SerecoFalsePositiveFinderTest.java => SerecoJobDataFalsePositiveFinderTest.java} (75%) create mode 100644 sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinderTest.java create mode 100644 sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactoryTest.java create mode 100644 sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoWebScanFalsePositiveProjectDataSupportTest.java rename sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/{WebScanFalsePositiveStrategyTest.java => WebScanJobDataFalsePositiveStrategyTest.java} (77%) create mode 100644 sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategyTest.java rename sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/{FalsePositiveJobDataConfigMerger.java => FalsePositiveDataConfigMerger.java} (57%) rename sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/{FalsePositiveJobDataList.java => FalsePositiveDataList.java} (64%) rename sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/{FalsePositiveJobDataListValidation.java => FalsePositiveDataListValidation.java} (61%) create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImpl.java rename sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/{FalsePositiveJobDataService.java => FalsePositiveDataService.java} (55%) delete mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImpl.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectData.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidation.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImpl.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidation.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImpl.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ProjectData.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectData.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidation.java create mode 100644 sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImpl.java create mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMergerTest.java rename sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/{FalsePositiveJobDataListTest.java => FalsePositiveDataListTest.java} (86%) create mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImplTest.java rename sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/{FalsePositiveJobDataServiceTest.java => FalsePositiveDataServiceTest.java} (52%) delete mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataConfigMergerTest.java delete mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImplTest.java create mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImplTest.java create mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImplTest.java create mode 100644 sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImplTest.java rename sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/{UseCaseUserMarksFalsePositivesForJob.java => UseCaseUserMarksFalsePositives.java} (70%) rename sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/{UseCaseUserUnmarksFalsePositives.java => UseCaseUserUnmarksFalsePositiveByJobData.java} (87%) create mode 100644 sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositiveByProjectData.java diff --git a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java index e54020c4a8..7e0304fcc5 100644 --- a/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java +++ b/sechub-developertools/src/main/java/com/mercedesbenz/sechub/developertools/admin/DeveloperAdministration.java @@ -687,7 +687,7 @@ public String fetchProjectFalsePositiveConfiguration(String projectId) { } public String markFalsePositivesForProjectByJobData(String projectId, String json) { - return getRestHelper().putJSON(getUrlBuilder().buildUserAddsFalsePositiveJobDataListForProject(projectId), json); + return getRestHelper().putJSON(getUrlBuilder().buildUserAddsFalsePositiveDataListForProject(projectId), json); } public void deleteFalsePositivesForProject(String projectId, UUID jobUUID, int findingId) { 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 baf5a2b62c..c67328550f 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 @@ -4,37 +4,38 @@ skinparam linetype ortho hide empty methods hide empty fields package com.mercedesbenz.sechub.domain.scan.project{ -'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobDataService - class FalsePositiveJobDataService ##[bold]black { +'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataService + class FalsePositiveDataService ##[bold]black { ~ScanReportRepository : scanReportRepository ~ScanProjectConfigService : configService - ~FalsePositiveJobDataListValidation : falsePositiveJobDataListValidation - ~FalsePositiveJobDataConfigMerger : merger + ~FalsePositiveDataListValidation : falsePositiveJobDataListValidation + ~FalsePositiveDataConfigMerger : merger } - FalsePositiveJobDataService *-- FalsePositiveJobDataConfigMerger - FalsePositiveJobDataService -[#blue]- FalsePositiveJobDataList - FalsePositiveJobDataService *-- FalsePositiveJobDataListValidation - FalsePositiveJobDataService -[#blue]- FalsePositiveProjectConfiguration - FalsePositiveJobDataService *-- ScanProjectConfigService - FalsePositiveJobDataService *-- com.mercedesbenz.sechub.domain.scan.report.ScanReportRepository -'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobDataConfigMerger - class FalsePositiveJobDataConfigMerger{ + FalsePositiveDataService *-- FalsePositiveDataConfigMerger + FalsePositiveDataService -[#blue]- FalsePositiveDataList + FalsePositiveDataService *-- FalsePositiveDataListValidation + FalsePositiveDataService -[#blue]- FalsePositiveProjectConfiguration + FalsePositiveDataService *-- ScanProjectConfigService + FalsePositiveDataService *-- com.mercedesbenz.sechub.domain.scan.report.ScanReportRepository +'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataConfigMerger + class FalsePositiveDataConfigMerger{ } - FalsePositiveJobDataConfigMerger .[#green]. com.mercedesbenz.sechub.commons.model.SecHubCodeCallStack - FalsePositiveJobDataConfigMerger -[#blue]- com.mercedesbenz.sechub.commons.model.SecHubFinding - FalsePositiveJobDataConfigMerger .. FalsePositiveCodePartMetaData - FalsePositiveJobDataConfigMerger .. FalsePositiveEntry - FalsePositiveJobDataConfigMerger -[#blue]- FalsePositiveJobData - FalsePositiveJobDataConfigMerger -[#blue]- FalsePositiveMetaData - FalsePositiveJobDataConfigMerger -[#blue]- FalsePositiveProjectConfiguration - FalsePositiveJobDataConfigMerger -[#blue]- com.mercedesbenz.sechub.domain.scan.report.ScanSecHubReport -'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobDataList - class FalsePositiveJobDataList{ + FalsePositiveDataConfigMerger .[#green]. com.mercedesbenz.sechub.commons.model.SecHubCodeCallStack + FalsePositiveDataConfigMerger -[#blue]- com.mercedesbenz.sechub.commons.model.SecHubFinding + FalsePositiveDataConfigMerger .. FalsePositiveCodePartMetaData + FalsePositiveDataConfigMerger .. FalsePositiveEntry + FalsePositiveDataConfigMerger -[#blue]- FalsePositiveJobData + FalsePositiveDataConfigMerger -[#blue]- FalsePositiveMetaData + FalsePositiveDataConfigMerger -[#blue]- FalsePositiveProjectConfiguration + FalsePositiveDataConfigMerger -[#blue]- com.mercedesbenz.sechub.domain.scan.report.ScanSecHubReport +'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveDataList + class FalsePositiveDataList{ -String : apiVersion -String : type -FalsePositiveJobData : jobData } - FalsePositiveJobDataList -[#blue]- FalsePositiveJobData + FalsePositiveDataList -[#blue]- FalsePositiveJobData + FalsePositiveDataList -[#blue]- FalsePositiveProjectData 'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration class FalsePositiveProjectConfiguration{ -List : falsePositives @@ -57,6 +58,22 @@ package com.mercedesbenz.sechub.domain.scan.project{ -String : relevantPart -String : sourceCode } +'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData + class FalsePositiveProjectData{ + -String : id + -String : comment + -WebscanFalsePositiveProjectData: webScan + } +'com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData + class WebscanFalsePositiveProjectData{ + -Integer : cweId + -List : hostPatterns + -List : urlPathPatterns + -List : protocols + -List : ports + -List : methods + } + FalsePositiveProjectData *-- WebscanFalsePositiveProjectData 'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveEntry class FalsePositiveEntry{ -FalsePositiveJobData : jobData @@ -66,6 +83,7 @@ package com.mercedesbenz.sechub.domain.scan.project{ } FalsePositiveEntry -[#blue]- FalsePositiveJobData FalsePositiveEntry -[#blue]- FalsePositiveMetaData + FalsePositiveEntry -[#blue]- FalsePositiveProjectData 'com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData class FalsePositiveMetaData{ -ScanType : scanType @@ -110,4 +128,6 @@ package com.mercedesbenz.sechub.domain.scan.project{ -String: evidence -int: statuscode } + + @enduml diff --git a/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_runtime_overview.puml b/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_runtime_overview.puml index ba8f2d19fd..cade8913f2 100644 --- a/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_runtime_overview.puml +++ b/sechub-doc/src/docs/asciidoc/diagrams/diagram_falsepositives_runtime_overview.puml @@ -6,36 +6,53 @@ hide empty fields package com.mercedesbenz.sechub.domain.scan.product.sereco{ 'com.mercedesbenz.sechub.domain.scan.product.sereco.SerecoFalsePositiveMarker class SerecoFalsePositiveMarker ##[bold]black { - ~SerecoFalsePositiveFinder : falsePositiveCodeFinder + ~SerecoJobDataFalsePositiveFinder : jobDatafalsePositiveCodeFinder ~ScanProjectConfigService : scanProjectConfigService + ~SerecoProjectDataFalsePositiveFinder: projectDataFalsePositiveFinder + ~SerecoProjectDataPatternMapFactory: projectDataPatternMapFactory } - SerecoFalsePositiveMarker *-- SerecoFalsePositiveFinder + SerecoFalsePositiveMarker *-- SerecoJobDataFalsePositiveFinder + SerecoFalsePositiveMarker *-- SerecoProjectDataFalsePositiveFinder + SerecoFalsePositiveMarker *-- SerecoProjectDataPatternMapFactory SerecoFalsePositiveMarker -[#blue]- com.mercedesbenz.sechub.domain.scan.project.FalsePositiveEntry SerecoFalsePositiveMarker *-- com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigService SerecoFalsePositiveMarker -[#blue]- com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability -'com.mercedesbenz.sechub.domain.scan.product.sereco.SerecoFalsePositiveFinder - class SerecoFalsePositiveFinder{ - ~SerecoFalsePositiveCodeScanStrategy : codeSCanStrategy +'com.mercedesbenz.sechub.domain.scan.product.sereco.SerecoJobDataFalsePositiveFinder + class SerecoJobDataFalsePositiveFinder{ + ~CodeScanJobDataFalsePositiveStrategy : jobDataCodeScanStrategy + ~SecretScanJobDataFalsePositiveStrategy: jobDataSecretScanStrategy + ~WebScanJobDataFalsePositiveStrategy: jobDataSebScanStrategy } - SerecoFalsePositiveFinder *-- SerecoFalsePositiveCodeScanStrategy - SerecoFalsePositiveFinder *-- SerecoFalsePositiveWebScanStrategy - SerecoFalsePositiveFinder -[#blue]- com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData - SerecoFalsePositiveFinder -[#blue]- com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability -'com.mercedesbenz.sechub.domain.scan.product.sereco.SerecoFalsePositiveCodeScanStrategy - class SerecoFalsePositiveCodeScanStrategy{ + SerecoJobDataFalsePositiveFinder *-- CodeScanJobDataFalsePositiveStrategy + SerecoJobDataFalsePositiveFinder *-- WebScanJobDataFalsePositiveStrategy + SerecoJobDataFalsePositiveFinder *-- SecretScanJobDataFalsePositiveStrategy + SerecoJobDataFalsePositiveFinder -[#blue]- com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData + SerecoJobDataFalsePositiveFinder -[#blue]- com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability +'com.mercedesbenz.sechub.domain.scan.product.sereco.CodeScanJobDataFalsePositiveStrategy + class CodeScanJobDataFalsePositiveStrategy{ ~SerecoSourceRelevantPartResolver : relevantPartResolver } - SerecoFalsePositiveCodeScanStrategy *-- SerecoSourceRelevantPartResolver - SerecoFalsePositiveCodeScanStrategy -[#blue]- com.mercedesbenz.sechub.domain.scan.project.FalsePositiveCodePartMetaData - SerecoFalsePositiveCodeScanStrategy .[#green]. com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData - SerecoFalsePositiveCodeScanStrategy -[#blue]- com.mercedesbenz.sechub.sereco.metadata.SerecoCodeCallStackElement - SerecoFalsePositiveCodeScanStrategy .[#green]. com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability + CodeScanJobDataFalsePositiveStrategy *-- SerecoSourceRelevantPartResolver + CodeScanJobDataFalsePositiveStrategy -[#blue]- com.mercedesbenz.sechub.domain.scan.project.FalsePositiveCodePartMetaData + CodeScanJobDataFalsePositiveStrategy .[#green]. com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData + CodeScanJobDataFalsePositiveStrategy -[#blue]- com.mercedesbenz.sechub.sereco.metadata.SerecoCodeCallStackElement + CodeScanJobDataFalsePositiveStrategy .[#green]. com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability 'com.mercedesbenz.sechub.domain.scan.product.sereco.SerecoSourceRelevantPartResolver class SerecoSourceRelevantPartResolver{ } - class SerecoFalsePositiveWebScanStrategy{ + class WebScanJobDataFalsePositiveStrategy{ } +'com.mercedesbenz.sechub.domain.scan.product.sereco.SerecoProjectDataFalsePositiveFinder + class SerecoProjectDataFalsePositiveFinder{ + ~WebScanProjectDataFalsePositiveStrategy: webScanProjectDataStrategy + } + SerecoProjectDataFalsePositiveFinder *-- WebScanProjectDataFalsePositiveStrategy + +'com.mercedesbenz.sechub.domain.scan.product.sereco.WebScanProjectDataFalsePositiveStrategy + class WebScanProjectDataFalsePositiveStrategy{ + } + WebScanProjectDataFalsePositiveStrategy .[#green]. com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData + WebScanProjectDataFalsePositiveStrategy .[#green]. com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability } - @enduml 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 new file mode 100644 index 0000000000..32b5996cbf --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives.adoc @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +[[sechub-doclink-uc-user-marks-false-positives]] +A user wants to mark false positives either for a finished job or with project data not necessarily connected to a finished job. + +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. +The project data are not related to any job information. + diff --git a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives_for_job.adoc b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives_for_job.adoc deleted file mode 100644 index ffced7acad..0000000000 --- a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/mark_false_positives_for_job.adoc +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: MIT -[[sechub-doclink-uc-user-marks-false-positives-for-job]] -A user wants to mark false positives for a finished job. -This means the job has been executed, has finished without failure and job is NOT deleted. - -The user will be able to mark former job results by their given id as false positives. diff --git a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives.adoc b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives_jobdata.adoc similarity index 84% rename from sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives.adoc rename to sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives_jobdata.adoc index c9d8dcf3e5..5d3b413950 100644 --- a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives.adoc +++ b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives_jobdata.adoc @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -[[sechub-doclink-uc-user-unmarks-false-positives]] +[[sechub-doclink-uc-user-unmarks-false-positives-jobdata]] A user wants to unmark existing false positives This means the false positives has been marked before. diff --git a/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives_projectdata.adoc b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives_projectdata.adoc new file mode 100644 index 0000000000..c1bc780e5e --- /dev/null +++ b/sechub-doc/src/docs/asciidoc/documents/code2doc/usecases/user/unmark_false_positives_projectdata.adoc @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +[[sechub-doclink-uc-user-unmarks-false-positives-projectdata]] +A user wants to unmark existing false positives +This means the false positives has been marked before. + +NOTE: This will NOT change any former job report where the false positive to unmark has been filtered! + +After next scan job the former false positive is no longer filtering the finding. 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 42d5e48afc..2c9796a7bf 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,6 +1,6 @@ { "apiVersion": "1.0", //<1> - "type": "falsePositiveJobDataList", //<2> + "type": "falsePositiveDataList", //<2> "jobData": [ { "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", //<3> @@ -11,5 +11,19 @@ "jobUUID": "6cfa2ccf-da13-4dee-b529-0225ed9661bd", "findingId": 15 } + ], + "projectData": [ //<6> + { + "id": "unique-id", //<7> + "comment": "It was verified that there is no SQL-injection vulnerability at this location", + "webScan": { //<8> + "cweId": 89, //<9> + "hostPatterns": [ "127.0.*.1", "api.example.com", "dev.*.example.com"], //<10> + "urlPathPatterns": [ "/rest/products/search*", "/rest/users/profile" ], //<11> + "protocols": [ "HTTPS", "WSS" ], //<12> + "methods": [ "GET", "DELETE" ], //<13> + "ports": [ "8080", "443" ] //<14> + } + } ] } \ No newline at end of file diff --git a/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-howto-define-by-api.adoc b/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-howto-define-by-api.adoc index 5ef50daae2..6e1ed3fe94 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,7 +1,8 @@ // SPDX-License-Identifier: MIT [[section-false-positives-define-by-API]] -Define false positive handling in `JSON` by referencing a former {sechub} job UUID and the -corresponding finding entry (by id) and post it to REST 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. *JSON* @@ -14,16 +15,50 @@ include::false-positives-REST-API-content-example1.json[] <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. [NOTE] ==== -This is a very easy, generic approach - and also future-proof: The only dependency is to the job, +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. +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 later the {sechub} job it cannot destroy our false positive setup in {sechub}. +meta data. If we delete the {sechub} job later it cannot destroy our false positive setup in {sechub}. + +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`. ==== *ID handling* + 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 1d1ec720fa..564861e560 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 @@ -9,33 +9,45 @@ There are two different kind of phases: + ===== Definition phase -When a user has a {sechub} report with some findings - the user can define false positives by -the report UUID and the finding id which represents the false positive. +When a user has a {sechub} report with some findings - the user can define false positives in two different ways. By +the report UUID and the finding id which represents the false positive or by providing additional information like patterns to identify a false positive. The configuration is described at <>. [IMPORTANT] ==== -The user gives only information about which finding in an existing report is seen as a false positive. While a new +When using the first approach by providing report UUID and the finding id, the user only gives information about which finding in an existing report is seen as a false positive. While a new false positive is created, SecHub inspects the report and fetches all necessary meta data internally - depending on the scan type. These meta data will be stored independently from the report. So even if the report will be deleted afterwards, the false-positive handling will still work! + +When using the second approach the user provides additional data that helps identifying a false positive or even a specific group of false positives. +This approach requires more effort from a user to provide the data which identify the wanted false positives. +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! + +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! + *Meta data is only collected in definition phase, but NOT inspected!* ! [NOTE] ==== -Here a simple example for better understanding: +Here a simple example for better understanding. + +On the first approach with finding id and report UUID: - A user has 10 reports but containing always the same finding - The user marks for all of these reports the finding again as false positive - This will result in 10 false positive entries (and it's meta data) inside the database because SecHub does NOT inspect at this time that we have duplicates here! +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. + ==== ====== Overview @@ -51,30 +63,44 @@ The entry does contain a `FalsePositiveProjectConfiguration` object as JSON. ====== Data structure The `FalsePositiveProjectConfiguration` object contains a list of `FalsePositiveEntry` objects. -Every `FalsePositveEntry` object contains +Every `FalsePositveEntry` object contains either - `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. + +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 `FalsePositiveJobDataList` object which contains only `FalsePositiveJobData` inside. Such a list will -be used to add but also to remove false positives. +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. ====== Merging When a user adds or removes false positive definitions, the `FalsePositiveProjectConfiguration` will be updated by -`FalsePositiveJobDataConfigMerger`. +`FalsePositiveDataConfigMerger`. ===== Job execution phase -Here `Sereco` is in charge, which does only inspect the meta data. +Here `Sereco` is in charge, which does inspect each `FalsePositveEntry` and use either the meta data or the project data depending on the entry. ====== Overview plantuml::diagrams/diagram_falsepositives_runtime_overview.puml[format=svg, title="Overview runtime phase"] 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 f2a2138bc3..5bc35dcb86 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,9 +1,11 @@ // 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.FalsePositiveJobDataList.*; 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.*; @@ -40,18 +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.FalsePositiveEntry; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobDataConfigMerger; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobDataList; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobDataListValidation; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobDataService; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveMetaData; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration; -import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveRestController; -import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigService; +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; @@ -59,8 +50,9 @@ import com.mercedesbenz.sechub.sharedkernel.configuration.AbstractSecHubAPISecurityConfiguration; import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseRestDoc; import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserFetchesFalsePositiveConfigurationOfProject; -import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserMarksFalsePositivesForJob; -import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserUnmarksFalsePositives; +import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserMarksFalsePositives; +import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserUnmarksFalsePositiveByJobData; +import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserUnmarksFalsePositiveByProjectData; import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; import com.mercedesbenz.sechub.test.ExampleConstants; import com.mercedesbenz.sechub.test.TestIsNecessaryForDocumentation; @@ -90,13 +82,13 @@ public class FalsePositiveRestControllerRestDocTest implements TestIsNecessaryFo private ScanProjectConfigService configService; @MockBean - private FalsePositiveJobDataService falsePositiveJobDataService; + private FalsePositiveDataService falsePositiveDataService; @MockBean - private FalsePositiveJobDataListValidation falsePositiveJobDataListValidation; + private FalsePositiveDataListValidation falsePositiveDataListValidation; @MockBean - private FalsePositiveJobDataConfigMerger merger; + private FalsePositiveDataConfigMerger merger; @MockBean private UserContextService userContextService; @@ -109,21 +101,37 @@ public void before() { } @Test - @UseCaseRestDoc(useCase = UseCaseUserMarksFalsePositivesForJob.class) - public void restdoc_mark_false_positives_for_job() throws Exception { + @UseCaseRestDoc(useCase = UseCaseUserMarksFalsePositives.class) + public void restdoc_mark_false_positives() throws Exception { /* prepare */ - String apiEndpoint = https(PORT_USED).buildUserAddsFalsePositiveJobDataListForProject(PROJECT_ID.pathElement()); - Class useCase = UseCaseUserMarksFalsePositivesForJob.class; + String apiEndpoint = https(PORT_USED).buildUserAddsFalsePositiveDataListForProject(PROJECT_ID.pathElement()); + Class useCase = UseCaseUserMarksFalsePositives.class; - FalsePositiveJobDataList jobDataList = new FalsePositiveJobDataList(); - jobDataList.setApiVersion("1.0"); - List list = jobDataList.getJobData(); + FalsePositiveDataList dataList = new FalsePositiveDataList(); + dataList.setApiVersion("1.0"); + List list = dataList.getJobData(); FalsePositiveJobData data = new FalsePositiveJobData(); data.setComment("an optional comment why this is a false positive..."); data.setFindingId(42); data.setJobUUID(UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936")); list.add(data); - String content = jobDataList.toJSON(); + + 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/")); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setId("unique-identifier"); + projectData.setComment("an optional comment for this false positive project entry"); + projectData.setWebScan(webScan); + + dataList.getProjectData().add(projectData); + + String content = dataList.toJSON(); /* execute + test @formatter:off */ this.mockMvc.perform( @@ -138,7 +146,7 @@ public void restdoc_mark_false_positives_for_job() throws Exception { with(). useCaseData(useCase). tag(RestDocFactory.extractTag(apiEndpoint)). - requestSchema(OpenApiSchema.FALSE_POSITVES_FOR_JOB.getSchema()). + requestSchema(OpenApiSchema.FALSE_POSITIVES.getSchema()). and(). document( requestHeaders( @@ -146,12 +154,24 @@ public void restdoc_mark_false_positives_for_job() throws Exception { ), requestFields( fieldWithPath(PROPERTY_API_VERSION).description("The api version, currently only 1.0 is supported"), - fieldWithPath(PROPERTY_TYPE).description("The type of the json content. Currently only accepted value is '"+FalsePositiveJobDataList.ACCEPTED_TYPE+"'."), + fieldWithPath(PROPERTY_TYPE).description("The type of the json content. Currently only accepted value is '"+FalsePositiveDataList.ACCEPTED_TYPE+"' but we also still accept the deprecated type '"+FalsePositiveDataList.DEPRECATED_ACCEPTED_TYPE+"'."), fieldWithPath(PROPERTY_JOBDATA).description("Job data list containing false positive setup based on former jobs"), fieldWithPath(PROPERTY_JOBDATA+"[]."+ PROPERTY_JOBUUID).description("SecHub job uuid where finding was"), - fieldWithPath(PROPERTY_JOBDATA+"[]."+ PROPERTY_FINDINGID).description("SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive. *ATTENTION*: at the moment only code scan false positive handling is supported. Infra and web scan findings will lead to a non accepted error!"), - fieldWithPath(PROPERTY_JOBDATA+"[]."+ PROPERTY_COMMENT).optional().description("A comment describing why this is a false positive") + fieldWithPath(PROPERTY_JOBDATA+"[]."+ PROPERTY_FINDINGID).description("SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive."), + fieldWithPath(PROPERTY_JOBDATA+"[]."+ FalsePositiveJobData.PROPERTY_COMMENT).optional().description("A comment describing why this is a false positive"), + + fieldWithPath(PROPERTY_PROJECTDATA).description("Porject data list containing false positive setup for the project"), + fieldWithPath(PROPERTY_PROJECTDATA+"[]."+ PROPERTY_ID).description("Identifier which is used to update or remove the respective false positive entry."), + 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_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( parameterWithName(PROJECT_ID.paramName()).description("The projectId of the project where users adds false positives for") @@ -162,12 +182,12 @@ public void restdoc_mark_false_positives_for_job() throws Exception { } @Test - @UseCaseRestDoc(useCase = UseCaseUserUnmarksFalsePositives.class) + @UseCaseRestDoc(useCase = UseCaseUserUnmarksFalsePositiveByJobData.class) public void restdoc_unmark_false_positives() throws Exception { /* prepare */ String apiEndpoint = https(PORT_USED).buildUserRemovesFalsePositiveEntryFromProject(PROJECT_ID.pathElement(), JOB_UUID.pathElement(), FINDING_ID.pathElement()); - Class useCase = UseCaseUserUnmarksFalsePositives.class; + Class useCase = UseCaseUserUnmarksFalsePositiveByJobData.class; int findingId = 42; UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); @@ -197,6 +217,39 @@ public void restdoc_unmark_false_positives() throws Exception { /* @formatter:on */ } + @Test + @UseCaseRestDoc(useCase = UseCaseUserUnmarksFalsePositiveByProjectData.class) + public void restdoc_unmark_false_positive_project_data() throws Exception { + /* prepare */ + String apiEndpoint = https(PORT_USED).buildUserRemovesFalsePositiveProjectDataEntryFromProject(PROJECT_ID.pathElement(), PROJECT_DATA_ID.pathElement()); + Class useCase = UseCaseUserUnmarksFalsePositiveByProjectData.class; + + String projectDataId = "unique-identifier"; + + /* execute + test @formatter:off */ + this.mockMvc.perform( + delete(apiEndpoint,PROJECT1_ID,projectDataId). + header(AuthenticationHelper.HEADER_NAME, AuthenticationHelper.getHeaderValue()) + ). + andExpect(status().isNoContent()). + /*andDo(print()).*/ + andDo(defineRestService(). + with(). + useCaseData(useCase). + tag(RestDocFactory.extractTag(apiEndpoint)). + and(). + document( + requestHeaders( + + ), + pathParameters( + parameterWithName(PROJECT_ID.paramName()).description("The project id"), + parameterWithName(PROJECT_DATA_ID.paramName()).description("Identifier which is used to remove the respective false positive entry.") + ) + )); + /* @formatter:on */ + } + @Test @UseCaseRestDoc(useCase = UseCaseUserFetchesFalsePositiveConfigurationOfProject.class) public void user_fetches_false_positive_configuration() throws Exception { @@ -241,8 +294,24 @@ public void user_fetches_false_positive_configuration() throws Exception { entry.setMetaData(metaData); + 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/")); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setId("unique-identifier"); + projectData.setComment("an optional comment for this false positive project entry"); + projectData.setWebScan(webScan); + + entry.setProjectData(projectData); + fpList.add(entry); - when(falsePositiveJobDataService.fetchFalsePositivesProjectConfiguration(PROJECT1_ID)).thenReturn(config); + + when(falsePositiveDataService.fetchFalsePositivesProjectConfiguration(PROJECT1_ID)).thenReturn(config); /* execute + test @formatter:off */ String metaDataPath = PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_METADATA; @@ -258,7 +327,7 @@ public void user_fetches_false_positive_configuration() throws Exception { with(). useCaseData(useCase). tag(RestDocFactory.extractTag(apiEndpoint)). - responseSchema(OpenApiSchema.FALSE_POSITVES.getSchema()). + responseSchema(OpenApiSchema.FALSE_POSITIVES.getSchema()). and(). document( requestHeaders( @@ -291,7 +360,19 @@ public void user_fetches_false_positive_configuration() throws Exception { fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_JOBDATA).description("Job data parts, can be used as key to identify false positives"), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_JOBDATA+"."+PROPERTY_JOBUUID).description("SecHub job uuid where finding was"), fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_JOBDATA+"."+PROPERTY_FINDINGID).description("SecHub finding identifier - identifies problem inside the job which shall be markeda as a false positive. *ATTENTION*: at the moment only code scan false positive handling is supported. Infra and web scan findings will lead to a non accepted error!"), - fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_JOBDATA+"."+PROPERTY_COMMENT).optional().description("A comment from author describing why this was marked as a false positive") + fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+FalsePositiveEntry.PROPERTY_JOBDATA+"."+FalsePositiveJobData.PROPERTY_COMMENT).optional().description("A comment from author describing why this was marked as a false positive"), + + fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA).optional().description("Porject data list containing false positive setup for the project."), + fieldWithPath(PROPERTY_FALSE_POSITIVES+"[]."+PROPERTY_PROJECTDATA+"."+ PROPERTY_ID).description("Identifier which is used to update or remove the respective false positive entry."), + 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_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( parameterWithName(PROJECT_ID.paramName()).description("The project id") diff --git a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java index df032df5e6..70ac1df00c 100644 --- a/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java +++ b/sechub-doc/src/test/java/com/mercedesbenz/sechub/restdoc/OpenApiSchema.java @@ -48,9 +48,7 @@ enum OpenApiSchema { RUNNING_JOB_LIST("ListOfRunningJobs"), - FALSE_POSITVES_FOR_JOB("FalsePositivesForJob"), - - FALSE_POSITVES("FalsePositives"), + FALSE_POSITIVES("FalsePositives"), FULL_SCAN_DATA_ZIP("FullScanDataZIP"), diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java index 0a260f0274..9268c35e7b 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/api/AsUser.java @@ -1,8 +1,12 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.integrationtest.api; -import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.*; -import static org.junit.Assert.*; +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.assertProject; +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.ensureExecutorConfigUUID; +import static com.mercedesbenz.sechub.integrationtest.api.TestAPI.waitForJobDoneAndEvenWaitWhileJobIsFailing; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; @@ -34,6 +38,7 @@ import com.mercedesbenz.sechub.commons.mapping.MappingData; import com.mercedesbenz.sechub.commons.model.JSONConverter; import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; import com.mercedesbenz.sechub.integrationtest.JSONTestSupport; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestContext; import com.mercedesbenz.sechub.integrationtest.internal.IntegrationTestFileSupport; @@ -1064,6 +1069,8 @@ private class JobData { } private List jobData = new ArrayList<>(); + private List projectDataList = new ArrayList<>(); + private List projectDataIds = new ArrayList<>(); private WithSecHubClient withSechubClient; private IntegrationTestJSONLocation location; @@ -1120,7 +1127,7 @@ private void markFalsePositiveBySecHubClient() { private void markAsFalsePositiveByREST() { String json = buildJSON(); - String url = getUrlBuilder().buildUserAddsFalsePositiveJobDataListForProject(project.getProjectId()); + String url = getUrlBuilder().buildUserAddsFalsePositiveDataListForProject(project.getProjectId()); getRestHelper().putJSON(url, json); } @@ -1132,6 +1139,18 @@ public void unmarkFalsePositive() { } } + public void unmarkFalsePositiveProjectData() { + unmarkFalsePositiveProjectDataByREST(); + } + + private void unmarkFalsePositiveProjectDataByREST() { + for (String projectDataId : projectDataIds) { + String url = getUrlBuilder().buildUserRemovesFalsePositiveProjectDataEntryFromProject(project.getProjectId(), projectDataId); + getRestHelper().delete(url); + } + + } + private void unmarkFalsePositiveBySecHubClient() { String json = buildJSON(); IntegrationTestFileSupport testfileSupport = IntegrationTestFileSupport.getTestfileSupport(); @@ -1152,7 +1171,10 @@ private void unmarkFalsePositiveByREST() { } private String buildJSON() { - String content = "{\"apiVersion\":\"1.0\",\"type\":\"falsePositiveJobDataList\",\"jobData\":["; + String content = "{\"apiVersion\":\"1.0\",\"type\":\"falsePositiveDataList\","; + if (!jobData.isEmpty()) { + content += "\"jobData\":["; + } Iterator it = jobData.iterator(); while (it.hasNext()) { JobData data = it.next(); @@ -1178,7 +1200,24 @@ private String buildJSON() { content += ","; } } - content += "]}"; + if (projectDataList.isEmpty()) { + content += "]}"; + } else if (!jobData.isEmpty()) { + content += "],"; + } + if (!projectDataList.isEmpty()) { + content += "\"projectData\":["; + } + for (FalsePositiveProjectData projectData : projectDataList) { + content += JSONConverter.get().toJSON(projectData); + + if (it.hasNext()) { + content += ","; + } + } + if (!content.endsWith("]}")) { + content += "]}"; + } return content; } @@ -1195,6 +1234,17 @@ public ProjectFalsePositivesDefinition add(int findingId, UUID jobUUID, String c jobData.add(data); return this; } + + public ProjectFalsePositivesDefinition add(FalsePositiveProjectData projectData) { + projectDataList.add(projectData); + return this; + } + + public ProjectFalsePositivesDefinition add(String projectDataId) { + projectDataIds.add(projectDataId); + return this; + } + } public void changeProjectAccessLevel(TestProject project, ProjectAccessLevel accessLevel) { diff --git a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/scenario21/Scenario21.java b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/scenario21/Scenario21.java index 2d2e09628c..e7970e3f90 100644 --- a/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/scenario21/Scenario21.java +++ b/sechub-integrationtest/src/main/java/com/mercedesbenz/sechub/integrationtest/scenario21/Scenario21.java @@ -11,7 +11,7 @@ import com.mercedesbenz.sechub.integrationtest.internal.PDSTestScenario; /** - *

Scenario 20

+ *

Scenario 21

* *

Short description

PDS solutions mock scenario for multiple PDS * solutions 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 c15c848939..2f3039c987 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,6 +8,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.Iterator; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -17,23 +18,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.mercedesbenz.sechub.commons.model.JSONConverter; -import com.mercedesbenz.sechub.commons.model.ScanType; -import com.mercedesbenz.sechub.commons.model.SecHubCodeScanConfiguration; -import com.mercedesbenz.sechub.commons.model.SecHubConfigurationModel; -import com.mercedesbenz.sechub.commons.model.SecHubDataConfiguration; -import com.mercedesbenz.sechub.commons.model.SecHubFileSystemConfiguration; -import com.mercedesbenz.sechub.commons.model.SecHubInfrastructureScanConfiguration; -import com.mercedesbenz.sechub.commons.model.SecHubLicenseScanConfiguration; -import com.mercedesbenz.sechub.commons.model.SecHubMessage; -import com.mercedesbenz.sechub.commons.model.SecHubMessageType; -import com.mercedesbenz.sechub.commons.model.SecHubReportModel; -import com.mercedesbenz.sechub.commons.model.SecHubSecretScanConfiguration; -import com.mercedesbenz.sechub.commons.model.SecHubSourceDataConfiguration; -import com.mercedesbenz.sechub.commons.model.SecHubStatus; -import com.mercedesbenz.sechub.commons.model.SecHubWebScanConfiguration; -import com.mercedesbenz.sechub.commons.model.Severity; -import com.mercedesbenz.sechub.commons.model.TrafficLight; +import com.mercedesbenz.sechub.commons.model.*; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; import com.mercedesbenz.sechub.integrationtest.api.IntegrationTestSetup; import com.mercedesbenz.sechub.integrationtest.api.TestAPI; import com.mercedesbenz.sechub.integrationtest.api.TestProject; @@ -111,6 +98,68 @@ public void pds_solution_findsecuritybugs_mocked_report_in_json_and_html_availab executePDSSolutionJobAndStoreReports(ScanType.CODE_SCAN, PROJECT_10, "findsecuritybugs"); } + @Test + public void pds_solution_zap_mocked_report_REST_API_direct_mark_and_unmark_false_positive_projectData_webscan() throws Exception { + /* @formatter:off */ + + /* prepare */ + // execute scan to see a HIGH finding inside the webscan report + SecHubReportModel report1 = executePDSSolutionJobAndStoreReports(ScanType.WEB_SCAN, PROJECT_4, "zap"); + assertReportUnordered(report1.toJSON()) + .hasTrafficLight(TrafficLight.RED) + .finding() + .severity(Severity.HIGH) + .scanType(ScanType.WEB_SCAN) + .name("SQL Injection - SQLite") + .description("RDBMS [SQLite] likely, given error message fragment [SQLITE_ERROR] in HTML results") + .isContained(); + + WebscanFalsePositiveProjectData webscan = new WebscanFalsePositiveProjectData(); + // we set only mandatory parameters + webscan.setCweId(89); + webscan.setHostPatterns(List.of("localhost")); + webscan.setUrlPathPatterns(List.of("/rest/products/search*")); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + String projectDataId = "6a7fe94a-564f-11ef-87de-3f13a69f3e5d"; + projectData.setId(projectDataId); + projectData.setWebScan(webscan); + + /* execute 1 */ + // mark a false positive via projectData that matches the previously detected HIGH finding + as(USER_1).startFalsePositiveDefinition(PROJECT_4).add(projectData).markAsFalsePositive(); + SecHubReportModel report2 = executePDSSolutionJobAndStoreReports(ScanType.WEB_SCAN, PROJECT_4, "zap"); + + /* test 1 */ + // since the HIGH finding was marked as false positive we expected it to not be inside the report after a second scan + assertReportUnordered(report2.toJSON()) + .hasTrafficLight(TrafficLight.YELLOW) + .finding() + .severity(Severity.HIGH) + .scanType(ScanType.WEB_SCAN) + .name("SQL Injection - SQLite") + .description("RDBMS [SQLite] likely, given error message fragment [SQLITE_ERROR] in HTML results") + .isNotContained(); + + /* execute 2 */ + // unmark the projectData via the ID and perform another scan + as(USER_1).startFalsePositiveDefinition(PROJECT_4).add(projectDataId).unmarkFalsePositiveProjectData(); + SecHubReportModel report3 = executePDSSolutionJobAndStoreReports(ScanType.WEB_SCAN, PROJECT_4, "zap"); + + /* test 2 */ + // since the projectData must have been deleted, the HIGH finding must be back inside the report + assertReportUnordered(report3.toJSON()) + .hasTrafficLight(TrafficLight.RED) + .finding() + .severity(Severity.HIGH) + .scanType(ScanType.WEB_SCAN) + .name("SQL Injection - SQLite") + .description("RDBMS [SQLite] likely, given error message fragment [SQLITE_ERROR] in HTML results") + .isContained(); + + /* @formatter:on */ + } + private SecHubReportModel executePDSSolutionJobAndStoreReports(ScanType scanType, TestProject project, String solutionName) { SecHubConfigurationModel model = createTestModelFor(scanType, project); UUID jobUUID = as(USER_1).createJobAndReturnJobUUID(project, model); diff --git a/sechub-openapi-java/src/main/resources/openapi.yaml b/sechub-openapi-java/src/main/resources/openapi.yaml index 79c63fc5d8..97d53cceb5 100644 --- a/sechub-openapi-java/src/main/resources/openapi.yaml +++ b/sechub-openapi-java/src/main/resources/openapi.yaml @@ -1244,7 +1244,81 @@ components: jobUUID: jobUUID findingId: 1 comment: comment - + + WebscanFalsePositiveProjectData: + title: WebscanFalsePositiveProjectData + description: Project data parts, that identify false positives of webscans + type: object + properties: + cweId: + required: true + description: CWE ID for a category of findings to identify the type of false positive finding + type: integer + format: int32 + hostPatterns: + 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 + 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"] + + + FalsePositiveProjectData: + title: FalsePositiveProjectData + description: Project data parts, can be used to specify patterns that indentify false positives + type: object + properties: + id: + required: true + description: ID to identify this entry, can be used to overwrite or delete the entry for this project + type: string + comment: + description: A comment from author describing why this was marked as a false positive + type: string + webScan: + $ref: '#/components/schemas/WebscanFalsePositiveProjectData' + example: + id: unique-id + 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"] + FalsePositiveEntry: title: FalsePositiveEntry type: object @@ -1256,6 +1330,8 @@ components: type: string metaData: $ref: '#/components/schemas/FalsePositiveMetaData' + projectData: + $ref: '#/components/schemas/FalsePositiveProjectData' created: description: Creation timestamp type: string @@ -1281,6 +1357,16 @@ components: cveId: cveId name: name scanType: scanType + projectData: + id: unique-id + 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"] created: created FalsePositiveProjectConfiguration: @@ -1338,9 +1424,21 @@ components: comment: comment created: created author: author + - projectData: + id: unique-id + 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"] + created: created + author: author - FalsePositivesForJob: - title: FalsePositivesForJob + FalsePositives: + title: FalsePositives type: object properties: apiVersion: @@ -1348,13 +1446,18 @@ components: type: string type: description: The type of the json content. Currently only accepted value - is 'falsePositiveJobDataList'. + is 'falsePositiveDataList' and the deprecated 'falsePositiveJobDataList'. type: string jobData: description: Job data list containing false positive setup based on former jobs type: array items: $ref: '#/components/schemas/FalsePositiveJobData' + projectData: + description: Project data list containing false positive setup + type: array + items: + $ref: '#/components/schemas/FalsePositiveProjectData' ############# @@ -2582,8 +2685,8 @@ paths: - SecHub Execution x-content-type: application/json put: - description: User marks false positives for finished sechub job - operationId: userMarkFalsePositivesForJob + description: User marks false positives + operationId: userMarkFalsePositives parameters: - description: The projectId of the project where users adds false positives for @@ -2598,7 +2701,7 @@ paths: content: application/json;charset=UTF-8: schema: - $ref: '#/components/schemas/FalsePositivesForJob' + $ref: '#/components/schemas/FalsePositives' responses: "200": description: "Ok" @@ -2648,6 +2751,34 @@ paths: tags: - SecHub Execution + /api/project/{projectId}/false-positive/{id}: + delete: + summary: User unmarks existing false positive definitons + description: User unmarks existing false positive definitons + operationId: userUnmarkFalsePositives + parameters: + - name: projectId + description: The project id + in: path + required: true + schema: + type: string + - name: id + description: The id of the projectData entry the shall be deleted from FalsePositiveProjectConfiguration + in: path + required: true + schema: + type: string + responses: + "200": + description: "Ok" + "404": + description: "Not found" + "406": + description: "Not acceptable" + tags: + - SecHub Execution + /api/project/{projectId}/job/{jobUUID}/binaries: post: summary: User uploads binaries diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/AbstractSourceCodeBasedFalsePositiveStrategy.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/AbstractSourceCodeBasedFalsePositiveStrategy.java index 9b2728a4fb..c2e90ee644 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/AbstractSourceCodeBasedFalsePositiveStrategy.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/AbstractSourceCodeBasedFalsePositiveStrategy.java @@ -14,7 +14,7 @@ import com.mercedesbenz.sechub.sereco.metadata.SerecoCodeCallStackElement; import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; -public abstract class AbstractSourceCodeBasedFalsePositiveStrategy implements SerecoFalsePositiveStrategy { +public abstract class AbstractSourceCodeBasedFalsePositiveStrategy implements SerecoJobDataFalsePositiveStrategy { private static final Logger LOG = LoggerFactory.getLogger(AbstractSourceCodeBasedFalsePositiveStrategy.class); @@ -24,7 +24,7 @@ public abstract class AbstractSourceCodeBasedFalsePositiveStrategy implements Se SerecoSourceRelevantPartResolver relevantPartResolver; @Autowired - SerecoFalsePositiveSupport falsePositiveSupport; + SerecoJobDataFalsePositiveSupport falsePositiveSupport; public boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveMetaData metaData) { return isFalsePositive(getScanType(), vulnerability, metaData); diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanFalsePositiveStrategy.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanJobDataFalsePositiveStrategy.java similarity index 82% rename from sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanFalsePositiveStrategy.java rename to sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanJobDataFalsePositiveStrategy.java index 86e6fea3e9..be0ba3d39e 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanFalsePositiveStrategy.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanJobDataFalsePositiveStrategy.java @@ -13,7 +13,7 @@ * */ @Component -public class CodeScanFalsePositiveStrategy extends AbstractSourceCodeBasedFalsePositiveStrategy { +public class CodeScanJobDataFalsePositiveStrategy extends AbstractSourceCodeBasedFalsePositiveStrategy { @Override protected ScanType getScanType() { diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanFalsePositiveStrategy.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanJobDataFalsePositiveStrategy.java similarity index 81% rename from sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanFalsePositiveStrategy.java rename to sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanJobDataFalsePositiveStrategy.java index c8cfc2b152..588a31cea3 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanFalsePositiveStrategy.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanJobDataFalsePositiveStrategy.java @@ -13,7 +13,7 @@ * */ @Component -public class SecretScanFalsePositiveStrategy extends AbstractSourceCodeBasedFalsePositiveStrategy { +public class SecretScanJobDataFalsePositiveStrategy extends AbstractSourceCodeBasedFalsePositiveStrategy { @Override protected ScanType getScanType() { diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarker.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarker.java index 0da5ab5c6f..166bc896d2 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarker.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveMarker.java @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.product.sereco; -import static com.mercedesbenz.sechub.sharedkernel.util.Assert.*; +import static com.mercedesbenz.sechub.sharedkernel.util.Assert.notEmpty; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.mercedesbenz.sechub.commons.model.ScanType; @@ -15,6 +16,7 @@ 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.ScanProjectConfig; import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigID; import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigService; @@ -33,11 +35,23 @@ public class SerecoFalsePositiveMarker { private static final Logger LOG = LoggerFactory.getLogger(SerecoFalsePositiveMarker.class); - @Autowired - SerecoFalsePositiveFinder falsePositiveFinder; - - @Autowired - ScanProjectConfigService scanProjectConfigService; + private final SerecoJobDataFalsePositiveFinder jobDataFalsePositiveFinder; + private final SerecoProjectDataFalsePositiveFinder projectDataFalsePositiveFinder; + private final ScanProjectConfigService scanProjectConfigService; + private final SerecoProjectDataPatternMapFactory projectDataPatternMapFactory; + + /* @formatter:off */ + public SerecoFalsePositiveMarker(SerecoJobDataFalsePositiveFinder jobDataFalsePositiveFinder, + SerecoProjectDataFalsePositiveFinder projectDataFalsePositiveFinder, + ScanProjectConfigService scanProjectConfigService, + SerecoProjectDataPatternMapFactory projectDataPatternMapFactory) { + + this.jobDataFalsePositiveFinder = jobDataFalsePositiveFinder; + this.projectDataFalsePositiveFinder = projectDataFalsePositiveFinder; + this.scanProjectConfigService = scanProjectConfigService; + this.projectDataPatternMapFactory = projectDataPatternMapFactory; + /* @formatter:on */ + } public void markFalsePositives(String projectId, List all) { notEmpty(projectId, "project id may not be null or empty!"); @@ -56,25 +70,44 @@ public void markFalsePositives(String projectId, List all) FalsePositiveProjectConfiguration falsePositiveConfig = FalsePositiveProjectConfiguration.fromJSONString(data); List falsePositives = falsePositiveConfig.getFalsePositives(); + Map projectDataPatternMap = projectDataPatternMapFactory.create(falsePositives); + for (SerecoVulnerability vulnerability : all) { - handleVulnereability(falsePositives, vulnerability); + handleVulnerability(falsePositives, vulnerability, projectDataPatternMap); } } - private void handleVulnereability(List falsePositives, SerecoVulnerability vulnerability) { + private void handleVulnerability(List falsePositives, SerecoVulnerability vulnerability, Map projectDataPatternMap) { for (FalsePositiveEntry entry : falsePositives) { - if (isFalsePositive(vulnerability, entry)) { - vulnerability.setFalsePositive(true); - FalsePositiveJobData jobData = entry.getJobData(); - vulnerability.setFalsePositiveReason("finding:" + jobData.getFindingId() + " in job:" + jobData.getJobUUID() + " marked as false positive"); - return; + if (entry.getMetaData() != null) { + + if (isJobDataFalsePositive(vulnerability, entry)) { + vulnerability.setFalsePositive(true); + FalsePositiveJobData jobData = entry.getJobData(); + vulnerability + .setFalsePositiveReason("finding: %s in job: %s marked as false positive".formatted(jobData.getFindingId(), jobData.getJobUUID())); + return; + } + } else if (entry.getProjectData() != null) { + + if (isProjectDataFalsePositive(vulnerability, entry, projectDataPatternMap)) { + vulnerability.setFalsePositive(true); + FalsePositiveProjectData projectData = entry.getProjectData(); + vulnerability.setFalsePositiveReason("vulnerability matches false positive project data entry with id: %s".formatted(projectData.getId())); + return; + } } } } - private boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveEntry entry) { + private boolean isProjectDataFalsePositive(SerecoVulnerability vulnerability, FalsePositiveEntry entry, Map projectDataPatternMap) { + FalsePositiveProjectData projectData = entry.getProjectData(); + return projectDataFalsePositiveFinder.isFound(vulnerability, projectData, projectDataPatternMap); + } + + private boolean isJobDataFalsePositive(SerecoVulnerability vulnerability, FalsePositiveEntry entry) { FalsePositiveMetaData metaData = entry.getMetaData(); ScanType scanType = metaData.getScanType(); if (scanType != vulnerability.getScanType()) { @@ -89,7 +122,7 @@ private boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositive case CODE_SCAN: case SECRET_SCAN: case WEB_SCAN: - return falsePositiveFinder.isFound(vulnerability, metaData); + return jobDataFalsePositiveFinder.isFound(vulnerability, metaData); default: LOG.error("Cannot handle scan type {} - not implemented!", scanType); return false; diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveFinder.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveFinder.java similarity index 77% rename from sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveFinder.java rename to sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveFinder.java index 287a105bfe..6ebf6f7985 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveFinder.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveFinder.java @@ -16,16 +16,16 @@ * */ @Component -public class SerecoFalsePositiveFinder { +public class SerecoJobDataFalsePositiveFinder { @Autowired - CodeScanFalsePositiveStrategy codeScanStrategy; + CodeScanJobDataFalsePositiveStrategy jobDataCodeScanStrategy; @Autowired - SecretScanFalsePositiveStrategy secretScanStrategy; + SecretScanJobDataFalsePositiveStrategy jobDataSecretScanStrategy; @Autowired - WebScanFalsePositiveStrategy webScanStrategy; + WebScanJobDataFalsePositiveStrategy jobDataSebScanStrategy; public boolean isFound(SerecoVulnerability vulnerability, FalsePositiveMetaData metaData) { if (!isVulnerabilityValid(vulnerability)) { @@ -37,11 +37,11 @@ public boolean isFound(SerecoVulnerability vulnerability, FalsePositiveMetaData ScanType scanType = vulnerability.getScanType(); switch (scanType) { case CODE_SCAN: - return codeScanStrategy.isFalsePositive(vulnerability, metaData); + return jobDataCodeScanStrategy.isFalsePositive(vulnerability, metaData); case SECRET_SCAN: - return secretScanStrategy.isFalsePositive(vulnerability, metaData); + return jobDataSecretScanStrategy.isFalsePositive(vulnerability, metaData); case WEB_SCAN: - return webScanStrategy.isFalsePositive(vulnerability, metaData); + return jobDataSebScanStrategy.isFalsePositive(vulnerability, metaData); default: return false; } diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveStrategy.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveStrategy.java similarity index 90% rename from sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveStrategy.java rename to sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveStrategy.java index 47884fb35e..c82f81b1d1 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveStrategy.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveStrategy.java @@ -11,7 +11,7 @@ * @author Albert Tregnaghi * */ -public interface SerecoFalsePositiveStrategy { +public interface SerecoJobDataFalsePositiveStrategy { public boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveMetaData metaData); } diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveSupport.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveSupport.java similarity index 91% rename from sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveSupport.java rename to sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveSupport.java index 9ac76bce33..63f31a91bd 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveSupport.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveSupport.java @@ -13,9 +13,9 @@ import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; @Component -public class SerecoFalsePositiveSupport { +public class SerecoJobDataFalsePositiveSupport { - private static final Logger LOG = LoggerFactory.getLogger(SerecoFalsePositiveSupport.class); + private static final Logger LOG = LoggerFactory.getLogger(SerecoJobDataFalsePositiveSupport.class); public boolean areBothHavingExpectedScanType(ScanType type, FalsePositiveMetaData metaData, SerecoVulnerability vulnerability) { notNull(vulnerability, " vulnerability may not be null"); @@ -61,7 +61,7 @@ public boolean areBothHavingSameCweIdOrBothNoCweId(FalsePositiveMetaData metaDat } } catch (NumberFormatException e) { - LOG.error("Code scan sereco vulnerability type:{} found CWE:{} but not expected integer format!", vulnerability.getType(), serecoCWE); + LOG.error("Sereco vulnerability type:{} found CWE:{} but not expected integer format!", vulnerability.getType(), serecoCWE); return false; } diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinder.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinder.java new file mode 100644 index 0000000000..eb79d1e71d --- /dev/null +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinder.java @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import java.util.Map; +import java.util.regex.Pattern; + +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; + +@Component +public class SerecoProjectDataFalsePositiveFinder { + + private final WebScanProjectDataFalsePositiveStrategy webScanProjectDataStrategy; + + public SerecoProjectDataFalsePositiveFinder(WebScanProjectDataFalsePositiveStrategy webScanProjectDataStrategy) { + this.webScanProjectDataStrategy = webScanProjectDataStrategy; + } + + public boolean isFound(SerecoVulnerability vulnerability, FalsePositiveProjectData projectData, Map projectDataPatternMap) { + if (!isVulnerabilityValid(vulnerability)) { + return false; + } + if (projectData == null) { + return false; + } + if (projectDataPatternMap == null || projectDataPatternMap.isEmpty()) { + return false; + } + + ScanType scanType = vulnerability.getScanType(); + if (scanType == ScanType.WEB_SCAN) { + return webScanProjectDataStrategy.isFalsePositive(vulnerability, projectData, projectDataPatternMap); + } + return false; + } + + private boolean isVulnerabilityValid(SerecoVulnerability vulnerability) { + if (vulnerability == null) { + return false; + } + if (vulnerability.getScanType() == null) { + return false; + } + if (vulnerability.getType() == null) { + return false; + } + return true; + } + +} diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveStrategy.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveStrategy.java new file mode 100644 index 0000000000..a3db5f26b1 --- /dev/null +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveStrategy.java @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import java.util.Map; +import java.util.regex.Pattern; + +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; + +public interface SerecoProjectDataFalsePositiveStrategy { + + /** + * Checks if given vulnerability is identified as false positive by given + * project data and the associated patterns from the pattern map. + * + * @param vulnerability + * @param projectData + * @param projectDataPatternMap + * @return true when identified as false positive + */ + boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveProjectData projectData, Map projectDataPatternMap); +} 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 new file mode 100644 index 0000000000..71a0d8711f --- /dev/null +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactory.java @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import static com.mercedesbenz.sechub.sharedkernel.util.Assert.notNull; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.springframework.stereotype.Component; + +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 { + + /** + * Takes all the projectData entries where wildcards are allowed and create + * 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. + */ + 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())); + } + } + 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"); + // remove unnecessary empty quotes to simplify the regex + pattern = pattern.replace("\\Q\\E", ""); + // make sure the patterns matches the string from start to finish and not just a + // substring in between + pattern = "^" + pattern + "$"; + Pattern compiledPattern = Pattern.compile(pattern); + return compiledPattern; + } + +} 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 new file mode 100644 index 0000000000..43a9c96662 --- /dev/null +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataWebScanFalsePositiveSupport.java @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import static com.mercedesbenz.sechub.sharedkernel.util.Assert.notNull; + +import java.util.List; +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; +import com.mercedesbenz.sechub.sereco.metadata.SerecoClassification; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; + +@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(); + + SerecoClassification serecoClassification = vulnerability.getClassification(); + String serecoCWE = serecoClassification.getCwe(); + if (serecoCWE == null || serecoCWE.isEmpty()) { + if (cweIdOrNull == null) { + /* + * when not set in meta data and also not in vulnerability, than we assume it is + * the same + */ + return true; + } + return false; + } + if (cweIdOrNull == null) { + return false; + } + try { + int serecoCWEint = Integer.parseInt(serecoCWE); + if (cweIdOrNull.intValue() != serecoCWEint) { + /* not same type of common vulnerability enumeration - so skip */ + return false; + } + + } catch (NumberFormatException e) { + LOG.error("Sereco vulnerability type:{} found CWE:{} but not expected integer format!", vulnerability.getType(), serecoCWE); + return false; + + } + return true; + } + + /** + * Iterates the given list of hostPatterns and uses each string value as key to + * get the corresponding compiled pattern from the given map. Then tries to + * match the given host against each pattern of the projectDataPatternMap. + * + * @param host + * @param hostPatterns + * @param projectDataPatternMap + * @return true if the given host matches any of the given patterns + */ + public boolean isMatchingHostPattern(String host, List hostPatterns, Map projectDataPatternMap) { + notNull(host, " host may not be null"); + notNull(hostPatterns, " hostPatterns may not be null"); + notNull(projectDataPatternMap, " projectDataPatternMap may not be null"); + + return isMatchingAnyPattern(host, hostPatterns, projectDataPatternMap); + } + + /** + * 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); + } + + /** + * Checks if the given method is inside the list of methods. + * + * @param method + * @param methods + * @return true if the given method is inside methods or methods is + * empty or null + */ + public boolean isMatchingMethodOrIgnoreIfNotSet(String method, List methods) { + notNull(method, " method may not be null"); + 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; + } + for (String projectDataPort : list) { + String projectDataPortTrimmed = projectDataPort.trim(); + if (projectDataPortTrimmed.equalsIgnoreCase(trimmedString)) { + return true; + } + } + return false; + } +} diff --git a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanFalsePositiveStrategy.java b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanJobDataFalsePositiveStrategy.java similarity index 94% rename from sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanFalsePositiveStrategy.java rename to sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanJobDataFalsePositiveStrategy.java index 0791523a6a..3dea018a4b 100644 --- a/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanFalsePositiveStrategy.java +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanJobDataFalsePositiveStrategy.java @@ -16,12 +16,12 @@ import com.mercedesbenz.sechub.sereco.metadata.SerecoWeb; @Component -public class WebScanFalsePositiveStrategy implements SerecoFalsePositiveStrategy { +public class WebScanJobDataFalsePositiveStrategy implements SerecoJobDataFalsePositiveStrategy { @Autowired - SerecoFalsePositiveSupport falsePositiveSupport; + SerecoJobDataFalsePositiveSupport falsePositiveSupport; - private static final Logger LOG = LoggerFactory.getLogger(WebScanFalsePositiveStrategy.class); + private static final Logger LOG = LoggerFactory.getLogger(WebScanJobDataFalsePositiveStrategy.class); /** * Checks if given vulnerability is identified as false positive by given meta 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 new file mode 100644 index 0000000000..a517633064 --- /dev/null +++ b/sechub-scan-product-sereco/src/main/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategy.java @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +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; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; +import com.mercedesbenz.sechub.sereco.metadata.SerecoWeb; + +@Component +public class WebScanProjectDataFalsePositiveStrategy implements SerecoProjectDataFalsePositiveStrategy { + + private static final Logger LOG = LoggerFactory.getLogger(WebScanProjectDataFalsePositiveStrategy.class); + + private final SerecoProjectDataWebScanFalsePositiveSupport webscanFalsePositiveProjectDataSupport; + + public WebScanProjectDataFalsePositiveStrategy(SerecoProjectDataWebScanFalsePositiveSupport webscanFalsePositiveProjectDataSupport) { + this.webscanFalsePositiveProjectDataSupport = webscanFalsePositiveProjectDataSupport; + } + + @Override + public boolean isFalsePositive(SerecoVulnerability vulnerability, FalsePositiveProjectData projectData, Map projectDataPatternMap) { + notNull(vulnerability, " vulnerability may not be null"); + notNull(projectData, " projectData may not be null"); + notNull(projectDataPatternMap, " projectDataPatternMap may not be null"); + + // We use a fast exit approach in this method, since all conditions must be + // satisfied, we exit as soon as possible. + + if (projectDataPatternMap.isEmpty()) { + return false; + } + + if (vulnerability.getScanType() != ScanType.WEB_SCAN) { + return false; + } + + WebscanFalsePositiveProjectData webScanData = projectData.getWebScan(); + if (webScanData == null) { + return false; + } + + SerecoWeb vulnerabilityWeb = vulnerability.getWeb(); + if (vulnerabilityWeb == null) { + LOG.error("Cannot check web vulnerability for false positives when vulnerability data has no web parts!"); + return false; + } + + /* ---------------------------------------------------- */ + /* -------------------CWE ID--------------------------- */ + /* ---------------------------------------------------- */ + if (!webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability)) { + 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); + 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)) { + return false; + } + + /* ---------------------------------------------------- */ + /* -------------------METHODS-------------------------- */ + /* ---------------------------------------------------- */ + String method = vulnerabilityWeb.getRequest().getMethod(); + if (!webscanFalsePositiveProjectDataSupport.isMatchingMethodOrIgnoreIfNotSet(method, webScanData.getMethods())) { + 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/CodeScanFalsePositiveStrategyTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanJobDataFalsePositiveStrategyTest.java similarity index 79% rename from sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanFalsePositiveStrategyTest.java rename to sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanJobDataFalsePositiveStrategyTest.java index b15ed81b2c..b51c6f4b86 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanFalsePositiveStrategyTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/CodeScanJobDataFalsePositiveStrategyTest.java @@ -14,21 +14,21 @@ import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration; import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; -public class CodeScanFalsePositiveStrategyTest { +public class CodeScanJobDataFalsePositiveStrategyTest { - private CodeScanFalsePositiveStrategy strategyToTest; + private CodeScanJobDataFalsePositiveStrategy strategyToTest; private SerecoSourceRelevantPartResolver relevantPartResolver; - private SerecoFalsePositiveSupport serecoFalsePositiveSupport; + private SerecoJobDataFalsePositiveSupport serecoJobDataFalsePositiveSupport; @Before public void before() throws Exception { - strategyToTest = new CodeScanFalsePositiveStrategy(); + strategyToTest = new CodeScanJobDataFalsePositiveStrategy(); relevantPartResolver = mock(SerecoSourceRelevantPartResolver.class); - serecoFalsePositiveSupport = mock(SerecoFalsePositiveSupport.class); + serecoJobDataFalsePositiveSupport = mock(SerecoJobDataFalsePositiveSupport.class); strategyToTest.relevantPartResolver = relevantPartResolver; - strategyToTest.falsePositiveSupport = serecoFalsePositiveSupport; + strategyToTest.falsePositiveSupport = serecoJobDataFalsePositiveSupport; } @@ -56,8 +56,8 @@ public void vulnerability_is_found_when_locations_and_relevant_parts_are_same() FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample3(); when(relevantPartResolver.toRelevantPart(any())).thenReturn(""); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); /* execute + test */ assertTrue(strategyToTest.isFalsePositive(vulnerability, metaData)); @@ -86,8 +86,8 @@ public void vulnerability_is_NOT_found_when_locations_and_relevant_parts_are_sam FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample3(); when(relevantPartResolver.toRelevantPart(any())).thenReturn(""); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(false); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(false); /* execute + test */ assertFalse(strategyToTest.isFalsePositive(vulnerability, metaData)); @@ -117,8 +117,8 @@ public void vulnerability_is_NOT_found_when_start_location_differs_and_relevant_ FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample3(); when(relevantPartResolver.toRelevantPart(any())).thenReturn(""); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); /* execute + test */ assertFalse(strategyToTest.isFalsePositive(vulnerability, metaData)); @@ -147,8 +147,8 @@ public void vulnerability_having_no_relevant_part_will_use_relevant_part_resolve when(relevantPartResolver.toRelevantPart("source1")).thenReturn("relevant1"); when(relevantPartResolver.toRelevantPart("source2")).thenReturn("relevant2"); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.CODE_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(vulnerability, metaData); diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanFalsePositiveStrategyTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanJobDataFalsePositiveStrategyTest.java similarity index 79% rename from sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanFalsePositiveStrategyTest.java rename to sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanJobDataFalsePositiveStrategyTest.java index d0c999f776..258115ea8b 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanFalsePositiveStrategyTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SecretScanJobDataFalsePositiveStrategyTest.java @@ -14,21 +14,21 @@ import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration; import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; -public class SecretScanFalsePositiveStrategyTest { +public class SecretScanJobDataFalsePositiveStrategyTest { - private SecretScanFalsePositiveStrategy strategyToTest; + private SecretScanJobDataFalsePositiveStrategy strategyToTest; private SerecoSourceRelevantPartResolver relevantPartResolver; - private SerecoFalsePositiveSupport serecoFalsePositiveSupport; + private SerecoJobDataFalsePositiveSupport serecoJobDataFalsePositiveSupport; @Before public void before() throws Exception { - strategyToTest = new SecretScanFalsePositiveStrategy(); + strategyToTest = new SecretScanJobDataFalsePositiveStrategy(); relevantPartResolver = mock(SerecoSourceRelevantPartResolver.class); - serecoFalsePositiveSupport = mock(SerecoFalsePositiveSupport.class); + serecoJobDataFalsePositiveSupport = mock(SerecoJobDataFalsePositiveSupport.class); strategyToTest.relevantPartResolver = relevantPartResolver; - strategyToTest.falsePositiveSupport = serecoFalsePositiveSupport; + strategyToTest.falsePositiveSupport = serecoJobDataFalsePositiveSupport; } @Test @@ -54,8 +54,8 @@ public void vulnerability_is_found_when_locations_and_relevant_parts_are_same() FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample5(); when(relevantPartResolver.toRelevantPart(any())).thenReturn(""); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); /* execute + test */ assertTrue(strategyToTest.isFalsePositive(vulnerability, metaData)); @@ -84,8 +84,8 @@ public void vulnerability_is_NOT_found_when_locations_and_relevant_parts_are_sam FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample5(); when(relevantPartResolver.toRelevantPart(any())).thenReturn(""); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(false); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(false); /* execute + test */ assertFalse(strategyToTest.isFalsePositive(vulnerability, metaData)); @@ -115,8 +115,8 @@ public void vulnerability_is_NOT_found_when_start_location_differs_and_relevant_ FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample5(); when(relevantPartResolver.toRelevantPart(any())).thenReturn(""); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); /* execute + test */ assertFalse(strategyToTest.isFalsePositive(vulnerability, metaData)); @@ -145,8 +145,8 @@ public void vulnerability_having_no_relevant_part_will_use_relevant_part_resolve when(relevantPartResolver.toRelevantPart("source1")).thenReturn("relevant1"); when(relevantPartResolver.toRelevantPart("source2")).thenReturn("relevant2"); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.SECRET_SCAN, metaData, vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(metaData, vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(vulnerability, metaData); 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 5b370483e5..25ddd7632a 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 @@ -1,49 +1,67 @@ // SPDX-License-Identifier: MIT package com.mercedesbenz.sechub.domain.scan.product.sereco; -import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; import com.mercedesbenz.sechub.commons.model.ScanType; import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveEntry; 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.ScanProjectConfig; import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigID; import com.mercedesbenz.sechub.domain.scan.project.ScanProjectConfigService; +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; public class SerecoFalsePositiveMarkerTest { private static final String PROJECT_ID = "project1"; private SerecoFalsePositiveMarker markerToTest; - private ScanProjectConfigService scanProjectConfigService; private ScanProjectConfig config; - private SerecoFalsePositiveFinder falsePositiveFinder; private FalsePositiveProjectConfiguration projectConfig; - @Before - public void before() throws Exception { - markerToTest = new SerecoFalsePositiveMarker(); + private static final ScanProjectConfigService scanProjectConfigService = mock(); + private static final SerecoJobDataFalsePositiveFinder jobDataFalsePositiveFinder = mock(); - scanProjectConfigService = mock(ScanProjectConfigService.class); - falsePositiveFinder = mock(SerecoFalsePositiveFinder.class); + private static final SerecoProjectDataFalsePositiveFinder projectDataFalsePositiveFinder = mock(); + private static final SerecoProjectDataPatternMapFactory projectDataPatternMapFactory = mock(); + + @BeforeEach + void beforeEach() throws Exception { + /* @formatter:off */ + Mockito.reset(jobDataFalsePositiveFinder, + projectDataFalsePositiveFinder, + scanProjectConfigService, + projectDataPatternMapFactory); + + markerToTest = new SerecoFalsePositiveMarker(jobDataFalsePositiveFinder, + projectDataFalsePositiveFinder, + scanProjectConfigService, + projectDataPatternMapFactory); + /* @formatter:on */ config = new ScanProjectConfig(ScanProjectConfigID.FALSE_POSITIVE_CONFIGURATION, PROJECT_ID); when(scanProjectConfigService.get(PROJECT_ID, ScanProjectConfigID.FALSE_POSITIVE_CONFIGURATION, false)).thenReturn(config); - markerToTest.scanProjectConfigService = scanProjectConfigService; - markerToTest.falsePositiveFinder = falsePositiveFinder; projectConfig = new FalsePositiveProjectConfiguration(); } @Test - public void a_webscan_triggers_falsePositiveFinder_for_fp_setting_for_webscan() { + void a_webscan_triggers_falsePositiveFinder_for_fp_setting_for_webscan() { /* prepare */ FalsePositiveMetaData metaData = addEntryAndReturnMetaData(projectConfig, ScanType.WEB_SCAN); @@ -56,11 +74,12 @@ public void a_webscan_triggers_falsePositiveFinder_for_fp_setting_for_webscan() markerToTest.markFalsePositives(PROJECT_ID, all); /* test */ - verify(falsePositiveFinder).isFound(v1, metaData); + verify(jobDataFalsePositiveFinder).isFound(v1, metaData); + verify(projectDataFalsePositiveFinder, never()).isFound(any(), any(), any()); } @Test - public void a_webscan_triggers_NOT_falsePositiveFinder_for_fp_setting_for_codescan() { + void a_webscan_triggers_NOT_falsePositiveFinder_for_fp_setting_for_codescan() { /* prepare */ FalsePositiveMetaData metaData = addEntryAndReturnMetaData(projectConfig, ScanType.CODE_SCAN); @@ -73,11 +92,12 @@ public void a_webscan_triggers_NOT_falsePositiveFinder_for_fp_setting_for_codesc markerToTest.markFalsePositives(PROJECT_ID, all); /* test */ - verify(falsePositiveFinder, never()).isFound(v1, metaData); + verify(jobDataFalsePositiveFinder, never()).isFound(v1, metaData); + verify(projectDataFalsePositiveFinder, never()).isFound(any(), any(), any()); } @Test - public void a_codescan_triggers_falsePositiveFinder_for_fp_setting_for_codescan() { + void a_codescan_triggers_falsePositiveFinder_for_fp_setting_for_codescan() { /* prepare */ FalsePositiveMetaData metaData = addEntryAndReturnMetaData(projectConfig, ScanType.CODE_SCAN); @@ -90,11 +110,12 @@ public void a_codescan_triggers_falsePositiveFinder_for_fp_setting_for_codescan( markerToTest.markFalsePositives(PROJECT_ID, all); /* test */ - verify(falsePositiveFinder).isFound(v1, metaData); + verify(jobDataFalsePositiveFinder).isFound(v1, metaData); + verify(projectDataFalsePositiveFinder, never()).isFound(any(), any(), any()); } @Test - public void a_codescan_triggers_NOT_falsePositiveFinder_for_fp_setting_for_webscan() { + void a_codescan_triggers_NOT_falsePositiveFinder_for_fp_setting_for_webscan() { /* prepare */ FalsePositiveMetaData metaData = addEntryAndReturnMetaData(projectConfig, ScanType.WEB_SCAN); @@ -107,7 +128,50 @@ public void a_codescan_triggers_NOT_falsePositiveFinder_for_fp_setting_for_websc markerToTest.markFalsePositives(PROJECT_ID, all); /* test */ - verify(falsePositiveFinder, never()).isFound(v1, metaData); + verify(jobDataFalsePositiveFinder, never()).isFound(v1, metaData); + verify(projectDataFalsePositiveFinder, never()).isFound(any(), any(), any()); + } + + @Test + void a_webscan_triggers_projectDataFalsePositiveFinder_when_projectData_with_webscan_available() { + /* prepare */ + FalsePositiveProjectData projectData = addEntryProjectDataWithWebscanAndReturnProjectData(projectConfig); + config.setData(projectConfig.toJSON()); + + List all = new ArrayList<>(); + SerecoVulnerability v1 = addVulnerability(all, ScanType.WEB_SCAN); + + @SuppressWarnings("unchecked") + Map mockedMap = mock(Map.class); + when(projectDataPatternMapFactory.create(projectConfig.getFalsePositives())).thenReturn(mockedMap); + + /* execute */ + markerToTest.markFalsePositives(PROJECT_ID, all); + + /* test */ + verify(projectDataFalsePositiveFinder).isFound(v1, projectData, mockedMap); + verify(jobDataFalsePositiveFinder, never()).isFound(any(), any()); + } + + @Test + void a_webscan_triggers_projectDataFalsePositiveFinder_when_projectData_without_webscan_available() { + /* prepare */ + FalsePositiveProjectData projectData = addEntryProjectDataWithoutWebscanAndReturnProjectData(projectConfig); + config.setData(projectConfig.toJSON()); + + List all = new ArrayList<>(); + SerecoVulnerability v1 = addVulnerability(all, ScanType.WEB_SCAN); + + @SuppressWarnings("unchecked") + Map mockedMap = mock(Map.class); + when(projectDataPatternMapFactory.create(projectConfig.getFalsePositives())).thenReturn(mockedMap); + + /* execute */ + markerToTest.markFalsePositives(PROJECT_ID, all); + + /* test */ + verify(projectDataFalsePositiveFinder).isFound(v1, projectData, mockedMap); + verify(jobDataFalsePositiveFinder, never()).isFound(any(), any()); } private SerecoVulnerability addVulnerability(List all, ScanType scanType) { @@ -117,6 +181,32 @@ private SerecoVulnerability addVulnerability(List all, Scan return v1; } + private FalsePositiveProjectData addEntryProjectDataWithWebscanAndReturnProjectData(FalsePositiveProjectConfiguration projectConfig) { + WebscanFalsePositiveProjectData webscan = new WebscanFalsePositiveProjectData(); + webscan.setHostPatterns(new ArrayList<>()); + webscan.setUrlPathPatterns(new ArrayList<>()); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setWebScan(webscan); + + FalsePositiveEntry entry = new FalsePositiveEntry(); + entry.setProjectData(projectData); + + List fp = projectConfig.getFalsePositives(); + fp.add(entry); + return projectData; + } + + private FalsePositiveProjectData addEntryProjectDataWithoutWebscanAndReturnProjectData(FalsePositiveProjectConfiguration projectConfig) { + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + FalsePositiveEntry entry = new FalsePositiveEntry(); + entry.setProjectData(projectData); + + List fp = projectConfig.getFalsePositives(); + fp.add(entry); + return projectData; + } + private FalsePositiveMetaData addEntryAndReturnMetaData(FalsePositiveProjectConfiguration projectConfig, ScanType scanType) { List fp = projectConfig.getFalsePositives(); FalsePositiveEntry e = new FalsePositiveEntry(); diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveSupportTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveSupportTest.java index 7457a240cf..061474b31a 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveSupportTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveSupportTest.java @@ -15,11 +15,11 @@ import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; class SerecoFalsePositiveSupportTest { - private SerecoFalsePositiveSupport supportToTest; + private SerecoJobDataFalsePositiveSupport supportToTest; @BeforeEach void beforeEach() { - supportToTest = new SerecoFalsePositiveSupport(); + supportToTest = new SerecoJobDataFalsePositiveSupport(); } @ParameterizedTest() diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveFinderTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveFinderTest.java similarity index 75% rename from sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveFinderTest.java rename to sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveFinderTest.java index 00453601c6..b92aee0eca 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoFalsePositiveFinderTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoJobDataFalsePositiveFinderTest.java @@ -12,11 +12,11 @@ import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectConfiguration; import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; -public class SerecoFalsePositiveFinderTest { +public class SerecoJobDataFalsePositiveFinderTest { - private SerecoFalsePositiveFinder finderToTest; - private CodeScanFalsePositiveStrategy codeScanStrategy; - private WebScanFalsePositiveStrategy webScanStrategy; + private SerecoJobDataFalsePositiveFinder finderToTest; + private CodeScanJobDataFalsePositiveStrategy jobDataCodeScanStrategy; + private WebScanJobDataFalsePositiveStrategy jobDataWebScanStrategy; // we use always true here, because every mock will return false when // not defined. Only some "syntactic sugar" to make test easier to read @@ -24,13 +24,13 @@ public class SerecoFalsePositiveFinderTest { @Before public void before() throws Exception { - finderToTest = new SerecoFalsePositiveFinder(); + finderToTest = new SerecoJobDataFalsePositiveFinder(); - codeScanStrategy = mock(CodeScanFalsePositiveStrategy.class); - finderToTest.codeScanStrategy = codeScanStrategy; + jobDataCodeScanStrategy = mock(CodeScanJobDataFalsePositiveStrategy.class); + finderToTest.jobDataCodeScanStrategy = jobDataCodeScanStrategy; - webScanStrategy = mock(WebScanFalsePositiveStrategy.class); - finderToTest.webScanStrategy = webScanStrategy; + jobDataWebScanStrategy = mock(WebScanJobDataFalsePositiveStrategy.class); + finderToTest.jobDataSebScanStrategy = jobDataWebScanStrategy; } @@ -55,13 +55,13 @@ public void code_scan_triggers_codescan_strategy_and_uses_its_result() { FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample3(); - when(codeScanStrategy.isFalsePositive(vulnerability, metaData)).thenReturn(yesItIsAFalsePositive); + when(jobDataCodeScanStrategy.isFalsePositive(vulnerability, metaData)).thenReturn(yesItIsAFalsePositive); /* execute */ boolean strategyResult = finderToTest.isFound(vulnerability, metaData); /* test */ - verify(codeScanStrategy).isFalsePositive(vulnerability, metaData); + verify(jobDataCodeScanStrategy).isFalsePositive(vulnerability, metaData); assertEquals(yesItIsAFalsePositive, strategyResult); } @@ -77,17 +77,17 @@ public void web_scan_triggers_webscan_strategy_and_uses_its_result() { FalsePositiveMetaData metaData = fetchFirstEntryMetaDataOfExample3(); - when(webScanStrategy.isFalsePositive(vulnerability, metaData)).thenReturn(yesItIsAFalsePositive); + when(jobDataWebScanStrategy.isFalsePositive(vulnerability, metaData)).thenReturn(yesItIsAFalsePositive); /* execute */ boolean strategyResult = finderToTest.isFound(vulnerability, metaData); /* test */ - verify(webScanStrategy).isFalsePositive(vulnerability, metaData); + verify(jobDataWebScanStrategy).isFalsePositive(vulnerability, metaData); assertEquals(yesItIsAFalsePositive, strategyResult); // additional check that other strategy is not called here - verify(codeScanStrategy, never()).isFalsePositive(vulnerability, metaData); + verify(jobDataCodeScanStrategy, never()).isFalsePositive(vulnerability, metaData); } @Test @@ -107,7 +107,7 @@ public void webscan_triggers_not_codescanstrategy() { finderToTest.isFound(vulnerability, metaData); /* test */ - verify(codeScanStrategy, never()).isFalsePositive(vulnerability, metaData); + verify(jobDataCodeScanStrategy, never()).isFalsePositive(vulnerability, metaData); } @Test @@ -127,7 +127,7 @@ public void infrascan_triggers_not_codescanstrategy() { finderToTest.isFound(vulnerability, metaData); /* test */ - verify(codeScanStrategy, never()).isFalsePositive(vulnerability, metaData); + verify(jobDataCodeScanStrategy, never()).isFalsePositive(vulnerability, metaData); } private FalsePositiveMetaData fetchFirstEntryMetaDataOfExample3() { diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinderTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinderTest.java new file mode 100644 index 0000000000..792ec7d50b --- /dev/null +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataFalsePositiveFinderTest.java @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.mockito.Mockito; + +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; + +class SerecoProjectDataFalsePositiveFinderTest { + + private SerecoProjectDataFalsePositiveFinder finderToTest; + + private static final WebScanProjectDataFalsePositiveStrategy webScanProjectDataStrategy = mock(); + + @BeforeEach + void beforeEach() { + Mockito.reset(webScanProjectDataStrategy); + + finderToTest = new SerecoProjectDataFalsePositiveFinder(webScanProjectDataStrategy); + } + + @Test + void vulnerability_invalid_no_strategy_is_ever_called() { + /* prepare */ + SerecoVulnerability vuln1 = null; + SerecoVulnerability vuln2 = new SerecoVulnerability(); + SerecoVulnerability vuln3 = new SerecoVulnerability(); + vuln3.setScanType(ScanType.WEB_SCAN); + + // not null + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + + // not null or empty + Map patternMap = new HashMap<>(); + patternMap.put("key", mock(Pattern.class)); + + /* execute */ + boolean result1 = finderToTest.isFound(vuln1, projectData, patternMap); + boolean result2 = finderToTest.isFound(vuln2, projectData, patternMap); + boolean result3 = finderToTest.isFound(vuln3, projectData, patternMap); + + /* test */ + verify(webScanProjectDataStrategy, never()).isFalsePositive(vuln1, projectData, patternMap); + verify(webScanProjectDataStrategy, never()).isFalsePositive(vuln2, projectData, patternMap); + verify(webScanProjectDataStrategy, never()).isFalsePositive(vuln3, projectData, patternMap); + + assertFalse(result1); + assertFalse(result2); + assertFalse(result3); + } + + @Test + void projectData_null_no_strategy_is_ever_called() { + /* prepare */ + SerecoVulnerability vuln = new SerecoVulnerability(); + vuln.setScanType(ScanType.WEB_SCAN); + vuln.setType("type"); + + FalsePositiveProjectData projectData = null; + // not null or empty + Map patternMap = new HashMap<>(); + patternMap.put("key", mock(Pattern.class)); + + /* execute */ + boolean result = finderToTest.isFound(vuln, projectData, patternMap); + + /* test */ + verify(webScanProjectDataStrategy, never()).isFalsePositive(vuln, projectData, patternMap); + assertFalse(result); + } + + @Test + void patternMap_null_or_empty_no_strategy_is_ever_called() { + /* prepare */ + SerecoVulnerability vuln = new SerecoVulnerability(); + vuln.setScanType(ScanType.WEB_SCAN); + vuln.setType("type"); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + + Map patternMapNull = null; + Map patternMapEmpty = new HashMap<>(); + + /* execute */ + boolean result1 = finderToTest.isFound(vuln, projectData, patternMapNull); + boolean result2 = finderToTest.isFound(vuln, projectData, patternMapEmpty); + + /* test */ + verify(webScanProjectDataStrategy, never()).isFalsePositive(vuln, projectData, patternMapNull); + verify(webScanProjectDataStrategy, never()).isFalsePositive(vuln, projectData, patternMapEmpty); + + assertFalse(result1); + assertFalse(result2); + } + + @ParameterizedTest + @EnumSource(ScanType.class) + void scantypes_are_handle_correctly(ScanType scantype) { + // This test needs to be updated if other scan types are supported with project + // data in the future + + /* prepare */ + SerecoVulnerability vuln = new SerecoVulnerability(); + vuln.setScanType(scantype); + vuln.setType("type"); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + + Map patternMap = new HashMap<>(); + patternMap.put("key", mock(Pattern.class)); + + when(webScanProjectDataStrategy.isFalsePositive(vuln, projectData, patternMap)).thenReturn(true); + + /* execute */ + boolean result = finderToTest.isFound(vuln, projectData, patternMap); + + /* test */ + if (scantype == ScanType.WEB_SCAN) { + verify(webScanProjectDataStrategy, times(1)).isFalsePositive(vuln, projectData, patternMap); + assertTrue(result); + } else { + verify(webScanProjectDataStrategy, never()).isFalsePositive(vuln, projectData, patternMap); + assertFalse(result); + } + } + +} 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 new file mode 100644 index 0000000000..f1c65dd1cc --- /dev/null +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoProjectDataPatternMapFactoryTest.java @@ -0,0 +1,329 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveEntry; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveJobData; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; + +class SerecoProjectDataPatternMapFactoryTest { + + private SerecoProjectDataPatternMapFactory factoryToTest; + + @BeforeEach + void beforeEach() { + factoryToTest = new SerecoProjectDataPatternMapFactory(); + } + + /*-------------------------------------TEST PATTERN CREATION-------------------------------------------*/ + + @Test + void factory_throws_illegal_argument_exception_if_list_of_false_positives_is_null() { + /* execute + test */ + assertThrows(IllegalArgumentException.class, () -> factoryToTest.create(null)); + } + + @Test + void factory_returns_empty_map_if_list_of_false_positives_is_null() { + /* execute */ + Map map = factoryToTest.create(new ArrayList<>()); + + /* test */ + assertTrue(map.isEmpty()); + } + + @Test + void factory_returns_empty_map_if_list_of_false_positives_only_contains_jobData_entries() { + /* prepare */ + List falsePositives = new ArrayList<>(); + + FalsePositiveEntry entry1 = new FalsePositiveEntry(); + entry1.setJobData(new FalsePositiveJobData()); + falsePositives.add(entry1); + + FalsePositiveEntry entry2 = new FalsePositiveEntry(); + entry2.setJobData(new FalsePositiveJobData()); + falsePositives.add(entry2); + + /* execute */ + Map map = factoryToTest.create(falsePositives); + + /* test */ + 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); + + 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); + + /* execute */ + Map patternMap = factoryToTest.create(falsePositives); + + /* test */ + Pattern pattern = patternMap.get(hostPattern); + + 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---------------------------------*/ + + @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"); + + List falsePositives = createFalsePositiveEntriesForProjectDataWebscan(urlPathPatterns, hostNames); + + /* execute */ + Map patternMap = factoryToTest.create(falsePositives); + + /* test */ + Pattern pattern = patternMap.get(urlPathPattern); + + assertMatches(mustMatchUrlPaths, pattern); + assertDoesNotMatch(mustNotMatchUrlPaths, pattern); + } + + @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); + } + + /*----------------------------------------------HELPERS------------------------------------------------------*/ + + private List createFalsePositiveEntriesForProjectDataWebscan(List urlPathPatterns, List hostPatterns) { + List falsePositives = new ArrayList<>(); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); + webScan.setUrlPathPatterns(urlPathPatterns); + webScan.setHostPatterns(hostPatterns); + projectData.setWebScan(webScan); + + FalsePositiveEntry entry = new FalsePositiveEntry(); + entry.setProjectData(projectData); + falsePositives.add(entry); + 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 new file mode 100644 index 0000000000..a937914a78 --- /dev/null +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/SerecoWebScanFalsePositiveProjectDataSupportTest.java @@ -0,0 +1,343 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; + +class SerecoWebScanFalsePositiveProjectDataSupportTest { + + private static List urlPathPatterns = List.of("*/rest/api/", "/rest/api/user/profile"); + private static String matchingUrlPathPattern = "/rest/api/user/profile"; + + private static List hostPatterns = List.of("*.example.com", "test.exampleapp.com"); + private static String matchingHost = "prod.example.com"; + + private static Pattern mockedPattern = mock(Pattern.class); + private static Matcher mockedMatcher = mock(Matcher.class); + + private SerecoProjectDataWebScanFalsePositiveSupport supportToTest; + private Map patternMap; + + @BeforeEach + void beforeEach() { + supportToTest = new SerecoProjectDataWebScanFalsePositiveSupport(); + patternMap = createPatternMapWithMocks(); + + when(mockedPattern.matcher(matchingHost)).thenReturn(mockedMatcher); + when(mockedPattern.matcher(matchingUrlPathPattern)).thenReturn(mockedMatcher); + } + + /*-------------------------------------CWE-IDs----------------------------------------------*/ + @ParameterizedTest + @EmptySource + @NullSource + @ValueSource(strings = { "1", "-1", "0", "4711" }) + void both_having_the_same_cwe_id_returns_true(String cweId) { + /* prepare */ + WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); + webScanData.setCweId(createIntegerFromString(cweId)); + SerecoVulnerability vulnerability = new SerecoVulnerability(); + vulnerability.getClassification().setCwe(cweId); + + /* execute */ + boolean result = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); + + /* test */ + assertTrue(result); + } + + @ParameterizedTest + @EmptySource + @NullSource + @ValueSource(strings = { "1", "-1", "0", "4711" }) + void cwe_id_of_webscan_data_is_one_more_returns_false(String cweId) { + /* prepare */ + WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); + webScanData.setCweId(createAsIntButPlusOne(cweId)); + + SerecoVulnerability vulnerability = new SerecoVulnerability(); + vulnerability.getClassification().setCwe(cweId); + + /* execute */ + boolean areBothHavingSameCweIdOrBothNoCweId = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); + + /* test */ + assertFalse(areBothHavingSameCweIdOrBothNoCweId); + } + + @ParameterizedTest + @NullSource + @ValueSource(ints = { 1, -1, 0, 4711 }) + void cwe_id_of_vulnerability_is_one_more_returns_false(Integer cweId) { + /* prepare */ + WebscanFalsePositiveProjectData webScanData = new WebscanFalsePositiveProjectData(); + webScanData.setCweId(cweId); + + SerecoVulnerability vulnerability = new SerecoVulnerability(); + vulnerability.getClassification().setCwe(createIntAsStringButPlusOne(cweId)); + + /* execute */ + boolean areBothHavingSameCweIdOrBothNoCweId = supportToTest.areBothHavingSameCweIdOrBothNoCweId(webScanData, vulnerability); + + /* test */ + assertFalse(areBothHavingSameCweIdOrBothNoCweId); + } + + /*-----------------------------------------------METHODS-----------------------------------------------*/ + + @Test + void methods_not_set_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("anything", null); + + /* test */ + assertTrue(result); + } + + @Test + void methods_empty_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("anything", Collections.emptyList()); + + /* test */ + assertTrue(result); + } + + @Test + void methods_containing_required_string_returns_true() { + /* prepare */ + List methods = List.of("POST", "GET", "DELETE"); + + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("GET", methods); + + /* test */ + assertTrue(result); + } + + @Test + void methods_not_containing_required_string_returns_false() { + /* prepare */ + List methods = List.of("POST", "GET", "DELETE"); + + /* execute */ + boolean result = supportToTest.isMatchingMethodOrIgnoreIfNotSet("no-in-list", methods); + + /* test */ + assertFalse(result); + } + + /*--------------------------------------------PORTS-----------------------------------------------*/ + + @Test + void ports_not_set_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("anything", null); + + /* test */ + assertTrue(result); + } + + @Test + void ports_empty_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("anything", Collections.emptyList()); + + /* test */ + assertTrue(result); + } + + @Test + void port_containing_required_string_returns_true() { + /* prepare */ + List ports = List.of("8080", "443", "80"); + + /* execute */ + boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("80", ports); + + /* test */ + assertTrue(result); + } + + @Test + void ports_not_containing_required_string_returns_false() { + /* prepare */ + List ports = List.of("8080", "443", "80"); + + /* execute */ + boolean result = supportToTest.isMatchingPortOrIgnoreIfNotSet("no-in-list", ports); + + /* test */ + assertFalse(result); + } + + /*----------------------------------------PROTOCOLS-----------------------------------------------*/ + + @Test + void protocols_not_set_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("anything", null); + + /* test */ + assertTrue(result); + } + + @Test + void protocols_empty_in_webscan_data_being_null_returns_true() { + /* execute */ + boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("anything", Collections.emptyList()); + + /* test */ + assertTrue(result); + } + + @Test + void protocols_containing_required_string_returns_true() { + /* prepare */ + List protocols = List.of("wss", "https"); + + /* execute */ + boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("https", protocols); + + /* test */ + assertTrue(result); + } + + @Test + void protocols_not_containing_required_string_returns_false() { + /* prepare */ + List protocols = List.of("wss", "https"); + + /* execute */ + boolean result = supportToTest.isMatchingProtocolOrIgnoreIfNotSet("no-in-list", protocols); + + /* test */ + assertFalse(result); + } + + /*----------------------------------------HOSTPATTERNS-----------------------------------------------*/ + + @Test + void for_hostPatterns_pattern_in_map_is_null_throws_exception() { + /* execute + test */ + // At this point this should never happen because the map is meant to be created + // by the associated projectData + assertThrows(IllegalStateException.class, () -> supportToTest.isMatchingHostPattern(matchingHost, hostPatterns, new HashMap<>())); + } + + @Test + void for_hostPatterns_not_matching_returns_false() { + /* prepare */ + when(mockedMatcher.matches()).thenReturn(false); + + /* execute */ + boolean result = supportToTest.isMatchingHostPattern(matchingHost, hostPatterns, patternMap); + + /* test */ + assertFalse(result); + } + + @Test + void for_hostPatterns_is_matching_returns_true() { + /* prepare */ + when(mockedMatcher.matches()).thenReturn(true); + + /* execute */ + boolean result = supportToTest.isMatchingHostPattern(matchingHost, hostPatterns, patternMap); + + /* test */ + assertTrue(result); + } + + /*---------------------------------------URLPATHPATTERNS--------------------------------------------*/ + + @Test + void for_urlPathPatterns_pattern_in_map_is_null_throws_exception() { + /* execute + test */ + // At this point this should never happen because the map is meant to be created + // by the associated projectData + assertThrows(IllegalStateException.class, () -> supportToTest.isMatchingHostPattern(matchingUrlPathPattern, urlPathPatterns, new HashMap<>())); + } + + @Test + void for_urlPathPatterns_not_matching_returns_false() { + /* prepare */ + when(mockedMatcher.matches()).thenReturn(false); + + /* execute */ + boolean result = supportToTest.isMatchingHostPattern(matchingUrlPathPattern, urlPathPatterns, patternMap); + + /* test */ + assertFalse(result); + } + + @Test + void for_urlPathPatterns_is_matching_returns_true() { + /* prepare */ + when(mockedMatcher.matches()).thenReturn(true); + + /* execute */ + boolean result = supportToTest.isMatchingHostPattern(matchingUrlPathPattern, urlPathPatterns, patternMap); + + /* test */ + assertTrue(result); + } + + /*-------------------------------------HELPERS----------------------------------------------*/ + + private Integer createIntegerFromString(String cweId) { + if (cweId == null) { + return null; + } + if (cweId.isEmpty()) { + return null; + } + return Integer.parseInt(cweId); + } + + private String createIntAsStringButPlusOne(Integer cweId) { + if (cweId == null) { + return "1"; + } + int next = cweId.intValue() + 1; + return String.valueOf(next); + } + + private Integer createAsIntButPlusOne(String cweId) { + Integer intvalue = createIntegerFromString(cweId); + if (intvalue == null) { + return 1; + } + return intvalue + 1; + } + + private Map createPatternMapWithMocks() { + Map patternMap = new HashMap<>(); + for (String urlPathPattern : urlPathPatterns) { + patternMap.put(urlPathPattern, mockedPattern); + } + + for (String hostPattern : hostPatterns) { + patternMap.put(hostPattern, mockedPattern); + } + return patternMap; + } + +} diff --git a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanFalsePositiveStrategyTest.java b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanJobDataFalsePositiveStrategyTest.java similarity index 77% rename from sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanFalsePositiveStrategyTest.java rename to sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanJobDataFalsePositiveStrategyTest.java index 50c80f3bb5..5e4601e2e6 100644 --- a/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanFalsePositiveStrategyTest.java +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanJobDataFalsePositiveStrategyTest.java @@ -23,29 +23,29 @@ import com.mercedesbenz.sechub.sereco.metadata.SerecoWebEvidence; import com.mercedesbenz.sechub.sereco.metadata.SerecoWebRequest; -class WebScanFalsePositiveStrategyTest { +class WebScanJobDataFalsePositiveStrategyTest { private static final String METHOD1 = "method1"; private static final String EVIDENCE1 = "evidence1"; private static final String TARGET1 = "target1"; private static final String ATTACK_VECTOR1 = "vector1"; private static final int CWE_ID_4711 = 4711; - private WebScanFalsePositiveStrategy strategyToTest; + private WebScanJobDataFalsePositiveStrategy strategyToTest; private FalsePositivedTestDataContainer testData; - private SerecoFalsePositiveSupport serecoFalsePositiveSupport; + private SerecoJobDataFalsePositiveSupport serecoJobDataFalsePositiveSupport; @BeforeEach void beforeEach() { - strategyToTest = new WebScanFalsePositiveStrategy(); + strategyToTest = new WebScanJobDataFalsePositiveStrategy(); // Initial this created test data contains meta and vulnerability data which do // match. When not changed, this must lead to a false positive detection. Tests // do change this data to simulate different situations. testData = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); - serecoFalsePositiveSupport = mock(SerecoFalsePositiveSupport.class); + serecoJobDataFalsePositiveSupport = mock(SerecoJobDataFalsePositiveSupport.class); - strategyToTest.falsePositiveSupport = serecoFalsePositiveSupport; + strategyToTest.falsePositiveSupport = serecoJobDataFalsePositiveSupport; } /* @formatter:off */ @@ -61,8 +61,8 @@ void beforeEach() { void no_false_positive_because_metadata_has_not_target_like_vulnerability(String target) { /* prepare */ testData.metaData.getWeb().getRequest().setTarget(target); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testData.vulnerability, testData.metaData); @@ -77,8 +77,8 @@ void no_false_positive_because_metadata_has_not_target_like_vulnerability(String void is_false_positive_because_metadata_has_same_target_like_vulnerability(String target) { /* prepare */ testData.metaData.getWeb().getRequest().setTarget(target); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testData.vulnerability, testData.metaData); @@ -98,8 +98,8 @@ void is_false_positive_because_metadata_has_same_target_like_vulnerability(Strin void no_false_positive_because_metadata_has_not_method_like_vulnerability(String method) { /* prepare */ testData.metaData.getWeb().getRequest().setMethod(method); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testData.vulnerability, testData.metaData); @@ -114,8 +114,8 @@ void no_false_positive_because_metadata_has_not_method_like_vulnerability(String void is_false_positive_because_metadata_has_similar_method_like_vulnerability(String method) { /* prepare */ testData.metaData.getWeb().getRequest().setMethod(method); - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testData.vulnerability, testData.metaData); @@ -132,8 +132,8 @@ void is_false_positive_because_metadata_has_similar_method_like_vulnerability(St @Test void no_false_positive_because_wrong_metadata_cweid() { /* prepare */ - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(false); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(false); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testData.vulnerability, testData.metaData); @@ -151,8 +151,8 @@ void no_false_positive_because_wrong_metadata_cweid() { @NullSource void no_false_positive_because_wrong_scan_type(ScanType type) { /* prepare */ - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(false); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(false); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testData.vulnerability, testData.metaData); @@ -164,8 +164,8 @@ void no_false_positive_because_wrong_scan_type(ScanType type) { @Test void is_false_positive_because_correct_scan_type() { /* prepare */ - when(serecoFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); - when(serecoFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingExpectedScanType(ScanType.WEB_SCAN, testData.metaData, testData.vulnerability)).thenReturn(true); + when(serecoJobDataFalsePositiveSupport.areBothHavingSameCweIdOrBothNoCweId(testData.metaData,testData.vulnerability)).thenReturn(true); /* execute */ boolean isFalsePositive = strategyToTest.isFalsePositive(testData.vulnerability, testData.metaData); 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 new file mode 100644 index 0000000000..1da8c9da05 --- /dev/null +++ b/sechub-scan-product-sereco/src/test/java/com/mercedesbenz/sechub/domain/scan/product/sereco/WebScanProjectDataFalsePositiveStrategyTest.java @@ -0,0 +1,379 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.product.sereco; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.mercedesbenz.sechub.commons.model.ScanType; +import com.mercedesbenz.sechub.domain.scan.project.FalsePositiveProjectData; +import com.mercedesbenz.sechub.domain.scan.project.WebscanFalsePositiveProjectData; +import com.mercedesbenz.sechub.sereco.metadata.SerecoVulnerability; +import com.mercedesbenz.sechub.sereco.metadata.SerecoWeb; +import com.mercedesbenz.sechub.sereco.metadata.SerecoWebRequest; + +class WebScanProjectDataFalsePositiveStrategyTest { + + private static final String PROTOCOL1 = "https"; + private static final String METHOD1 = "GET"; + private static final String TARGET1 = "https://api.example.com/rest/users/projects"; + private static final int CWE_ID_4711 = 4711; + + private WebScanProjectDataFalsePositiveStrategy strategyToTest; + + private static final SerecoProjectDataWebScanFalsePositiveSupport webscanFalsePositiveProjectDataSupport = mock(); + + @BeforeEach + void beforeEach() { + Mockito.reset(webscanFalsePositiveProjectDataSupport); + + strategyToTest = new WebScanProjectDataFalsePositiveStrategy(webscanFalsePositiveProjectDataSupport); + } + + @Test + void patternMap_empty_results_in_result_being_false() { + /* prepare */ + FalsePositiveTestDataContainer testDataContainer = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); + + /* execute */ + boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, new HashMap<>()); + + /* test */ + assertFalse(isFalsePositive); + } + + @Test + void scantype_is_not_webscan_results_in_result_being_false() { + /* prepare */ + Map projectDataPatternMap = new HashMap<>(); + projectDataPatternMap.put("key", mock(Pattern.class)); + + SerecoVulnerability vulnerability = mock(SerecoVulnerability.class); + FalsePositiveProjectData projectData = mock(FalsePositiveProjectData.class); + + when(vulnerability.getScanType()).thenReturn(null); + + /* execute */ + boolean isFalsePositive = strategyToTest.isFalsePositive(vulnerability, projectData, projectDataPatternMap); + + /* test */ + verify(vulnerability, times(1)).getScanType(); + verify(projectData, never()).getWebScan(); + assertFalse(isFalsePositive); + } + + @Test + void projectData_webscan_is_null_results_in_result_being_false() { + /* prepare */ + Map projectDataPatternMap = new HashMap<>(); + projectDataPatternMap.put("key", mock(Pattern.class)); + + SerecoVulnerability vulnerability = mock(SerecoVulnerability.class); + FalsePositiveProjectData projectData = mock(FalsePositiveProjectData.class); + + when(vulnerability.getScanType()).thenReturn(ScanType.WEB_SCAN); + when(projectData.getWebScan()).thenReturn(null); + + /* execute */ + boolean isFalsePositive = strategyToTest.isFalsePositive(vulnerability, projectData, projectDataPatternMap); + + /* test */ + verify(vulnerability, times(1)).getScanType(); + verify(projectData, times(1)).getWebScan(); + assertFalse(isFalsePositive); + } + + @Test + void vulnerability_web_part_is_null_results_in_result_being_false() { + /* prepare */ + Map projectDataPatternMap = new HashMap<>(); + projectDataPatternMap.put("key", mock(Pattern.class)); + + SerecoVulnerability vulnerability = mock(SerecoVulnerability.class); + FalsePositiveProjectData projectData = mock(FalsePositiveProjectData.class); + + when(vulnerability.getScanType()).thenReturn(ScanType.WEB_SCAN); + when(vulnerability.getWeb()).thenReturn(null); + when(projectData.getWebScan()).thenReturn(new WebscanFalsePositiveProjectData()); + + /* execute */ + boolean isFalsePositive = strategyToTest.isFalsePositive(vulnerability, projectData, projectDataPatternMap); + + /* test */ + verify(vulnerability, times(1)).getScanType(); + verify(projectData, times(1)).getWebScan(); + verify(vulnerability, times(1)).getWeb(); + assertFalse(isFalsePositive); + } + + @Test + void cwe_comparison_fails_results_in_result_being_false() { + /* prepare */ + FalsePositiveTestDataContainer testDataContainer = createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition(); + + when(webscanFalsePositiveProjectDataSupport.areBothHavingSameCweIdOrBothNoCweId(testDataContainer.projectData.getWebScan(), + testDataContainer.vulnerability)).thenReturn(false); + + /* execute */ + boolean isFalsePositive = strategyToTest.isFalsePositive(testDataContainer.vulnerability, testDataContainer.projectData, + testDataContainer.projectDataPatternMap); + + /* test */ + 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()).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() { + /* 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); + + /* 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); + // 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); + // nothing else is called + verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingMethodOrIgnoreIfNotSet(any(), any()); + verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingProtocolOrIgnoreIfNotSet(any(), any()); + verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingPortOrIgnoreIfNotSet(any(), any()); + + assertFalse(isFalsePositive); + } + + @Test + void method_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(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.isMatchingPortOrIgnoreIfNotSet("443", webScan.getPorts())).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()); + // nothing else is called + verify(webscanFalsePositiveProjectDataSupport, never()).isMatchingProtocolOrIgnoreIfNotSet(PROTOCOL1, null); + + assertFalse(isFalsePositive); + } + + @Test + void protocol_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.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(); + + 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, + 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()); + + assertTrue(isFalsePositive); + } + + private FalsePositiveTestDataContainer createInitialTestDataWithMatchingVulnerabilityAndFalsePositiveDefinition() { + return new FalsePositiveTestDataContainer(); + } + + private class FalsePositiveTestDataContainer { + FalsePositiveProjectData projectData = createValidTestFalsePositiveProjectData(); + SerecoVulnerability vulnerability = createValidTestVulnerability(); + Map projectDataPatternMap = createMockedTestMap(); + + } + + private FalsePositiveProjectData createValidTestFalsePositiveProjectData() { + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + 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")); + + projectData.setWebScan(webScan); + + return projectData; + } + + private SerecoVulnerability createValidTestVulnerability() { + SerecoVulnerability vulnerability = new SerecoVulnerability(); + SerecoWeb web = new SerecoWeb(); + vulnerability.getClassification().setCwe("" + CWE_ID_4711); + vulnerability.setWeb(web); + vulnerability.setScanType(ScanType.WEB_SCAN); + + SerecoWebRequest request = web.getRequest(); + request.setMethod(METHOD1); + request.setTarget(TARGET1); + request.setProtocol(PROTOCOL1); + + return vulnerability; + } + + private Map createMockedTestMap() { + Map projectDataPatternMap = new HashMap<>(); + projectDataPatternMap.put("key", mock(Pattern.class)); + + return projectDataPatternMap; + } +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataConfigMerger.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMerger.java similarity index 57% rename from sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataConfigMerger.java rename to sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMerger.java index 68d26d895b..0aa9dd36bd 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataConfigMerger.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; @@ -20,9 +22,9 @@ * */ @Component -public class FalsePositiveJobDataConfigMerger { +public class FalsePositiveDataConfigMerger { - private static final Logger LOG = LoggerFactory.getLogger(FalsePositiveJobDataConfigMerger.class); + private static final Logger LOG = LoggerFactory.getLogger(FalsePositiveDataConfigMerger.class); @Autowired FalsePositiveMetaDataFactory metaDataFactory; @@ -50,21 +52,73 @@ public void addJobDataWithMetaDataToConfig(ScanSecHubReport report, FalsePositiv } + public void addFalsePositiveProjectDataEntryOrUpdateExisting(FalsePositiveProjectConfiguration config, FalsePositiveProjectData projectData, + String userId) { + 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); + } + public void removeJobDataWithMetaDataFromConfig(FalsePositiveProjectConfiguration config, FalsePositiveJobData jobDataToRemove) { FalsePositiveEntry entry = findExistingFalsePositiveEntryInConfig(config, jobDataToRemove); - if (entry == null) { - return; + if (entry != null) { + config.getFalsePositives().remove(entry); + } + + } + + public void removeProjectDataFromConfig(FalsePositiveProjectConfiguration config, FalsePositiveProjectData projectDataToRemove) { + FalsePositiveEntry entry = findExistingProjectDataFalsePositiveEntryInConfig(config, projectDataToRemove); + if (entry != null) { + config.getFalsePositives().remove(entry); } - config.getFalsePositives().remove(entry); + } public boolean isFalsePositiveEntryAlreadyExisting(FalsePositiveProjectConfiguration config, FalsePositiveJobData falsePositiveJobData) { return findExistingFalsePositiveEntryInConfig(config, falsePositiveJobData) != null; } + private FalsePositiveEntry findExistingProjectDataFalsePositiveEntryInConfig(FalsePositiveProjectConfiguration config, + FalsePositiveProjectData projectDataToRemove) { + for (FalsePositiveEntry existingFPEntry : config.getFalsePositives()) { + 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(projectDataToRemove.getId())) { + return existingFPEntry; + } + } + return null; + } + private FalsePositiveEntry findExistingFalsePositiveEntryInConfig(FalsePositiveProjectConfiguration config, FalsePositiveJobData falsePositiveJobData) { for (FalsePositiveEntry existingFPEntry : config.getFalsePositives()) { FalsePositiveJobData jobData = existingFPEntry.getJobData(); + if (jobData == null) { + LOG.debug("The entry is a projectData entry so no jobData and metaData"); + continue; + } + if (!jobData.getJobUUID().equals(falsePositiveJobData.getJobUUID())) { continue; } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataList.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataList.java similarity index 64% rename from sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataList.java rename to sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataList.java index 12bdc1ca63..fada506771 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataList.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataList.java @@ -13,14 +13,19 @@ @JsonIgnoreProperties(ignoreUnknown = true) // we do ignore to avoid problems from wrong configured values! @JsonInclude(value = Include.NON_ABSENT) @MustBeKeptStable -public class FalsePositiveJobDataList implements JSONable { +public class FalsePositiveDataList implements JSONable { - public static final String ACCEPTED_TYPE = "falsePositiveJobDataList"; - private static final FalsePositiveJobDataList CONVERTER = new FalsePositiveJobDataList(); + @Deprecated + public static final String DEPRECATED_ACCEPTED_TYPE = "falsePositiveJobDataList"; + + public static final String ACCEPTED_TYPE = "falsePositiveDataList"; + + private static final FalsePositiveDataList CONVERTER = new FalsePositiveDataList(); public static final String PROPERTY_API_VERSION = "apiVersion"; public static final String PROPERTY_TYPE = "type"; public static final String PROPERTY_JOBDATA = "jobData"; + public static final String PROPERTY_PROJECTDATA = "projectData"; private String apiVersion; @@ -28,10 +33,16 @@ public class FalsePositiveJobDataList implements JSONable jobData = new ArrayList<>(); + private List projectData = new ArrayList<>(); + public List getJobData() { return jobData; } + public List getProjectData() { + return projectData; + } + public String getType() { return type; } @@ -41,8 +52,8 @@ public void setType(String type) { } @Override - public Class getJSONTargetClass() { - return FalsePositiveJobDataList.class; + public Class getJSONTargetClass() { + return FalsePositiveDataList.class; } public void setApiVersion(String apiVersion) { @@ -53,7 +64,7 @@ public String getApiVersion() { return apiVersion; } - public static FalsePositiveJobDataList fromString(String json) { + public static FalsePositiveDataList fromString(String json) { return CONVERTER.fromJSON(json); } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidation.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidation.java similarity index 61% rename from sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidation.java rename to sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidation.java index 64c1f89b47..bd161d64ff 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidation.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidation.java @@ -3,6 +3,6 @@ import com.mercedesbenz.sechub.sharedkernel.validation.Validation; -public interface FalsePositiveJobDataListValidation extends Validation { +public interface FalsePositiveDataListValidation extends Validation { } 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 new file mode 100644 index 0000000000..903894c4f7 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImpl.java @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.validation.AbstractValidation; +import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidation; +import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidationFactory; +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationContext; +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; + +@Component +public class FalsePositiveDataListValidationImpl extends AbstractValidation implements FalsePositiveDataListValidation { + + private static final String API_VERSION_1_0 = "1.0"; + private static final int MINIMUM_FP_DATA_LIST_SIZE = 0; + private static final int MAXIMUM_FP_DATA_LIST_SIZE = 500; + + private static final String VALIDATOR_NAME = "false positive list validation"; + + private final ApiVersionValidationFactory apiVersionValidationFactory; + + private ApiVersionValidation apiVersionValidation; + + private final FalsePositiveJobDataValidation falsePositiveJobDataValidation; + + private final FalsePositiveProjectDataValidation falsePositiveProjectDataValidation; + + /* @formatter:off */ + public FalsePositiveDataListValidationImpl(ApiVersionValidationFactory apiVersionValidationFactory, + FalsePositiveJobDataValidation falsePositiveJobDataValidation, + FalsePositiveProjectDataValidation falsePositiveProjectDataValidation) { + + this.apiVersionValidationFactory = apiVersionValidationFactory; + this.falsePositiveJobDataValidation = falsePositiveJobDataValidation; + this.falsePositiveProjectDataValidation = falsePositiveProjectDataValidation; + /* @formatter:on */ + + apiVersionValidation = this.apiVersionValidationFactory.createValidationAccepting(API_VERSION_1_0); + } + + @Override + protected void setup(AbstractValidation.ValidationConfig config) { + config.minLength = MINIMUM_FP_DATA_LIST_SIZE; + config.maxLength = MAXIMUM_FP_DATA_LIST_SIZE; + } + + @Override + protected String getValidatorName() { + return VALIDATOR_NAME; + } + + @Override + protected void validate(ValidationContext context) { + validateNotNull(context); + if (context.isInValid()) { + return; + } + + FalsePositiveDataList target = getObjectToValidate(context); + + validateVersion(context, target); + if (context.isInValid()) { + return; + } + + validateType(context, target); + if (context.isInValid()) { + return; + } + + validateJobDataAndProjectDataSize(context, target); + if (context.isInValid()) { + return; + } + + validateJobData(context, target.getJobData()); + + validateProjectData(context, target.getProjectData()); + + } + + private void validateProjectData(ValidationContext context, List projectDataList) { + for (FalsePositiveProjectData projectData : projectDataList) { + ValidationResult resultForJobData = falsePositiveProjectDataValidation.validate(projectData); + if (!resultForJobData.isValid()) { + context.addErrors(resultForJobData); + } + } + } + + private void validateJobData(ValidationContext context, List jobDataList) { + for (FalsePositiveJobData jobData : jobDataList) { + ValidationResult resultForJobData = falsePositiveJobDataValidation.validate(jobData); + if (!resultForJobData.isValid()) { + context.addErrors(resultForJobData); + } + } + } + + private void validateJobDataAndProjectDataSize(ValidationContext context, FalsePositiveDataList target) { + List jobDataList = target.getJobData(); + List projectDataList = target.getProjectData(); + + validateNotNull(context, jobDataList, "projectDataList"); + validateNotNull(context, projectDataList, "projectDataList"); + if (context.isInValid()) { + return; + } + + validateMinSize(context, jobDataList, getConfig().minLength, "jobDataList"); + validateMinSize(context, jobDataList, getConfig().minLength, "projectDataList"); + + int combinedSize = jobDataList.size() + projectDataList.size(); + + if (combinedSize > getConfig().maxLength) { + context.addError(getValidatorName(), ": The number of specified false positives in jobDataList and projectDataList must not be greater than: %s" + .formatted(getConfig().maxLength)); + } + } + + private void validateType(ValidationContext context, FalsePositiveDataList target) { + validateContainsExpectedOnly(context, "given type not known", target.getType(), FalsePositiveDataList.ACCEPTED_TYPE, + FalsePositiveDataList.DEPRECATED_ACCEPTED_TYPE); + } + + private void validateVersion(ValidationContext context, FalsePositiveDataList target) { + context.addErrors(apiVersionValidation.validate(target.getApiVersion())); + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataService.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java similarity index 55% rename from sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataService.java rename to sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java index 951da73c95..3a4ef42ebe 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataService.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataService.java @@ -9,7 +9,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.mercedesbenz.sechub.domain.scan.ScanAssertService; @@ -22,43 +21,53 @@ import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; @Service -public class FalsePositiveJobDataService { +public class FalsePositiveDataService { - private static final ScanProjectConfigID CONFIG_ID = ScanProjectConfigID.FALSE_POSITIVE_CONFIGURATION; - - private static final Logger LOG = LoggerFactory.getLogger(FalsePositiveJobDataService.class); - - @Autowired - ScanReportRepository scanReportRepository; - - @Autowired - UserInputAssertion userInputAssertion; - - @Autowired - ScanProjectConfigService configService; - - @Autowired - FalsePositiveJobDataListValidation falsePositiveJobDataListValidation; + private static final String EMPTY_JSON = "{}"; - @Autowired - FalsePositiveJobDataConfigMerger merger; - - @Autowired - UserContextService userContextService; - - @Autowired - ScanAssertService scanAssertService; + private static final ScanProjectConfigID CONFIG_ID = ScanProjectConfigID.FALSE_POSITIVE_CONFIGURATION; - @Autowired - AuditLogService auditLogService; + private static final Logger LOG = LoggerFactory.getLogger(FalsePositiveDataService.class); + + private final ScanReportRepository scanReportRepository; + private final UserInputAssertion userInputAssertion; + private final ScanProjectConfigService configService; + private final FalsePositiveDataListValidation falsePositiveDataListValidation; + private final FalsePositiveDataConfigMerger merger; + private final UserContextService userContextService; + private final ScanAssertService scanAssertService; + private final AuditLogService auditLogService; + + /* @formatter:off */ + public FalsePositiveDataService(ScanReportRepository scanReportRepository, + UserInputAssertion userInputAssertion, + ScanProjectConfigService configService, + FalsePositiveDataListValidation falsePositiveDataListValidation, + FalsePositiveDataConfigMerger merger, + UserContextService userContextService, + ScanAssertService scanAssertService, + AuditLogService auditLogService) { + + this.scanReportRepository = scanReportRepository; + this.userInputAssertion = userInputAssertion; + this.configService = configService; + this.falsePositiveDataListValidation = falsePositiveDataListValidation; + this.merger = merger; + this.userContextService = userContextService; + this.scanAssertService = scanAssertService; + this.auditLogService = auditLogService; + /* @formatter:on */ + } - public void addFalsePositives(String projectId, FalsePositiveJobDataList data) { + public void addFalsePositives(String projectId, FalsePositiveDataList data) { validateUserInputAndProjectAccess(projectId, data); - auditLogService.log("triggers add of {} false postive entries to project {}", data.getJobData().size(), projectId); + int numberOfEntries = data.getJobData().size() + data.getProjectData().size(); + + auditLogService.log("triggers add or update of {} false postive entries to project {}", numberOfEntries, projectId); - if (data.getJobData().isEmpty()) { - LOG.debug("User false positive job data list has no entries - so skip further steps"); + if (data.getJobData().isEmpty() && data.getProjectData().isEmpty()) { + LOG.debug("User false positive data list has no entries - so skip further steps"); return; } @@ -88,6 +97,22 @@ public void removeFalsePositive(String projectId, UUID jobUUID, int findingId) { } + public void removeFalsePositiveByProjectDataId(String projectId, String id) { + validateProjectIdAndProjectAccess(projectId); + + auditLogService.log("triggers remove of false positive project data entry from project {}: id={}", projectId, id); + + FalsePositiveProjectConfiguration config = fetchOrCreateConfiguration(projectId); + FalsePositiveProjectData projectDataToRemove = new FalsePositiveProjectData(); + projectDataToRemove.setId(id); + + merger.removeProjectDataFromConfig(config, projectDataToRemove); + + /* update configuration */ + configService.set(projectId, CONFIG_ID, config.toJSON()); + + } + public FalsePositiveProjectConfiguration fetchFalsePositivesProjectConfiguration(String projectId) { validateProjectIdAndProjectAccess(projectId); @@ -96,9 +121,9 @@ public FalsePositiveProjectConfiguration fetchFalsePositivesProjectConfiguration return config; } - private void validateUserInputAndProjectAccess(String projectId, FalsePositiveJobDataList data) { + private void validateUserInputAndProjectAccess(String projectId, FalsePositiveDataList data) { validateProjectIdAndProjectAccess(projectId); - assertValid(data, falsePositiveJobDataListValidation); + assertValid(data, falsePositiveDataListValidation); } private void validateProjectIdAndProjectAccess(String projectId) { @@ -106,27 +131,32 @@ private void validateProjectIdAndProjectAccess(String projectId) { scanAssertService.assertUserHasAccessToProject(projectId); } - private void addJobDataListToConfiguration(FalsePositiveProjectConfiguration config, FalsePositiveJobDataList jobDataList) { - List list = jobDataList.getJobData(); + private void addJobDataListToConfiguration(FalsePositiveProjectConfiguration config, FalsePositiveDataList dataList) { + List jobDataList = dataList.getJobData(); /* * Reason for sorting: we want to load reports only one time, so sort by report * job UUID is necessary for method "fetchReportIfNotAlreadyLoaded" */ - list.sort(Comparator.comparing(FalsePositiveJobData::getJobUUID)); + jobDataList.sort(Comparator.comparing(FalsePositiveJobData::getJobUUID)); ScanSecHubReport currentReport = null; - for (FalsePositiveJobData data : list) { - if (merger.isFalsePositiveEntryAlreadyExisting(config, data)) { - LOG.debug("Skip processing because FP already defined: {}", data); + for (FalsePositiveJobData jobData : jobDataList) { + if (merger.isFalsePositiveEntryAlreadyExisting(config, jobData)) { + LOG.debug("Skip processing because FP already defined: {}", jobData); continue; } - UUID jobUUID = data.getJobUUID(); + UUID jobUUID = jobData.getJobUUID(); currentReport = fetchReportIfNotAlreadyLoaded(jobUUID, currentReport); - merger.addJobDataWithMetaDataToConfig(currentReport, config, data, userContextService.getUserId()); + merger.addJobDataWithMetaDataToConfig(currentReport, config, jobData, userContextService.getUserId()); + } + + List projectDataList = dataList.getProjectData(); + for (FalsePositiveProjectData projectData : projectDataList) { + merger.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData, userContextService.getUserId()); } } @@ -145,7 +175,9 @@ private ScanSecHubReport fetchReportIfNotAlreadyLoaded(UUID jobUUID, ScanSecHubR } private FalsePositiveProjectConfiguration fetchOrCreateConfiguration(String projectId) { - ScanProjectConfig projectConfig = configService.getOrCreate(projectId, CONFIG_ID, false, "{}"); // access check unnecessary, already done + ScanProjectConfig projectConfig = configService.getOrCreate(projectId, CONFIG_ID, false, EMPTY_JSON); // access check + // unnecessary, + // already done FalsePositiveProjectConfiguration falsePositiveConfiguration = FalsePositiveProjectConfiguration.fromJSONString(projectConfig.getData()); return falsePositiveConfiguration; diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveEntry.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveEntry.java index cb4d7877f4..57b0608ecd 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveEntry.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveEntry.java @@ -12,6 +12,7 @@ public class FalsePositiveEntry { public static final String PROPERTY_AUTHOR = "author"; public static final String PROPERTY_CREATED = "created"; public static final String PROPERTY_METADATA = "metaData"; + public static final String PROPERTY_PROJECTDATA = "projectData"; private FalsePositiveJobData jobData; @@ -19,6 +20,8 @@ public class FalsePositiveEntry { private FalsePositiveMetaData metaData; + private FalsePositiveProjectData projectData; + private Date created = new Date(); // we use initial now @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") @@ -38,6 +41,14 @@ public FalsePositiveJobData getJobData() { return jobData; } + public void setProjectData(FalsePositiveProjectData projectData) { + this.projectData = projectData; + } + + public FalsePositiveProjectData getProjectData() { + return projectData; + } + public void setAuthor(String author) { this.author = author; } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImpl.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImpl.java deleted file mode 100644 index 2cfa3c7dc6..0000000000 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImpl.java +++ /dev/null @@ -1,90 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan.project; - -import java.util.List; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import com.mercedesbenz.sechub.sharedkernel.validation.AbstractValidation; -import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidation; -import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidationFactory; -import com.mercedesbenz.sechub.sharedkernel.validation.ValidationContext; -import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; - -import jakarta.annotation.PostConstruct; - -@Component -public class FalsePositiveJobDataListValidationImpl extends AbstractValidation implements FalsePositiveJobDataListValidation { - - @Autowired - ApiVersionValidationFactory apiVersionValidationFactory; - - private ApiVersionValidation apiVersionValidation; - - @Autowired - FalsePositiveJobDataValidation falsePositiveJobDataValidation; - - @PostConstruct - void postConstruct() { - apiVersionValidation = apiVersionValidationFactory.createValidationAccepting("1.0"); - } - - @Override - protected void setup(AbstractValidation.ValidationConfig config) { - config.minLength = 0; // empty list is also accepted - config.maxLength = 500; // we allow maximum 500 entries in one list - } - - @Override - protected String getValidatorName() { - return "false positive list validation"; - } - - @Override - protected void validate(ValidationContext context) { - validateNotNull(context); - FalsePositiveJobDataList target = getObjectToValidate(context); - - validateVersion(context, target); - if (context.isInValid()) { - return; - } - - validateType(context, target); - if (context.isInValid()) { - return; - } - - validateJobData(context, target); - - } - - private void validateJobData(ValidationContext context, FalsePositiveJobDataList target) { - List jobDataList = target.getJobData(); - validateNotNull(context, jobDataList, "jobDataList"); - - validateMinSize(context, jobDataList, getConfig().minLength, "jobDataList"); - validateMaxSize(context, jobDataList, getConfig().maxLength, "jobDataList"); - - if (context.isInValid()) { - return; - } - - for (FalsePositiveJobData jobData : jobDataList) { - ValidationResult resultForJobData = falsePositiveJobDataValidation.validate(jobData); - if (!resultForJobData.isValid()) { - context.addErrors(resultForJobData); - } - } - } - - private void validateType(ValidationContext context, FalsePositiveJobDataList target) { - validateContainsExpectedOnly(context, "given type not known", target.getType(), FalsePositiveJobDataList.ACCEPTED_TYPE); - } - - private void validateVersion(ValidationContext context, FalsePositiveJobDataList target) { - context.addErrors(apiVersionValidation.validate(target.getApiVersion())); - } - -} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectData.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectData.java new file mode 100644 index 0000000000..6cd836b09f --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectData.java @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import java.util.Objects; + +public class FalsePositiveProjectData { + + public static final String PROPERTY_ID = "id"; + public static final String PROPERTY_WEBSCAN = "webScan"; + public static final String PROPERTY_COMMENT = "comment"; + + private String id; + private WebscanFalsePositiveProjectData webScan; + private String comment; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public WebscanFalsePositiveProjectData getWebScan() { + return webScan; + } + + public void setWebScan(WebscanFalsePositiveProjectData webScan) { + this.webScan = webScan; + } + + public String getComment() { + return comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + @Override + public String toString() { + return "FalsePositiveProjectData [id=%s, webScan=%s, comment=%s]".formatted(id, webScan, comment); + } + + @Override + public int hashCode() { + return Objects.hash(comment, id, webScan); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + FalsePositiveProjectData other = (FalsePositiveProjectData) obj; + return Objects.equals(comment, other.comment) && Objects.equals(id, other.id) && Objects.equals(webScan, other.webScan); + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidation.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidation.java new file mode 100644 index 0000000000..bee745646b --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidation.java @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import com.mercedesbenz.sechub.sharedkernel.validation.Validation; + +public interface FalsePositiveProjectDataIdValidation extends Validation { + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImpl.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImpl.java new file mode 100644 index 0000000000..ebb1ace1eb --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImpl.java @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.validation.AbstractSimpleStringValidation; +import com.mercedesbenz.sechub.sharedkernel.validation.AbstractValidation; +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationContext; + +@Component +public class FalsePositiveProjectDataIdValidationImpl extends AbstractSimpleStringValidation implements FalsePositiveProjectDataIdValidation { + + private static final String VALIDATOR_NAME = "false positive project data id validation"; + private static final char UNDERSCORE = '_'; + private static final char HYPHEN = '-'; + private static final int PROJECT_DATA_ID_MIN_SIZE = 1; + private static final int PROJECT_DATA_ID_MAX_SIZE = 100; + + @Override + protected void setup(AbstractValidation.ValidationConfig config) { + config.maxLength = PROJECT_DATA_ID_MAX_SIZE; // we allow maximum 100 chars for ids + config.minLength = PROJECT_DATA_ID_MIN_SIZE; // we allow minimum 1 char for ids, since it is mandatory + } + + @Override + protected void validate(ValidationContext context) { + validateNotNull(context); + validateLength(context); + validateOnlyAlphabeticDigitOrAllowedParts(context, HYPHEN, UNDERSCORE); + } + + @Override + protected String getValidatorName() { + return VALIDATOR_NAME; + } +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidation.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidation.java new file mode 100644 index 0000000000..c4a3d7f8d1 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidation.java @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import com.mercedesbenz.sechub.sharedkernel.validation.Validation; + +public interface FalsePositiveProjectDataValidation extends Validation { + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImpl.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImpl.java new file mode 100644 index 0000000000..74a57c9ff1 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImpl.java @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.validation.AbstractValidation; +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationContext; + +@Component +public class FalsePositiveProjectDataValidationImpl extends AbstractValidation implements FalsePositiveProjectDataValidation { + + private static final String VALIDATOR_NAME = "false positive project data validation"; + + private static final int MAXIMUM_CHARS_FOR_COMMENTS = 500; + + private final FalsePositiveProjectDataIdValidation idValidation; + private final WebscanFalsePositiveProjectDataValidation webscanValidation; + + public FalsePositiveProjectDataValidationImpl(FalsePositiveProjectDataIdValidation idValidation, + WebscanFalsePositiveProjectDataValidation webscanValidation) { + this.idValidation = idValidation; + this.webscanValidation = webscanValidation; + + } + + @Override + protected void setup(AbstractValidation.ValidationConfig config) { + config.maxLength = MAXIMUM_CHARS_FOR_COMMENTS; + } + + @Override + protected void validate(ValidationContext context) { + validateNotNull(context); + + if (context.isInValid()) { + return; + } + + FalsePositiveProjectData target = getObjectToValidate(context); + + String commentName = "%s.%s".formatted(FalsePositiveDataList.PROPERTY_PROJECTDATA, FalsePositiveProjectData.PROPERTY_COMMENT); + + validateMaxLength(context, target.getComment(), getConfig().maxLength, commentName); + + context.addErrors(idValidation.validate(target.getId())); + context.addErrors(webscanValidation.validate(target.getWebScan())); + } + + @Override + protected String getValidatorName() { + return VALIDATOR_NAME; + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveRestController.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveRestController.java index a67ad05c86..3118b813ab 100644 --- a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveRestController.java +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveRestController.java @@ -5,19 +5,23 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import com.mercedesbenz.sechub.sharedkernel.APIConstants; import com.mercedesbenz.sechub.sharedkernel.RoleConstants; import com.mercedesbenz.sechub.sharedkernel.Step; import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserFetchesFalsePositiveConfigurationOfProject; -import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserMarksFalsePositivesForJob; -import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserUnmarksFalsePositives; +import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserMarksFalsePositives; +import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserUnmarksFalsePositiveByJobData; +import com.mercedesbenz.sechub.sharedkernel.usecases.user.execute.UseCaseUserUnmarksFalsePositiveByProjectData; import jakarta.annotation.security.RolesAllowed; @@ -34,22 +38,22 @@ public class FalsePositiveRestController { @Autowired - private FalsePositiveJobDataService falsePositiveJobDataService; + private FalsePositiveDataService falsePositiveDataService; /* @formatter:off */ - @UseCaseUserMarksFalsePositivesForJob(@Step(number=1,name="REST API call to define false positives by JSON data containing identifiers for existing job",needsRestDoc=true)) + @UseCaseUserMarksFalsePositives(@Step(number=1,name="REST API call to define false positives by JSON data containing identifiers for existing jobs or false positive project data",needsRestDoc=true)) @RequestMapping(path = "/false-positives", method = RequestMethod.PUT, produces= {MediaType.APPLICATION_JSON_VALUE}) - public void addFalsePositivesByJobData( + public void addFalsePositiveData( @PathVariable("projectId") String projectId, - @RequestBody FalsePositiveJobDataList data + @RequestBody FalsePositiveDataList data ) { /* @formatter:on */ - falsePositiveJobDataService.addFalsePositives(projectId, data); + falsePositiveDataService.addFalsePositives(projectId, data); } /* @formatter:off */ - @UseCaseUserUnmarksFalsePositives(@Step(number=1,name="REST API call to remove existing false positive definition",needsRestDoc=true)) + @UseCaseUserUnmarksFalsePositiveByJobData(@Step(number=1,name="REST API call to remove existing false positive definition",needsRestDoc=true)) @RequestMapping(path = "/false-positive/{jobUUID}/{findingId}", method = RequestMethod.DELETE) public void removeFalsePositiveFromProjectByJobUUIDAndFindingId( @PathVariable("projectId") String projectId, @@ -57,7 +61,20 @@ public void removeFalsePositiveFromProjectByJobUUIDAndFindingId( @PathVariable("findingId") int findingId ) { /* @formatter:on */ - falsePositiveJobDataService.removeFalsePositive(projectId, jobUUID, findingId); + falsePositiveDataService.removeFalsePositive(projectId, jobUUID, findingId); + + } + + /* @formatter:off */ + @UseCaseUserUnmarksFalsePositiveByProjectData(@Step(number=1,name="REST API call to remove existing false positive project data definition by id",needsRestDoc=true)) + @DeleteMapping(path = "/false-positive/project-data/{id}") + @ResponseStatus(HttpStatus.NO_CONTENT) + public void removeFalsePositiveFromProjectByProjectDataId( + @PathVariable("projectId") String projectId, + @PathVariable("id") String id + ) { + /* @formatter:on */ + falsePositiveDataService.removeFalsePositiveByProjectDataId(projectId, id); } @@ -68,7 +85,7 @@ public FalsePositiveProjectConfiguration fetchFalsePositivesProjectConfiguration @PathVariable("projectId") String projectId ) { /* @formatter:on */ - return falsePositiveJobDataService.fetchFalsePositivesProjectConfiguration(projectId); + return falsePositiveDataService.fetchFalsePositivesProjectConfiguration(projectId); } diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ProjectData.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ProjectData.java new file mode 100644 index 0000000000..9287de8bd7 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/ProjectData.java @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +public interface ProjectData { + +} 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 new file mode 100644 index 0000000000..69904361c1 --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectData.java @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import java.util.List; +import java.util.Objects; + +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_METHODS = "methods"; + + private Integer cweId; + private List ports; + private List protocols; + private List urlPathPatterns; + private List hostPatterns; + private List methods; + + public Integer getCweId() { + return cweId; + } + + public void setCweId(Integer cweId) { + this.cweId = cweId; + } + + public List getPorts() { + return ports; + } + + 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 List getMethods() { + return methods; + } + + public void setMethods(List methods) { + this.methods = methods; + } + + @Override + public String toString() { + return "WebscanFalsePositiveProjectData [cweId=" + cweId + ", ports=" + ports + ", protocols=" + protocols + ", urlPatterns=" + urlPathPatterns + + ", servers=" + hostPatterns + ", methods=" + methods + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(cweId, methods, ports, protocols, hostPatterns, urlPathPatterns); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + 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); + } + +} diff --git a/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidation.java b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidation.java new file mode 100644 index 0000000000..f217fbaa5b --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidation.java @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import com.mercedesbenz.sechub.sharedkernel.validation.Validation; + +public interface WebscanFalsePositiveProjectDataValidation extends Validation { + +} 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 new file mode 100644 index 0000000000..75bcbca76f --- /dev/null +++ b/sechub-scan/src/main/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImpl.java @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import java.util.List; +import java.util.regex.Pattern; + +import org.springframework.stereotype.Component; + +import com.mercedesbenz.sechub.sharedkernel.validation.AbstractValidation; +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationContext; + +@Component +public class WebscanFalsePositiveProjectDataValidationImpl extends AbstractValidation + 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 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 = { "/" }; + + @Override + protected void setup(AbstractValidation.ValidationConfig config) { + config.maxLength = WEBSCAN_PROJECT_DATA_LIST_ENTRY_MAX_SIZE; // we allow maximum 300 chars for list entries + } + + @Override + protected String getValidatorName() { + return VALIDATOR_NAME; + } + + @Override + protected void validate(ValidationContext context) { + WebscanFalsePositiveProjectData webScan = getObjectToValidate(context); + if (webScan == null) { + return; + } + /* validate mandatory parts */ + validateCweId(context, webScan.getCweId()); + validateHostPatterns(context, webScan.getHostPatterns()); + validateUrlPathPatterns(context, webScan.getUrlPathPatterns()); + + /* validate optional parts */ + validateMethods(context, webScan.getMethods()); + validatePorts(context, webScan.getPorts()); + validateProtocols(context, webScan.getProtocols()); + } + + private void validateCweId(ValidationContext context, Integer cweId) { + if (cweId == null || cweId >= 0) { + return; + } + String name = "%s.%s".formatted(FalsePositiveProjectData.PROPERTY_WEBSCAN, WebscanFalsePositiveProjectData.PROPERTY_CWEID); + context.addError(getValidatorName(), + ": The value for '%s' must not be negative. Do not specify any CWE if the targeted finding has none or specify the correct value." + .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)); + 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; + } + 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()) { + return; + } + validateSize(context, list, name); + } + + private void validateSize(ValidationContext context, List list, String name) { + validateMaxSize(context, list, WEBSCAN_PROJECT_DATA_LIST_MAX_SIZE, name); + + for (String entry : list) { + validateMaxLength(context, entry, WEBSCAN_PROJECT_DATA_LIST_ENTRY_MAX_SIZE, ": Entry: '%s' inside '%s'".formatted(entry, 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 new file mode 100644 index 0000000000..2aa7c91412 --- /dev/null +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataConfigMergerTest.java @@ -0,0 +1,458 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.commons.model.SecHubFinding; +import com.mercedesbenz.sechub.commons.model.Severity; +import com.mercedesbenz.sechub.domain.scan.ScanDomainTestFileSupport; +import com.mercedesbenz.sechub.domain.scan.report.ScanSecHubReport; + +public class FalsePositiveDataConfigMergerTest { + + private static final String TEST_AUTHOR = "author1"; + private FalsePositiveDataConfigMerger toTest; + + private FalsePositiveProjectConfiguration config; + private FalsePositiveMetaDataFactory metaDataFactory; + + @BeforeEach + void beforeEach() throws Exception { + toTest = new FalsePositiveDataConfigMerger(); + metaDataFactory = mock(FalsePositiveMetaDataFactory.class); + + toTest.metaDataFactory = metaDataFactory; + config = new FalsePositiveProjectConfiguration(); + + } + + @Test + void sanity_check_for_JSON_example_data() { + /* execute */ + ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); + SecHubFinding secHubFinding = scanSecHubReport.getResult().getFindings().get(1); + assertEquals(Severity.MEDIUM, secHubFinding.getSeverity()); + Integer cweId = secHubFinding.getCweId(); + assertEquals(Integer.valueOf(1), cweId); + } + + @Test + void report_example1_add_one_jobdata_results_in_one_entry_in_config() { + /* prepare */ + UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); + + ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); + + FalsePositiveJobData falsePositiveJobData = new FalsePositiveJobData(); + falsePositiveJobData.setComment("comment1"); + falsePositiveJobData.setFindingId(2); + falsePositiveJobData.setJobUUID(jobUUID); + + FalsePositiveMetaData metaDataCreatedByFactory = new FalsePositiveMetaData(); + when(metaDataFactory.createMetaData(any())).thenReturn(metaDataCreatedByFactory); + + /* execute */ + toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData, TEST_AUTHOR); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertNotNull(falsePositives); + assertEquals(1, falsePositives.size()); + + FalsePositiveEntry fp1 = falsePositives.iterator().next(); + + // check given job data contained + FalsePositiveJobData jobData = fp1.getJobData(); + assertEquals("comment1", jobData.getComment()); + assertEquals(2, jobData.getFindingId()); + assertEquals(jobUUID, jobData.getJobUUID()); + assertEquals(TEST_AUTHOR, fp1.getAuthor()); + + // check meta data created by factory fetched and added + FalsePositiveMetaData metaData = fp1.getMetaData(); + assertSame(metaDataCreatedByFactory, metaData); + + } + + @Test + void report_example1_add_job_data_already_contained_does_not_change() { + /* prepare */ + UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); + + ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); + + FalsePositiveJobData falsePositiveJobData = new FalsePositiveJobData(); + falsePositiveJobData.setComment("comment1"); + falsePositiveJobData.setFindingId(2); + falsePositiveJobData.setJobUUID(jobUUID); + + // first call does setup configuration + toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData, TEST_AUTHOR); + + // now we change the false positive job data + FalsePositiveJobData falsePositiveJobData2 = new FalsePositiveJobData(); + falsePositiveJobData2.setComment("comment2"); + falsePositiveJobData2.setFindingId(2); + falsePositiveJobData2.setJobUUID(jobUUID); + + /* execute */ + toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData2, TEST_AUTHOR); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertNotNull(falsePositives); + assertEquals(1, falsePositives.size()); + + FalsePositiveEntry fp1 = falsePositives.iterator().next(); + + // check given job data contained + FalsePositiveJobData jobData = fp1.getJobData(); + assertEquals("comment1", jobData.getComment()); // we still have comment1, so no changes + + } + + @Test + void report_example1_REMOVE_job_data_contained_does_remove_it() { + /* prepare */ + UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); + + ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); + + FalsePositiveJobData falsePositiveJobData2 = new FalsePositiveJobData(); + falsePositiveJobData2.setComment("comment2"); + falsePositiveJobData2.setFindingId(2); + falsePositiveJobData2.setJobUUID(jobUUID); + + FalsePositiveJobData falsePositiveJobData3 = new FalsePositiveJobData(); + falsePositiveJobData3.setComment("comment3"); + falsePositiveJobData3.setFindingId(3); + falsePositiveJobData3.setJobUUID(jobUUID); + + FalsePositiveJobData falsePositiveJobData4 = new FalsePositiveJobData(); + falsePositiveJobData4.setComment("comment4"); + falsePositiveJobData4.setFindingId(4); + falsePositiveJobData4.setJobUUID(jobUUID); + + toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData2, TEST_AUTHOR); + toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData3, TEST_AUTHOR); + toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData4, TEST_AUTHOR); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertNotNull(falsePositives); + assertEquals(3, falsePositives.size()); + + /* execute */ + // now we remove the false positive job data + FalsePositiveJobData falsePositiveDataToRemove = new FalsePositiveJobData(); + falsePositiveDataToRemove.setFindingId(3); + falsePositiveDataToRemove.setJobUUID(jobUUID); + + toTest.removeJobDataWithMetaDataFromConfig(config, falsePositiveDataToRemove); + + /* test */ + falsePositives = config.getFalsePositives(); + assertNotNull(falsePositives); + assertEquals(2, falsePositives.size()); + + Iterator iterator = falsePositives.iterator(); + FalsePositiveEntry fp2 = iterator.next(); + FalsePositiveEntry fp4 = iterator.next(); + + FalsePositiveJobData jd2 = fp2.getJobData(); + FalsePositiveJobData jd4 = fp4.getJobData(); + assertEquals(2, jd2.getFindingId()); + assertEquals(4, jd4.getFindingId()); + + } + + @Test + 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/*")); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setId(id); + projectData.setComment("comment1"); + projectData.setWebScan(webScan); + + /* execute */ + toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData, TEST_AUTHOR); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertEquals(1, falsePositives.size()); + + FalsePositiveEntry falsePositiveEntry = falsePositives.get(0); + assertNull(falsePositiveEntry.getJobData()); + assertNull(falsePositiveEntry.getMetaData()); + + assertEquals(TEST_AUTHOR, falsePositiveEntry.getAuthor()); + assertEquals(projectData, falsePositiveEntry.getProjectData()); + } + + @Test + void add_one_project_data_entry_which_already_exists_results_in_one_updated_entry_in_config() { + /* prepare */ + String id = "unique-id"; + WebscanFalsePositiveProjectData webScan1 = new WebscanFalsePositiveProjectData(); + webScan1.setHostPatterns(List.of("*.host.com")); + webScan1.setUrlPathPatterns(List.of("/rest/api/project/*")); + + FalsePositiveProjectData projectData1 = new FalsePositiveProjectData(); + projectData1.setId(id); + projectData1.setComment("comment1"); + projectData1.setWebScan(webScan1); + + FalsePositiveEntry falsePositiveEntry = new FalsePositiveEntry(); + falsePositiveEntry.setProjectData(projectData1); + falsePositiveEntry.setAuthor(TEST_AUTHOR); + + // add projectData with id="unique-id" to config + 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/")); + + FalsePositiveProjectData projectData2 = new FalsePositiveProjectData(); + projectData2.setId(id); + projectData2.setComment("comment2"); + projectData2.setWebScan(webScan2); + + /* execute */ + toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData2, TEST_AUTHOR); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertEquals(1, falsePositives.size()); + + FalsePositiveEntry updatedFalsePositiveEntry = falsePositives.get(0); + assertNull(updatedFalsePositiveEntry.getJobData()); + assertNull(updatedFalsePositiveEntry.getMetaData()); + + assertEquals(TEST_AUTHOR, updatedFalsePositiveEntry.getAuthor()); + assertEquals(projectData2, updatedFalsePositiveEntry.getProjectData()); + } + + @Test + void add_a_second_project_data_entry_which_has_an_unique_id_results_in_two_entries_in_config() { + /* prepare */ + String id1 = "unique-id"; + WebscanFalsePositiveProjectData webScan1 = new WebscanFalsePositiveProjectData(); + webScan1.setHostPatterns(List.of("*.host.com")); + webScan1.setUrlPathPatterns(List.of("/rest/api/project/*")); + + FalsePositiveProjectData projectData1 = new FalsePositiveProjectData(); + projectData1.setId(id1); + projectData1.setComment("comment1"); + projectData1.setWebScan(webScan1); + + FalsePositiveEntry falsePositiveEntry = new FalsePositiveEntry(); + falsePositiveEntry.setProjectData(projectData1); + falsePositiveEntry.setAuthor(TEST_AUTHOR); + + // add projectData with id="unique-id" to config + config.getFalsePositives().add(falsePositiveEntry); + + 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/")); + + FalsePositiveProjectData projectData2 = new FalsePositiveProjectData(); + projectData2.setId(id2); + projectData2.setComment("comment2"); + projectData2.setWebScan(webScan2); + + /* execute */ + toTest.addFalsePositiveProjectDataEntryOrUpdateExisting(config, projectData2, TEST_AUTHOR); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertEquals(2, falsePositives.size()); + + FalsePositiveEntry firstEntry = falsePositives.get(0); + assertNull(firstEntry.getJobData()); + assertNull(firstEntry.getMetaData()); + + assertEquals(TEST_AUTHOR, firstEntry.getAuthor()); + assertEquals(projectData1, firstEntry.getProjectData()); + + FalsePositiveEntry secondEntry = falsePositives.get(1); + assertNull(secondEntry.getJobData()); + assertNull(secondEntry.getMetaData()); + + assertEquals(TEST_AUTHOR, secondEntry.getAuthor()); + assertEquals(projectData2, secondEntry.getProjectData()); + } + + @Test + 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/*")); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setId(id); + projectData.setComment("comment1"); + projectData.setWebScan(webScan); + + FalsePositiveEntry falsePositiveEntry = new FalsePositiveEntry(); + falsePositiveEntry.setProjectData(projectData); + falsePositiveEntry.setAuthor(TEST_AUTHOR); + + // add projectData with id="unique-id" to config + config.getFalsePositives().add(falsePositiveEntry); + + FalsePositiveProjectData projectDataToRemove = new FalsePositiveProjectData(); + projectDataToRemove.setId(id); + + /* execute */ + toTest.removeProjectDataFromConfig(config, projectDataToRemove); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertEquals(0, falsePositives.size()); + } + + @Test + void remove_one_project_data_entry_which_does_not_exist_results_in_unchanged_config() { + /* prepare */ + String id = "unique-id"; + WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); + webScan.setHostPatterns(List.of("*.host.com")); + webScan.setUrlPathPatterns(List.of("/rest/api/project/*")); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setId(id); + projectData.setComment("comment1"); + projectData.setWebScan(webScan); + + FalsePositiveEntry falsePositiveEntry = new FalsePositiveEntry(); + falsePositiveEntry.setProjectData(projectData); + falsePositiveEntry.setAuthor(TEST_AUTHOR); + + // add projectData with id="unique-id" to config + config.getFalsePositives().add(falsePositiveEntry); + + FalsePositiveProjectData projectDataToRemove = new FalsePositiveProjectData(); + projectDataToRemove.setId("other-unique-id"); + + /* execute */ + toTest.removeProjectDataFromConfig(config, projectDataToRemove); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertEquals(1, falsePositives.size()); + + FalsePositiveEntry unchangedFalsePositiveEntry = falsePositives.get(0); + assertNull(unchangedFalsePositiveEntry.getJobData()); + assertNull(unchangedFalsePositiveEntry.getMetaData()); + + assertEquals(TEST_AUTHOR, unchangedFalsePositiveEntry.getAuthor()); + assertEquals(projectData, unchangedFalsePositiveEntry.getProjectData()); + } + + @Test + void remove_one_project_data_entry_when_only_job_data_available_results_in_unchanged_config() { + /* prepare */ + UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); + + ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); + + FalsePositiveJobData falsePositiveJobData = new FalsePositiveJobData(); + falsePositiveJobData.setComment("comment1"); + falsePositiveJobData.setFindingId(2); + falsePositiveJobData.setJobUUID(jobUUID); + + FalsePositiveMetaData metaDataCreatedByFactory = new FalsePositiveMetaData(); + when(metaDataFactory.createMetaData(any())).thenReturn(metaDataCreatedByFactory); + + // first call does setup configuration + toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData, TEST_AUTHOR); + + FalsePositiveProjectData projectDataToRemove = new FalsePositiveProjectData(); + projectDataToRemove.setId("other-unique-id"); + + /* execute */ + toTest.removeProjectDataFromConfig(config, projectDataToRemove); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertEquals(1, falsePositives.size()); + + FalsePositiveEntry unchangedFalsePositiveEntry = falsePositives.get(0); + assertEquals(falsePositiveJobData, unchangedFalsePositiveEntry.getJobData()); + assertNotNull(unchangedFalsePositiveEntry.getMetaData()); + + assertEquals(TEST_AUTHOR, unchangedFalsePositiveEntry.getAuthor()); + assertEquals(null, unchangedFalsePositiveEntry.getProjectData()); + } + + @Test + void remove_one_job_data_entry_when_only_project_data_available_results_in_unchanged_config() { + /* prepare */ + String id = "unique-id"; + WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); + webScan.setHostPatterns(List.of("*.host.com")); + webScan.setUrlPathPatterns(List.of("/rest/api/project/*")); + + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setId(id); + projectData.setComment("comment1"); + projectData.setWebScan(webScan); + + FalsePositiveEntry falsePositiveEntry = new FalsePositiveEntry(); + falsePositiveEntry.setAuthor(TEST_AUTHOR); + falsePositiveEntry.setProjectData(projectData); + + // add projectData with id="unique-id" to config + config.getFalsePositives().add(falsePositiveEntry); + + UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); + int findingId = 2; + + FalsePositiveJobData falsePositiveJobData = new FalsePositiveJobData(); + falsePositiveJobData.setFindingId(findingId); + falsePositiveJobData.setJobUUID(jobUUID); + + /* execute */ + toTest.removeJobDataWithMetaDataFromConfig(config, falsePositiveJobData); + + /* test */ + List falsePositives = config.getFalsePositives(); + assertEquals(1, falsePositives.size()); + + FalsePositiveEntry falsePositiveEntryFromConfig = falsePositives.get(0); + assertNull(falsePositiveEntryFromConfig.getJobData()); + assertNull(falsePositiveEntryFromConfig.getMetaData()); + + assertEquals(TEST_AUTHOR, falsePositiveEntryFromConfig.getAuthor()); + assertEquals(projectData, falsePositiveEntryFromConfig.getProjectData()); + } + + private ScanSecHubReport loadScanReport(String path) { + String reportJSON = ScanDomainTestFileSupport.getTestfileSupport().loadTestFile(path); + return ScanSecHubReport.fromJSONString(reportJSON); + } + +} diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListTest.java similarity index 86% rename from sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListTest.java rename to sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListTest.java index c2a2e331f3..1b39a3e9a3 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListTest.java @@ -10,7 +10,7 @@ import com.mercedesbenz.sechub.domain.scan.ScanDomainTestFileSupport; -public class FalsePositiveJobDataListTest { +public class FalsePositiveDataListTest { @Test public void json_content_as_described_in_example_of_documentation() { @@ -19,10 +19,10 @@ public void json_content_as_described_in_example_of_documentation() { .loadTestFileFromRoot("/sechub-doc/src/docs/asciidoc/documents/shared/false-positives/false-positives-REST-API-content-example1.json"); /* execute */ - FalsePositiveJobDataList dataList = FalsePositiveJobDataList.fromString(json); + FalsePositiveDataList dataList = FalsePositiveDataList.fromString(json); /* test */ - assertEquals(FalsePositiveJobDataList.ACCEPTED_TYPE, dataList.getType()); + assertEquals(FalsePositiveDataList.ACCEPTED_TYPE, dataList.getType()); List jobData = dataList.getJobData(); assertEquals(2, jobData.size()); Iterator it = jobData.iterator(); diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImplTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImplTest.java new file mode 100644 index 0000000000..8392eec7f2 --- /dev/null +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataListValidationImplTest.java @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import static com.mercedesbenz.sechub.sharedkernel.validation.AssertValidation.assertValid; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +import com.mercedesbenz.sechub.sharedkernel.error.NotAcceptableException; +import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidation; +import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidationFactory; +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; + +class FalsePositiveDataListValidationImplTest { + + private static ValidationResult validResult = new ValidationResult(); + private static ValidationResult invalidResult = new ValidationResult(); + + private static final int MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES = 500; + private FalsePositiveDataListValidationImpl validationToTest; + private static final ApiVersionValidation apiVersionValidation = mock(); + private static final ApiVersionValidationFactory apiVersionValidationFactory = mock(); + private static final FalsePositiveJobDataValidation falsePositiveJobDataValidation = mock(); + private static final FalsePositiveProjectDataValidation falsePositiveProjectDataValidation = mock(); + + @BeforeAll + static void beforeAll() { + invalidResult.addError("error"); + assertTrue(validResult.isValid()); + assertFalse(invalidResult.isValid()); + } + + @BeforeEach + void beforeEach() { + /* @formatter:off */ + Mockito.reset(apiVersionValidation, + apiVersionValidationFactory, + falsePositiveJobDataValidation, + falsePositiveProjectDataValidation); + /* @formatter:on */ + + when(apiVersionValidationFactory.createValidationAccepting(any())).thenReturn(apiVersionValidation); + + /* @formatter:off */ + validationToTest = new FalsePositiveDataListValidationImpl(apiVersionValidationFactory, + falsePositiveJobDataValidation, + falsePositiveProjectDataValidation); + /* @formatter:on */ + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES }) + void job_data_list_with_valid_entries_is_accepted_with_this_size(int amountOfFalsePositiveEntries) { + /* prepare */ + when(falsePositiveJobDataValidation.validate(any())).thenReturn(validResult); + + FalsePositiveDataList list = createJobDataListWithEntries(amountOfFalsePositiveEntries); + + /* execute + test (no exception ) */ + assertValid(list, validationToTest); + } + + @ParameterizedTest + @ValueSource(ints = { 1, MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES }) + void job_data_list_with_invalid_entries_is_not_accepted_with_this_size(int amountOfFalsePositiveEntries) { + /* prepare */ + when(falsePositiveJobDataValidation.validate(any())).thenReturn(invalidResult); + + FalsePositiveDataList list = createJobDataListWithEntries(amountOfFalsePositiveEntries); + + /* execute + test (no exception ) */ + assertThrows(NotAcceptableException.class, () -> assertValid(list, validationToTest)); + } + + @ParameterizedTest + @ValueSource(ints = { MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES + 1 }) + void job_data_list_with_valid_entries_is_NOT_accepted_with_this_size(int amountOfFalsePositiveEntries) { + /* prepare */ + when(falsePositiveJobDataValidation.validate(any())).thenReturn(validResult); + + FalsePositiveDataList list = createJobDataListWithEntries(MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES + 1); + + /* execute + test (no exception ) */ + assertThrows(NotAcceptableException.class, () -> assertValid(list, validationToTest)); + } + + @ParameterizedTest + @ValueSource(ints = { 0, 1, MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES }) + void project_data_list_with_valid_entries_is_accepted_with_this_size(int amountOfFalsePositiveEntries) { + /* prepare */ + when(falsePositiveProjectDataValidation.validate(any())).thenReturn(validResult); + + FalsePositiveDataList list = createProjectDataListWithEntries(amountOfFalsePositiveEntries); + + /* execute + test (no exception ) */ + assertValid(list, validationToTest); + } + + @ParameterizedTest + @ValueSource(ints = { 1, MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES }) + void project_data_list_with_invalid_entries_is_not_accepted_with_this_size(int amountOfFalsePositiveEntries) { + /* prepare */ + when(falsePositiveProjectDataValidation.validate(any())).thenReturn(invalidResult); + + FalsePositiveDataList list = createProjectDataListWithEntries(amountOfFalsePositiveEntries); + + /* execute + test (no exception ) */ + assertThrows(NotAcceptableException.class, () -> assertValid(list, validationToTest)); + } + + @ParameterizedTest + @ValueSource(ints = { MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES + 1 }) + void project_data_list_with_valid_entries_is_NOT_accepted_with_this_size(int amountOfFalsePositiveEntries) { + /* prepare */ + when(falsePositiveProjectDataValidation.validate(any())).thenReturn(validResult); + + FalsePositiveDataList list = createProjectDataListWithEntries(MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES + 1); + + /* execute + test (no exception ) */ + assertThrows(NotAcceptableException.class, () -> assertValid(list, validationToTest)); + } + + @Test + void job_data_list_mixed_with_project_data_list_with_valid_entries_is_NOT_accepted_with_this_size() { + /* prepare */ + when(falsePositiveProjectDataValidation.validate(any())).thenReturn(validResult); + when(falsePositiveJobDataValidation.validate(any())).thenReturn(validResult); + + FalsePositiveDataList list = createJobDataListWithEntries(MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES); + list.getProjectData().add(new FalsePositiveProjectData()); + + /* execute + test (no exception ) */ + assertThrows(NotAcceptableException.class, () -> assertValid(list, validationToTest)); + } + + private FalsePositiveDataList createJobDataListWithEntries(int amountOfFalsePositiveEntries) { + FalsePositiveDataList list = new FalsePositiveDataList(); + for (int i = 0; i < amountOfFalsePositiveEntries; i++) { + FalsePositiveJobData data = new FalsePositiveJobData(); + list.getJobData().add(data); + } + /* internal sanity check for this data */ + assertEquals(amountOfFalsePositiveEntries, list.getJobData().size(), "sanity check failed - test data wrong!"); + return list; + } + + private FalsePositiveDataList createProjectDataListWithEntries(int amountOfFalsePositiveEntries) { + FalsePositiveDataList list = new FalsePositiveDataList(); + for (int i = 0; i < amountOfFalsePositiveEntries; i++) { + FalsePositiveProjectData data = new FalsePositiveProjectData(); + list.getProjectData().add(data); + } + /* internal sanity check for this data */ + assertEquals(amountOfFalsePositiveEntries, list.getProjectData().size(), "sanity check failed - test data wrong!"); + return list; + } + +} diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataServiceTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataServiceTest.java similarity index 52% rename from sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataServiceTest.java rename to sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataServiceTest.java index ff36d52f2d..fecdff9f58 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataServiceTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveDataServiceTest.java @@ -6,40 +6,47 @@ import org.junit.Before; import org.junit.Test; +import org.mockito.Mockito; import com.mercedesbenz.sechub.domain.scan.ScanAssertService; import com.mercedesbenz.sechub.sharedkernel.logging.AuditLogService; import com.mercedesbenz.sechub.sharedkernel.validation.UserInputAssertion; import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; -public class FalsePositiveJobDataServiceTest { +public class FalsePositiveDataServiceTest { private static final String PROJECT_ID = "testprojectId"; - private FalsePositiveJobDataService serviceToTest; - private FalsePositiveJobDataListValidation falsePositiveListValidation; - private ScanProjectConfigService configService; - private ScanAssertService scanAssertService; - private UserInputAssertion userInputAssertion; - private ScanProjectConfig config; - private AuditLogService auditLogService; + private FalsePositiveDataService serviceToTest; - @Before - public void before() { - serviceToTest = new FalsePositiveJobDataService(); + private static final FalsePositiveDataListValidation falsePositiveListValidation = mock(); - falsePositiveListValidation = mock(FalsePositiveJobDataListValidation.class); - configService = mock(ScanProjectConfigService.class); - userInputAssertion = mock(UserInputAssertion.class); - scanAssertService = mock(ScanAssertService.class); - auditLogService = mock(AuditLogService.class); + private static final ScanProjectConfigService configService = mock(); + private static final ScanAssertService scanAssertService = mock(); + private static final UserInputAssertion userInputAssertion = mock(); + private static final AuditLogService auditLogService = mock(); - serviceToTest.falsePositiveJobDataListValidation = falsePositiveListValidation; - serviceToTest.configService = configService; - serviceToTest.scanAssertService = scanAssertService; - serviceToTest.userInputAssertion = userInputAssertion; - serviceToTest.auditLogService = auditLogService; + private ScanProjectConfig config; - when(falsePositiveListValidation.validate(any(FalsePositiveJobDataList.class))).thenReturn(new ValidationResult()); + @Before + public void before() { + /* @formatter:off */ + Mockito.reset(userInputAssertion, + configService, + falsePositiveListValidation, + scanAssertService, + auditLogService); + + serviceToTest = new FalsePositiveDataService(null, + userInputAssertion, + configService, + falsePositiveListValidation, + null, + null, + scanAssertService, + auditLogService); + /* @formatter:on */ + + when(falsePositiveListValidation.validate(any(FalsePositiveDataList.class))).thenReturn(new ValidationResult()); /* we mock config service */ config = new ScanProjectConfig(); @@ -52,7 +59,7 @@ public void before() { @Test public void check_validations_are_triggered() { /* prepare */ - FalsePositiveJobDataList data = new FalsePositiveJobDataList(); + FalsePositiveDataList data = new FalsePositiveDataList(); /* execute */ serviceToTest.addFalsePositives(PROJECT_ID, data); @@ -60,7 +67,7 @@ public void check_validations_are_triggered() { /* test */ verify(userInputAssertion).assertIsValidProjectId(PROJECT_ID); verify(scanAssertService).assertUserHasAccessToProject(PROJECT_ID); - verify(falsePositiveListValidation).validate(any(FalsePositiveJobDataList.class)); + verify(falsePositiveListValidation).validate(any(FalsePositiveDataList.class)); } } diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataConfigMergerTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataConfigMergerTest.java deleted file mode 100644 index 691a18c29e..0000000000 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataConfigMergerTest.java +++ /dev/null @@ -1,183 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan.project; - -import static org.junit.Assert.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import java.util.Iterator; -import java.util.List; -import java.util.UUID; - -import org.junit.Before; -import org.junit.Test; - -import com.mercedesbenz.sechub.commons.model.SecHubFinding; -import com.mercedesbenz.sechub.commons.model.Severity; -import com.mercedesbenz.sechub.domain.scan.ScanDomainTestFileSupport; -import com.mercedesbenz.sechub.domain.scan.report.ScanSecHubReport; - -public class FalsePositiveJobDataConfigMergerTest { - - private static final String TEST_AUTHOR = "author1"; - private FalsePositiveJobDataConfigMerger toTest; - - private FalsePositiveProjectConfiguration config; - private FalsePositiveMetaDataFactory metaDataFactory; - - @Before - public void before() throws Exception { - toTest = new FalsePositiveJobDataConfigMerger(); - metaDataFactory = mock(FalsePositiveMetaDataFactory.class); - - toTest.metaDataFactory = metaDataFactory; - config = new FalsePositiveProjectConfiguration(); - - } - - @Test - public void sanity_check_for_JSON_example_data() { - /* execute */ - ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); - SecHubFinding secHubFinding = scanSecHubReport.getResult().getFindings().get(1); - assertEquals(Severity.MEDIUM, secHubFinding.getSeverity()); - Integer cweId = secHubFinding.getCweId(); - assertEquals(Integer.valueOf(1), cweId); - } - - @Test - public void report_example1_add_one_jobdata_results_in_one_entry_in_config() { - /* prepare */ - UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); - - ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); - - FalsePositiveJobData falsePositiveJobData = new FalsePositiveJobData(); - falsePositiveJobData.setComment("comment1"); - falsePositiveJobData.setFindingId(2); - falsePositiveJobData.setJobUUID(jobUUID); - - FalsePositiveMetaData metaDataCreatedByFactory = new FalsePositiveMetaData(); - when(metaDataFactory.createMetaData(any())).thenReturn(metaDataCreatedByFactory); - - /* execute */ - toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData, TEST_AUTHOR); - - /* test */ - List falsePositives = config.getFalsePositives(); - assertNotNull(falsePositives); - assertEquals(1, falsePositives.size()); - - FalsePositiveEntry fp1 = falsePositives.iterator().next(); - - // check given job data contained - FalsePositiveJobData jobData = fp1.getJobData(); - assertEquals("comment1", jobData.getComment()); - assertEquals(2, jobData.getFindingId()); - assertEquals(jobUUID, jobData.getJobUUID()); - assertEquals(TEST_AUTHOR, fp1.getAuthor()); - - // check meta data created by factory fetched and added - FalsePositiveMetaData metaData = fp1.getMetaData(); - assertSame(metaDataCreatedByFactory, metaData); - - } - - @Test - public void report_example1_add_job_data_already_contained_does_not_change() { - /* prepare */ - UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); - - ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); - - FalsePositiveJobData falsePositiveJobData = new FalsePositiveJobData(); - falsePositiveJobData.setComment("comment1"); - falsePositiveJobData.setFindingId(2); - falsePositiveJobData.setJobUUID(jobUUID); - - // first call does setup configuration - toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData, TEST_AUTHOR); - - // now we change the false positive job data - FalsePositiveJobData falsePositiveJobData2 = new FalsePositiveJobData(); - falsePositiveJobData2.setComment("comment2"); - falsePositiveJobData2.setFindingId(2); - falsePositiveJobData2.setJobUUID(jobUUID); - - /* execute */ - toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData2, TEST_AUTHOR); - - /* test */ - List falsePositives = config.getFalsePositives(); - assertNotNull(falsePositives); - assertEquals(1, falsePositives.size()); - - FalsePositiveEntry fp1 = falsePositives.iterator().next(); - - // check given job data contained - FalsePositiveJobData jobData = fp1.getJobData(); - assertEquals("comment1", jobData.getComment()); // we still have comment1, so no changes - - } - - @Test - public void report_example1_REMOVE_job_data_contained_does_remove_it() { - /* prepare */ - UUID jobUUID = UUID.fromString("f1d02a9d-5e1b-4f52-99e5-401854ccf936"); - - ScanSecHubReport scanSecHubReport = loadScanReport("sechub_result/sechub-report-example1-noscantype.json"); - - FalsePositiveJobData falsePositiveJobData2 = new FalsePositiveJobData(); - falsePositiveJobData2.setComment("comment2"); - falsePositiveJobData2.setFindingId(2); - falsePositiveJobData2.setJobUUID(jobUUID); - - FalsePositiveJobData falsePositiveJobData3 = new FalsePositiveJobData(); - falsePositiveJobData3.setComment("comment3"); - falsePositiveJobData3.setFindingId(3); - falsePositiveJobData3.setJobUUID(jobUUID); - - FalsePositiveJobData falsePositiveJobData4 = new FalsePositiveJobData(); - falsePositiveJobData4.setComment("comment4"); - falsePositiveJobData4.setFindingId(4); - falsePositiveJobData4.setJobUUID(jobUUID); - - toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData2, TEST_AUTHOR); - toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData3, TEST_AUTHOR); - toTest.addJobDataWithMetaDataToConfig(scanSecHubReport, config, falsePositiveJobData4, TEST_AUTHOR); - - /* test */ - List falsePositives = config.getFalsePositives(); - assertNotNull(falsePositives); - assertEquals(3, falsePositives.size()); - - /* execute */ - // now we remove the false positive job data - FalsePositiveJobData falsePositiveDataToRemove = new FalsePositiveJobData(); - falsePositiveDataToRemove.setFindingId(3); - falsePositiveDataToRemove.setJobUUID(jobUUID); - - toTest.removeJobDataWithMetaDataFromConfig(config, falsePositiveDataToRemove); - - /* test */ - falsePositives = config.getFalsePositives(); - assertNotNull(falsePositives); - assertEquals(2, falsePositives.size()); - - Iterator iterator = falsePositives.iterator(); - FalsePositiveEntry fp2 = iterator.next(); - FalsePositiveEntry fp4 = iterator.next(); - - FalsePositiveJobData jd2 = fp2.getJobData(); - FalsePositiveJobData jd4 = fp4.getJobData(); - assertEquals(2, jd2.getFindingId()); - assertEquals(4, jd4.getFindingId()); - - } - - private ScanSecHubReport loadScanReport(String path) { - String reportJSON = ScanDomainTestFileSupport.getTestfileSupport().loadTestFile(path); - return ScanSecHubReport.fromJSONString(reportJSON); - } - -} diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImplTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImplTest.java deleted file mode 100644 index 9741b7c1b7..0000000000 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveJobDataListValidationImplTest.java +++ /dev/null @@ -1,103 +0,0 @@ -// SPDX-License-Identifier: MIT -package com.mercedesbenz.sechub.domain.scan.project; - -import static com.mercedesbenz.sechub.sharedkernel.validation.AssertValidation.*; -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.*; - -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.ValueSource; - -import com.mercedesbenz.sechub.sharedkernel.error.NotAcceptableException; -import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidation; -import com.mercedesbenz.sechub.sharedkernel.validation.ApiVersionValidationFactory; -import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; - -class FalsePositiveJobDataListValidationImplTest { - - private static ValidationResult validResult = new ValidationResult(); - private static ValidationResult invalidResult = new ValidationResult(); - - private static final int MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES = 500; - private FalsePositiveJobDataListValidationImpl validationToTest; - private ApiVersionValidation apiVersionValidation; - private ApiVersionValidationFactory apiVersionValidationFactory; - private FalsePositiveJobDataValidation falsePositiveJobDataValidation; - - @BeforeAll - static void beforeAll() { - invalidResult.addError("error"); - assertTrue(validResult.isValid()); - assertFalse(invalidResult.isValid()); - } - - @BeforeEach - void beforeEach() { - apiVersionValidation = mock(ApiVersionValidation.class); - - apiVersionValidationFactory = mock(ApiVersionValidationFactory.class); - when(apiVersionValidationFactory.createValidationAccepting(any())).thenReturn(apiVersionValidation); - - falsePositiveJobDataValidation = mock(FalsePositiveJobDataValidation.class); - - validationToTest = new FalsePositiveJobDataListValidationImpl(); - validationToTest.apiVersionValidationFactory = apiVersionValidationFactory; - validationToTest.falsePositiveJobDataValidation = falsePositiveJobDataValidation; - - // call the post construct method like spring boot does - does some necessary - // intialization - validationToTest.postConstruct(); - - } - - @ParameterizedTest - @ValueSource(ints = { 0, 1, MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES }) - void list_with_valid_entries_is_accepted_with_this_size(int amountOfFalsePositiveEntries) { - /* prepare */ - when(falsePositiveJobDataValidation.validate(any())).thenReturn(validResult); - - FalsePositiveJobDataList list = createListWithEntries(amountOfFalsePositiveEntries); - - /* execute + test (no exception ) */ - assertValid(list, validationToTest); - } - - @ParameterizedTest - @ValueSource(ints = { 1, MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES }) - void list_with_invalid_entries_is_not_accepted_with_this_size(int amountOfFalsePositiveEntries) { - /* prepare */ - when(falsePositiveJobDataValidation.validate(any())).thenReturn(invalidResult); - - FalsePositiveJobDataList list = createListWithEntries(amountOfFalsePositiveEntries); - - /* execute + test (no exception ) */ - assertThrows(NotAcceptableException.class, () -> assertValid(list, validationToTest)); - } - - @ParameterizedTest - @ValueSource(ints = { MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES + 1 }) - void list_with_valid_entries_is_NOT_accepted_with_this_size(int amountOfFalsePositiveEntries) { - /* prepare */ - when(falsePositiveJobDataValidation.validate(any())).thenReturn(validResult); - - FalsePositiveJobDataList list = createListWithEntries(MAXIMUM_ACCEPTED_AMOUNT_OF_ENTRIES + 1); - - /* execute + test (no exception ) */ - assertThrows(NotAcceptableException.class, () -> assertValid(list, validationToTest)); - } - - private FalsePositiveJobDataList createListWithEntries(int amountOfFalsePositiveEntries) { - FalsePositiveJobDataList list = new FalsePositiveJobDataList(); - for (int i = 0; i < amountOfFalsePositiveEntries; i++) { - FalsePositiveJobData data = new FalsePositiveJobData(); - list.getJobData().add(data); - } - /* internal sanity check for this data */ - assertEquals(amountOfFalsePositiveEntries, list.getJobData().size(), "sanity check failed - test data wrong!"); - return list; - } - -} diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectConfigurationTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectConfigurationTest.java index 3fb4b6e24c..e0197bd7df 100644 --- a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectConfigurationTest.java +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectConfigurationTest.java @@ -124,7 +124,6 @@ public void marshal_and_unmarshal_contains_same_content() { /* execute */ String json = configToTest.toJSON(); - System.out.println(json); /* test */ FalsePositiveProjectConfiguration loaded = FalsePositiveProjectConfiguration.fromJSONString(json); diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImplTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImplTest.java new file mode 100644 index 0000000000..a5759dc513 --- /dev/null +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataIdValidationImplTest.java @@ -0,0 +1,93 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; + +class FalsePositiveProjectDataIdValidationImplTest { + + private FalsePositiveProjectDataIdValidationImpl validationToTest; + + @BeforeEach + void beforeEach() { + validationToTest = new FalsePositiveProjectDataIdValidationImpl(); + } + + @Test + void validate_null_returns_validation_result_invalid() { + /* prepare */ + String target = null; + + /* execute */ + ValidationResult result = validationToTest.validate(target); + + /* test */ + assertFalse(result.isValid()); + } + + @Test + void validate_empty_string_returns_validation_result_invalid() { + /* prepare */ + String target = ""; + + /* execute */ + ValidationResult result = validationToTest.validate(target); + + /* test */ + assertFalse(result.isValid()); + } + + @Test + void validate_whitespace_string_returns_validation_result_invalid() { + /* prepare */ + String target = " "; + + /* execute */ + ValidationResult result = validationToTest.validate(target); + + /* test */ + assertFalse(result.isValid()); + } + + @Test + void validate_string_longer_than_100_chars_returns_validation_result_invalid() { + /* prepare */ + String target = "a".repeat(101); + + /* execute */ + ValidationResult result = validationToTest.validate(target); + + /* test */ + assertFalse(result.isValid()); + } + + @Test + void validate_string_with_invalid_char_returns_validation_result_invalid() { + /* prepare */ + String target = "abcd/efgh"; + + /* execute */ + ValidationResult result = validationToTest.validate(target); + + /* test */ + assertFalse(result.isValid()); + } + + @Test + void validate_valid_string_returns_validation_result_valid() { + /* prepare */ + // use max length 100 chars + String target = "a".repeat(50) + "-_" + "1".repeat(48); + + /* execute */ + ValidationResult result = validationToTest.validate(target); + + /* test */ + assertTrue(result.isValid()); + } +} diff --git a/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImplTest.java b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImplTest.java new file mode 100644 index 0000000000..4dfce26552 --- /dev/null +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/FalsePositiveProjectDataValidationImplTest.java @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; + +class FalsePositiveProjectDataValidationImplTest { + + private FalsePositiveProjectDataValidationImpl validationToTest; + + private static final FalsePositiveProjectDataIdValidation idValidation = mock(); + private static final WebscanFalsePositiveProjectDataValidation webscanValidation = mock(); + + @SuppressWarnings("unchecked") + @BeforeEach + void beforeEach() { + Mockito.reset(idValidation, webscanValidation); + + validationToTest = new FalsePositiveProjectDataValidationImpl(idValidation, webscanValidation); + + when(idValidation.validate(any())).thenReturn(new ValidationResult()); + when(webscanValidation.validate(any())).thenReturn(new ValidationResult()); + } + + @Test + void no_project_data_validation_returns_invalid_result() { + /* prepare */ + FalsePositiveProjectData projectData = null; + + /* execute */ + ValidationResult result = validationToTest.validate(projectData); + + /* test */ + assertFalse(result.isValid()); + } + + @Test + void without_optional_parts_returns_valid_result() { + /* prepare */ + FalsePositiveProjectData projectData = new FalsePositiveProjectData(); + projectData.setComment("a".repeat(501)); + + /* execute */ + ValidationResult result = validationToTest.validate(projectData); + + /* test */ + assertFalse(result.isValid()); + } + +} 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 new file mode 100644 index 0000000000..3b395b787c --- /dev/null +++ b/sechub-scan/src/test/java/com/mercedesbenz/sechub/domain/scan/project/WebscanFalsePositiveProjectDataValidationImplTest.java @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.domain.scan.project; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import com.mercedesbenz.sechub.sharedkernel.validation.ValidationResult; + +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 WebscanFalsePositiveProjectDataValidationImpl validationToTest; + + @BeforeEach + void beforeEach() { + validationToTest = new WebscanFalsePositiveProjectDataValidationImpl(); + } + + @Test + void no_webscan_inside_project_data_validation_returns_valid_result() { + /* prepare */ + WebscanFalsePositiveProjectData webScan = null; + + /* execute */ + ValidationResult result = validationToTest.validate(webScan); + + // is valid because we might have other types inside + // FalsePositiveProjectDataValidation.java besides + // WebscanFalsePositiveProjectData.java in the future so webScan can be null if + // not needed + /* test */ + assertTrue(result.isValid()); + } + + @Test + void without_optional_parts_returns_valid_result() { + /* prepare */ + WebscanFalsePositiveProjectData webScan = createWebscanFalsePositiveProjectDataWithValidMandatoryParts(); + + /* execute */ + ValidationResult result = validationToTest.validate(webScan); + + /* test */ + assertTrue(result.isValid()); + } + + @Test + void too_long_optional_lists_returns_invalid_result() { + /* prepare */ + List methods = createTooLongListOfStrings(); + List ports = createTooLongListOfStrings(); + List protocols = createTooLongListOfStrings(); + + WebscanFalsePositiveProjectData webScan = createWebscanFalsePositiveProjectDataWithValidMandatoryParts(); + webScan.setMethods(methods); + webScan.setPorts(ports); + webScan.setProtocols(protocols); + + /* execute */ + ValidationResult result = validationToTest.validate(webScan); + + /* test */ + assertFalse(result.isValid()); + List errors = result.getErrors(); + assertEquals(3, errors.size()); + + assertTrue(errors.get(0).contains(methodsFieldName)); + assertTrue(errors.get(1).contains(portsFieldName)); + assertTrue(errors.get(2).contains(protocolsFieldName)); + } + + @Test + void too_long_list_entry_in_optional_lists_returns_invalid_result() { + /* prepare */ + String tooLongEntry = "a".repeat(301); + + 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); + + /* test */ + assertFalse(result.isValid()); + List errors = result.getErrors(); + assertEquals(3, errors.size()); + + assertTrue(errors.get(0).contains(methodsFieldName)); + assertTrue(errors.get(1).contains(portsFieldName)); + assertTrue(errors.get(2).contains(protocolsFieldName)); + } + + @Test + void mandatory_lists_null_returns_invalid_result() { + /* prepare */ + WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); + webScan.setHostPatterns(null); + webScan.setUrlPathPatterns(null); + + /* 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 mandatory_lists_empty_returns_invalid_result() { + /* prepare */ + WebscanFalsePositiveProjectData webScan = new WebscanFalsePositiveProjectData(); + webScan.setHostPatterns(new ArrayList<>()); + webScan.setUrlPathPatterns(new ArrayList<>()); + + /* 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_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() { + /* 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); + + /* execute */ + ValidationResult result = validationToTest.validate(webScan); + + /* test */ + assertFalse(result.isValid()); + List errors = result.getErrors(); + assertEquals(2, 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")); + } + + 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); + + return webScan; + } + + private List createTooLongListOfStrings() { + List list = new LinkedList<>(); + for (int i = 0; i < 51; i++) { + list.add("a"); + } + return list; + } +} diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java index aad7de68aa..eb5d3a75c3 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/UseCaseIdentifier.java @@ -104,10 +104,12 @@ public enum UseCaseIdentifier { UC_ADMIN_RECEIVES_NOTIFICATION_ABOUT_CLUSTER_MEMBER_START(43, false), - UC_USER_MARKS_FALSE_POSITIVES_FOR_FINISHED_JOB(44), + UC_USER_MARKS_FALSE_POSITIVES(44), UC_USER_UNMARKS_FALSE_POSITIVES(45), + UC_USER_UNMARKS_FALSE_POSITIVES_PROJECT_DATA(78), + UC_USER_FETCHES_FALSE_POSITIVE_CONFIGURATION_OF_PROJECT(46), /* executors */ diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserMarksFalsePositivesForJob.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserMarksFalsePositives.java similarity index 70% rename from sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserMarksFalsePositivesForJob.java rename to sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserMarksFalsePositives.java index 5f2124057d..e25501745b 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserMarksFalsePositivesForJob.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserMarksFalsePositives.java @@ -15,12 +15,12 @@ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @UseCaseDefinition( - id=UseCaseIdentifier.UC_USER_MARKS_FALSE_POSITIVES_FOR_FINISHED_JOB, + id=UseCaseIdentifier.UC_USER_MARKS_FALSE_POSITIVES, group=UseCaseGroup.SECHUB_EXECUTION, - apiName="userMarksFalsePositivesForJob", - title="User marks false positives for finished sechub job", - description="user/mark_false_positives_for_job.adoc") -public @interface UseCaseUserMarksFalsePositivesForJob { + apiName="userMarksFalsePositives", + title="User marks false positives", + description="user/mark_false_positives.adoc") +public @interface UseCaseUserMarksFalsePositives { Step value(); } diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositives.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositiveByJobData.java similarity index 87% rename from sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositives.java rename to sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositiveByJobData.java index 1a7af0b704..e5d3f5f0fe 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositives.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositiveByJobData.java @@ -19,8 +19,8 @@ group=UseCaseGroup.SECHUB_EXECUTION, apiName="userUnmarksFalsePositives", title="User unmarks existing false positive definitons", - description="user/unmark_false_positives.adoc") -public @interface UseCaseUserUnmarksFalsePositives { + description="user/unmark_false_positives_jobdata.adoc") +public @interface UseCaseUserUnmarksFalsePositiveByJobData { Step value(); } diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositiveByProjectData.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositiveByProjectData.java new file mode 100644 index 0000000000..207bb56b48 --- /dev/null +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/usecases/user/execute/UseCaseUserUnmarksFalsePositiveByProjectData.java @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +package com.mercedesbenz.sechub.sharedkernel.usecases.user.execute; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import com.mercedesbenz.sechub.sharedkernel.Step; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseDefinition; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseGroup; +import com.mercedesbenz.sechub.sharedkernel.usecases.UseCaseIdentifier; + +/* @formatter:off */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@UseCaseDefinition( + id=UseCaseIdentifier.UC_USER_UNMARKS_FALSE_POSITIVES_PROJECT_DATA, + group=UseCaseGroup.SECHUB_EXECUTION, + apiName="userUnmarksFalsePositivesProjectData", + title="User unmarks existing false positive project data definitons", + description="user/unmark_false_positives_projectdata.adoc") +public @interface UseCaseUserUnmarksFalsePositiveByProjectData { + + Step value(); +} +/* @formatter:on */ diff --git a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/validation/AbstractSimpleStringValidation.java b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/validation/AbstractSimpleStringValidation.java index 5c0b04f9b3..72968ab08e 100644 --- a/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/validation/AbstractSimpleStringValidation.java +++ b/sechub-shared-kernel/src/main/java/com/mercedesbenz/sechub/sharedkernel/validation/AbstractSimpleStringValidation.java @@ -45,7 +45,7 @@ protected void validateOnlyAlphabeticDigitOrAllowedParts(ValidationContext 0) { sb.append("or "); for (int i = 0; i < alsoAllowed.length; i++) { diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/RestDocPathParameter.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/RestDocPathParameter.java index e876c82117..ae93ad719f 100644 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/RestDocPathParameter.java +++ b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/RestDocPathParameter.java @@ -37,6 +37,8 @@ public enum RestDocPathParameter { WITH_META_DATA("withMetaData"), + PROJECT_DATA_ID("id"), + ; private String restDocName; diff --git a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java index 942266d5ff..3487554842 100644 --- a/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java +++ b/sechub-testframework/src/main/java/com/mercedesbenz/sechub/test/SecHubTestURLBuilder.java @@ -87,7 +87,7 @@ public String buildUploadBinariesUrl(String projectId, String jobUUID) { return buildUrl(API_PROJECT, projectId, "job", jobUUID, "binaries"); } - public String buildUserAddsFalsePositiveJobDataListForProject(String projectId) { + public String buildUserAddsFalsePositiveDataListForProject(String projectId) { return buildUrl(API_PROJECT, projectId, "false-positives"); } @@ -95,6 +95,10 @@ public String buildUserRemovesFalsePositiveEntryFromProject(String projectId, St return buildUrl(API_PROJECT, projectId, "false-positive", jobUUID, findingId); } + public String buildUserRemovesFalsePositiveProjectDataEntryFromProject(String projectId, String projectDataId) { + return buildUrl(API_PROJECT, projectId, "false-positive", "project-data", projectDataId); + } + public String buildUserFetchesFalsePositiveConfigurationOfProject(String projectId) { return buildUrl(API_PROJECT, projectId, "false-positives"); }