diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1b06fd4de35..1031d9f6101 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,6 +11,8 @@ updates: versions: - ">= 5.a" - "< 6" + - dependency-name: boto3 + update-types: ["version-update:semver-minor"] - package-ecosystem: npm directory: "/components" schedule: diff --git a/.github/renovate.json b/.github/renovate.json index 8b51f3bfa3a..9ba0161dc13 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -26,6 +26,7 @@ "description": "Update renovate weekly (sundays) - They are releasing new versions too often, so it is a bit noisy, and keeping renovating a bit older does not create vulnerabilities in DD", "matchDatasources": "github-releases", "matchPackageNames": "renovatebot/renovate", + "separateMinorPatch": false, "schedule": ["* * * * 0"] },{ "description": "Minikube does not like freshly released k8s. We need to wait some time so it will be adopted", diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 217f0317688..91e042f8049 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -3,8 +3,11 @@ name: github-pages on: workflow_dispatch: push: + paths: + - 'docs/**' branches: - master + - bugfix # Taken from https://github.com/marketplace/actions/hugo-setup#%EF%B8%8F-workflow-for-autoprefixer-and-postcss-cli # Both builds have to be one worflow as otherwise one publish will overwrite the other diff --git a/.github/workflows/test-helm-chart.yml b/.github/workflows/test-helm-chart.yml index f20915f3f9a..8fa56ea0ba9 100644 --- a/.github/workflows/test-helm-chart.yml +++ b/.github/workflows/test-helm-chart.yml @@ -66,7 +66,7 @@ jobs: if: env.changed == 'true' - name: Check update of "artifacthub.io/changes" HELM annotation - if: env.changed == 'true' + if: ${{ env.changed == 'true' && !(startsWith(github.head_ref, 'master-into-dev/') || startsWith(github.head_ref, 'master-into-bugfix/')) }} run: | # fast fail if `git show` fails set -e diff --git a/components/package.json b/components/package.json index cd38f67ae36..564f54c63a0 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.52.2", + "version": "2.52.3", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/docs/content/en/share_your_findings/jira_guide.md b/docs/content/en/share_your_findings/jira_guide.md index 5b409742122..baeaca65094 100644 --- a/docs/content/en/share_your_findings/jira_guide.md +++ b/docs/content/en/share_your_findings/jira_guide.md @@ -4,11 +4,11 @@ description: "Work with the Jira integration" weight: 1 --- -DefectDojo's Jira integration can be used to push Finding data to one or more Jira Projects. By doing so, you can integrate DefectDojo into your standard development workflow. Here are some examples of how this can work: +DefectDojo's Jira integration can be used to push Finding data to one or more Jira Spaces. By doing so, you can integrate DefectDojo into your standard development workflow. Here are some examples of how this can work: -* The AppSec team can selectively push Findings to a Jira Project used by developers, so that issue remediation can be appropriately prioritized alongside regular development. Developers on this board don't need to access DefectDojo - they can keep all their work in one place. -* DefectDojo can push ALL Findings to a bidirectional Jira Project which the AppSec team uses, which allows them to split up issue validation. This board keeps in sync with DefectDojo and allows for complex remediation workflows. -* DefectDojo can selectively push Findings from separate Products &/or Engagements to separate Jira Projects, to keep things in their proper context. +* The AppSec team can selectively push Findings to a Jira Space used by developers, so that issue remediation can be appropriately prioritized alongside regular development. Developers on this board don't need to access DefectDojo - they can keep all their work in one place. +* DefectDojo can push ALL Findings to a bidirectional Jira Space which the AppSec team uses, which allows them to split up issue validation. This board keeps in sync with DefectDojo and allows for complex remediation workflows. +* DefectDojo can selectively push Findings from separate Products &/or Engagements to separate Jira Spaces, to keep things in their proper context. # Setting Up Jira Setting Up Jira requires the following steps: @@ -40,7 +40,7 @@ Optionally, you can map: * Jira Transitions to trigger Re-Opening and Closing Findings * Jira Resolutions which can apply Risk Acceptance and False Positive statuses to Findings (optional) -Multiple Jira Projects can be handled by a single Jira Instance connection, as long as the Jira account / token used by DefectDojo has permission to create Issues in the associated Jira Project. +Multiple Jira Spaces can be handled by a single Jira Instance connection, as long as the Jira account / token used by DefectDojo has permission to create Issues in the associated Jira Space. ### Add a Jira Instance (Pro UI) @@ -62,7 +62,7 @@ Multiple Jira Projects can be handled by a single Jira Instance connection, as l Note that the user associated with this connection have permission to create Issues and access data in your Jira instance. 6. You will need to provide values for an Epic Name ID, Re-open Transition ID and Close Transition ID. These values can be changed later. While logged into Jira, you can access these values from the following URLs: -- **Epic Name ID**: visit `https:///rest/api/2/field` and search for Epic Name. Copy the number out of `number` and paste it here. +- **Epic Name ID**: visit `https:///rest/api/2/field` and search for Epic Name. Copy the number out of `number` and paste it here. If you do not have an Epic Name ID associated with your Space in Jira (due to using a Team-Managed Space, for example), enter 0 on this field. - **Re-open Transition ID**: visit `https:///rest/api/latest/issue//transitions?expand-transitions.fields` to find the ID for your Jira instance. Paste it in the Reopen Transition ID field. - **Close Transition ID**: Visit `https:///rest/api/latest/issue//transitions?expand-transitions.fields` to find the ID for your Jira instance. Paste it in the Close Transition ID field. @@ -88,11 +88,11 @@ The form can be submitted from here. If you wish, you can further customize you ​ ![image](images/Connect_DefectDojo_to_Jira.png) -3. You will see a list of all currently configured Jira Projects which are linked to DefectDojo. To add a new Project Configuration, click the wrench icon and choose either the **Add Jira Configuration (Express)** or **Add Jira Configuration** options. +3. You will see a list of all currently configured Jira Spaces which are linked to DefectDojo. To add a new Project Configuration, click the wrench icon and choose either the **Add Jira Configuration (Express)** or **Add Jira Configuration** options. #### Add Jira Configuration (Express) -The Express method allows for a quicker method of linking a Project. Use the Express method if you simply want to connect a Jira Project quickly, and you aren’t dealing with a complex Jira workflow. +The Express method allows for a quicker method of linking a Space. Use the Express method if you simply want to connect a Jira Space quickly, and you aren’t dealing with a complex Jira workflow. ![image](images/Connect_DefectDojo_to_Jira_2.png) @@ -122,7 +122,7 @@ If you leave this field blank, it will default to **Jira\_full.** ​ 9. Decide whether you wish to automatically sync Findings with Jira. If this is enabled, Jira Issues will automatically be kept in sync with the related Findings. If this is not enabled, you will need to manually push any changes made to a Finding after the Issue has been created in Jira. ​ -10. Select your Issue key. In Jira, this is the string associated with an Issue (e.g. the word **‘EXAMPLE’** in an issue called **EXAMPLE\-123**). If you don’t know your issue key, create a new Issue in the Jira Project. In the screenshot below, we can see that the issue key on our Jira Project is **DEF**. +10. Select your Issue key. In Jira, this is the string associated with an Issue (e.g. the word **‘EXAMPLE’** in an issue called **EXAMPLE\-123**). If you don’t know your issue key, create a new Issue in the Jira Space. In the screenshot below, we can see that the issue key on our Jira Space is **DEF**. ​ ![image](images/Connect_DefectDojo_to_Jira_3.png) ​ @@ -154,23 +154,23 @@ Comments (in Jira) and Notes (in DefectDojo) can be kept in sync. This setting c ## Step 2: Connect a Product or Engagement to Jira -Each Product or Engagement in DefectDojo has its own settings which govern how Findings are converted to JIRA Issues. From here, you can decide the associated JIRA Project and set the default behaviour for creating Issues, Epics, Labels and other JIRA metadata. +Each Product or Engagement in DefectDojo has its own settings which govern how Findings are converted to JIRA Issues. From here, you can decide the associated Jira Space and set the default behaviour for creating Issues, Epics, Labels and other JIRA metadata. ### Add Jira to a Product or Engagement (Pro UI) -You can find this page by clicking the Gear menu on a Product or Engagement - ⚙️ and opening the Jira Project Settings page. +You can find this page by clicking the Gear menu on a Product or Engagement - ⚙️ and opening the Jira Space Settings page. ![image](images/jira-project-settings.png) #### Jira Instance -If you have multiple instances of Jira set up, for separate products or teams within your organization, you can indicate which Jira Project you want DefectDojo to create Issues in. Select a Project from the drop\-down menu. +If you have multiple instances of Jira set up, for separate products or teams within your organization, you can indicate which Jira Space you want DefectDojo to create Issues in. Select a Space from the drop\-down menu. -If this menu doesn't list any Jira instances, confirm that those Projects are connected in your global Jira Configuration for DefectDojo \- yourcompany.defectdojo.com/jira. +If this menu doesn't list any Jira instances, confirm that those Space are connected in your global Jira Configuration for DefectDojo \- yourcompany.defectdojo.com/jira. #### Project key -This is the key of the Project that you want to use with DefectDojo. The Project Key for a given project can be found in the URL. +This is the key of the Space that you want to use with DefectDojo. The Space Key for a given Space can be found in the URL. (This was previously referred to as a **Jira Project Key**, but as of Sepetember 2025, this is now referred to in Jira as the **Space Key**). ![image](images/Add_a_Connected_Jira_Project_to_a_Product_3.png) @@ -192,15 +192,15 @@ Here is an example of a **jira\_full** Issue: #### Component -If you manage your Jira project using Components, you can assign the appropriate Component for DefectDojo here. +If you manage your Jira Space using Components, you can assign the appropriate Component for DefectDojo here. **Custom fields** If you don’t need to use Custom Fields with DefectDojo issues, you can leave this field as ‘null’. -However, if your Jira Project Settings **require you** to use Custom Fields on new Issues, you will need to hard-code these mappings. +However, if your Jira Space Settings **require you** to use Custom Fields on new Issues, you will need to hard-code these mappings. -Note that DefectDojo cannot send any Issue\-specific metadata as Custom Fields, only a default value. This section should only be set up if your JIRA Project **requires that these Custom Fields exist** in every Issue in your project. +Note that DefectDojo cannot send any Issue\-specific metadata as Custom Fields, only a default value. This section should only be set up if your Jira Space **requires that these Custom Fields exist** in every Issue in your Space. Follow **[this guide](#custom-fields-in-jira)** to get started working with Custom Fields. @@ -212,7 +212,7 @@ Select the relevant labels that you want the Issue to be created with in Jira, e #### Default assignee -The name of the default assignee in Jira. If left blank, DefectDojo will follow the default behaviour in your Jira Project when creating Issues. +The name of the default assignee in Jira. If left blank, DefectDojo will follow the default behaviour in your Jira Space when creating Issues. ### Add Jira to a Product or Engagement (Classic UI / Open-Source) @@ -228,13 +228,13 @@ Jira settings are located near the bottom of the Product Settings page. #### Jira Instance -If you have multiple instances of Jira set up, for separate products or teams within your organization, you can indicate which Jira Project you want DefectDojo to create Issues in. Select a Project from the drop\-down menu. +If you have multiple instances of Jira set up, for separate products or teams within your organization, you can indicate which Jira Space you want DefectDojo to create Issues in. Select a Project from the drop\-down menu. If this menu doesn't list any Jira instances, confirm that those Projects are connected in your global Jira Configuration for DefectDojo \- yourcompany.defectdojo.com/jira. #### Project key -This is the key of the Project that you want to use with DefectDojo. The Project Key for a given project can be found in the URL. +This is the key of the Space that you want to use with DefectDojo. The Space Key for a given project can be found in the URL, or under "Space key" listed in Space Settings. ![image](images/Add_a_Connected_Jira_Project_to_a_Product_3.png) @@ -256,17 +256,17 @@ Here is an example of a **jira\_full** Issue: #### Component -If you manage your Jira project using Components, you can assign the appropriate Component for DefectDojo here. +If you manage your Jira Space using Components, you can assign the appropriate Component for DefectDojo here. **Custom fields** If you don’t need to use Custom Fields with DefectDojo issues, you can leave this field as ‘null’. -However, if your Jira Project Settings **require you** to use Custom Fields on new Issues, you will need to hard\-code these mappings. +However, if your Jira Space Settings **require you** to use Custom Fields on new Issues, you will need to hard\-code these mappings. **Jira Cloud now allows you to create a default Custom Field value directly in\-app. [See Atlassian's documentation on Custom Fields](https://support.atlassian.com/jira-cloud-administration/docs/configure-a-custom-field/) for more information on how to configure this.** -Note that DefectDojo cannot send any Issue\-specific metadata as Custom Fields, only a default value. This section should only be set up if your JIRA Project **requires that these Custom Fields exist** in every Issue in your project. +Note that DefectDojo cannot send any Issue\-specific metadata as Custom Fields, only a default value. This section should only be set up if your Jira Space **requires that these Custom Fields exist** in every Issue in your Space. Follow **[this guide](#custom-fields-in-jira)** to get started working with Custom Fields. @@ -278,11 +278,11 @@ Select the relevant labels that you want the Issue to be created with in Jira, e #### Default assignee -The name of the default assignee in Jira. If left blank, DefectDojo will follow the default behaviour in your Jira Project when creating Issues. +The name of the default assignee in Jira. If left blank, DefectDojo will follow the default behaviour in your Jira Space when creating Issues. ### Additional Form Options -#### Enable Connection With Jira Project +#### Enable Connection With Jira Space Jira integrations can be removed from your instance only if no related Issues have been created. If Issues have been created, there is no way to completely remove a Jira Instance from DefectDojo. @@ -373,7 +373,7 @@ Add whatever title severity and description you wish, and then click “Finished If Jira Issues are not being created correctly, check your Notifications for error codes. -* Confirm that the Jira User associated with DefectDojo's Jira Configuration has permission to create and update issues on that particular Jira Project. +* Confirm that the Jira User associated with DefectDojo's Jira Configuration has permission to create and update issues on that particular Jira Space. #### Test 2: Jira Webhooks send to DefectDojo @@ -389,7 +389,7 @@ If this doesn’t work correctly, it could be due to a Firewall issue on your Ji Jira integrations can be removed from your instance only if no related Issues have been created. If Issues have been created, there is no way to completely remove a Jira Instance from DefectDojo. -However, you can disable your Jira integration by disabling it at the Product level. From the **Edit Product** form (Classic UI) or from the **Jira Product Settings** (Pro UI) you can uncheck the "Enable Connection With Jira Project" option. This will not delete or change any existing Jira tickets created by DefectDojo, but will disable any further updates. +However, you can disable your Jira integration by disabling it at the Product level. From the **Edit Product** form (Classic UI) or from the **Jira Product Settings** (Pro UI) you can uncheck the "Enable Connection With Jira Space" option. This will not delete or change any existing Jira tickets created by DefectDojo, but will disable any further updates. # Pushing Findings To Jira diff --git a/docs/content/en/share_your_findings/troubleshooting_jira.md b/docs/content/en/share_your_findings/troubleshooting_jira.md index 5bdee959791..aecdb8f1bcf 100644 --- a/docs/content/en/share_your_findings/troubleshooting_jira.md +++ b/docs/content/en/share_your_findings/troubleshooting_jira.md @@ -53,6 +53,8 @@ For example: curl -H "Authorization: Bearer ATATT1234567890abcdefghijklmnopqrstuvwxyz" https://.atlassian.net/rest/api/latest/issue//transitions?expand=transitions.fields ``` +## I can't find an Epic Name ID for my Space +Certain Spaces in Jira, such as Team-Managed Spaces, do not use Epics and therefore will not have an Epic Name ID. In this case, set Epic Name ID to 0 in DefectDojo. ## Findings that I 'Push To Jira' do not appear in Jira Using the 'Push To Jira' workflow triggers an asynchronous process, however an Issue should be created in Jira fairly quickly after 'Push To Jira' is triggered. @@ -60,16 +62,16 @@ Using the 'Push To Jira' workflow triggers an asynchronous process, however an I * Check your DefectDojo notifications to see if the process was successful. If the push failed, you will get an error response from Jira in your notifications. Common reasons issues are not created: -* The Default Issue Type you have selected is not usable with the Jira Project -* Issues in the Project have required attributes that prevent them from being created via DefectDojo (see our guide to [Custom Fields](../jira_guide/#custom-fields-in-jira)) +* The Default Issue Type you have selected is not usable with the Jira Space +* Issues in the Space have required attributes that prevent them from being created via DefectDojo (see our guide to [Custom Fields](../jira_guide/#custom-fields-in-jira)) ## Error: Product Misconfigured or no permissions in Jira? This error message can appear when attempting to add a created Jira configuration to a Product. DefectDojo will attempt to validate a connection to Jira, and if that connection fails, it will raise this error message. -* Check to see if your Jira credentials are allowed to create issues in the given Jira Project you have selected. -* The "Project Key" field needs to be a valid Jira Project. Jira issues can use many different Keys within a single Project; the easiest way to confirm your Project Key is to look at the URL for that particular Jira Project: generally this will look like `https://xyz.atlassian.net/jira/core/projects/JTV/board`. In this case `JTV` is the Project Key. +* Check to see if your Jira credentials are allowed to create issues in the given Jira Space you have selected. +* The "Project Key" field needs to be a valid Jira Space. Jira issues can use many different Keys within a single Space; the easiest way to confirm your Project Key is to look at the URL for that particular Jira Space: generally this will look like `https://xyz.atlassian.net/jira/core/projects/JTV/board`. In this case `JTV` is the Space Key. ## Changes made to Jira issues are not updating Findings in DefectDojo @@ -87,7 +89,7 @@ This error message can appear when attempting to add a created Jira configuratio DefectDojo's Jira integration needs a customfield value for 'Epic Name'. However, your Project settings might not actually use 'Epic Name' as a field when creating Epics. Atlassian made a change in [August 2023](https://community.atlassian.com/t5/Jira-articles/Upcoming-changes-to-epic-fields-in-company-managed-projects/ba-p/1997562) which combined the 'Epic Name' and 'Epic Summary' fields. -Newer Jira Projects might not use this field when creating Epics by default, which results in this error message. +Newer Jira Spaces might not use this field when creating Epics by default, which results in this error message. To correct this issue, you can add the 'Epic Name' field to your Project's issue creation screen: diff --git a/docs/layouts/_partials/head/script-header.html b/docs/layouts/_partials/head/script-header.html index 76b5fa4ffc5..30d1f9754f8 100644 --- a/docs/layouts/_partials/head/script-header.html +++ b/docs/layouts/_partials/head/script-header.html @@ -1,6 +1,12 @@ - \ No newline at end of file + !function () { var e, t, n; e = "a92cfcfa51eca96", t = function () { Reo.init({ clientID: "a92cfcfa51eca96" }) }, (n = document.createElement("script")).src = "https://static.reo.dev/" + e + "/reo.js", n.async = !0, n.onload = t, document.head.appendChild(n) }(); + + + \ No newline at end of file diff --git a/dojo/__init__.py b/dojo/__init__.py index effca246b4b..5ddbfa246b6 100644 --- a/dojo/__init__.py +++ b/dojo/__init__.py @@ -4,6 +4,6 @@ # Django starts so that shared_task will use this app. from .celery import app as celery_app # noqa: F401 -__version__ = "2.52.2" +__version__ = "2.52.3" __url__ = "https://github.com/DefectDojo/django-DefectDojo" __docs__ = "https://documentation.defectdojo.com" diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index 806a8a1453a..7acd0eac1ab 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -2103,6 +2103,11 @@ class ProductSerializer(serializers.ModelSerializer): findings_count = serializers.SerializerMethodField() findings_list = serializers.SerializerMethodField() + business_criticality = serializers.ChoiceField(choices=Product.BUSINESS_CRITICALITY_CHOICES, allow_blank=True, allow_null=True, required=False) + platform = serializers.ChoiceField(choices=Product.PLATFORM_CHOICES, allow_blank=True, allow_null=True, required=False) + lifecycle = serializers.ChoiceField(choices=Product.LIFECYCLE_CHOICES, allow_blank=True, allow_null=True, required=False) + origin = serializers.ChoiceField(choices=Product.ORIGIN_CHOICES, allow_blank=True, allow_null=True, required=False) + tags = TagListSerializerField(required=False) product_meta = ProductMetaSerializer(read_only=True, many=True) diff --git a/dojo/filters.py b/dojo/filters.py index ebd8b023fb8..449b755ef1e 100644 --- a/dojo/filters.py +++ b/dojo/filters.py @@ -1423,10 +1423,10 @@ class ApiProductFilter(DojoFilter): name = CharFilter(lookup_expr="icontains") name_exact = CharFilter(field_name="name", lookup_expr="iexact") description = CharFilter(lookup_expr="icontains") - business_criticality = CharFilter(method=custom_filter, field_name="business_criticality") - platform = CharFilter(method=custom_filter, field_name="platform") - lifecycle = CharFilter(method=custom_filter, field_name="lifecycle") - origin = CharFilter(method=custom_filter, field_name="origin") + business_criticality = MultipleChoiceFilter(choices=Product.BUSINESS_CRITICALITY_CHOICES) + platform = MultipleChoiceFilter(choices=Product.PLATFORM_CHOICES) + lifecycle = MultipleChoiceFilter(choices=Product.LIFECYCLE_CHOICES) + origin = MultipleChoiceFilter(choices=Product.ORIGIN_CHOICES) # NumberInFilter id = NumberInFilter(field_name="id", lookup_expr="in") product_manager = NumberInFilter(field_name="product_manager", lookup_expr="in") diff --git a/dojo/middleware.py b/dojo/middleware.py index 8c7cb55d2d3..275cfa09e71 100644 --- a/dojo/middleware.py +++ b/dojo/middleware.py @@ -111,7 +111,8 @@ class DojoSytemSettingsMiddleware: def __init__(self, get_response): self.get_response = get_response from dojo.models import System_Settings # noqa: PLC0415 circular import - models.signals.post_save.connect(self.cleanup, sender=System_Settings) + # Use classmethod directly to avoid keeping reference to middleware instance + models.signals.post_save.connect(DojoSytemSettingsMiddleware.cleanup, sender=System_Settings) def __call__(self, request): self.load() diff --git a/dojo/settings/settings.dist.py b/dojo/settings/settings.dist.py index 54c9af4f23b..57c18e6ea56 100644 --- a/dojo/settings/settings.dist.py +++ b/dojo/settings/settings.dist.py @@ -1888,6 +1888,7 @@ def saml2_attrib_map_format(din): "C-": "https://hub.armosec.io/docs/", # e.g. https://hub.armosec.io/docs/c-0085 "CISCO-SA-": "https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/", # e.g. https://sec.cloudapps.cisco.com/security/center/content/CiscoSecurityAdvisory/cisco-sa-umbrella-tunnel-gJw5thgE "CAPEC": "https://capec.mitre.org/data/definitions/&&.html", # e.g. https://capec.mitre.org/data/definitions/157.html + "CERTFR-": "https://www.cert.ssi.gouv.fr/alerte/", # e.g. https://www.cert.ssi.gouv.fr/alerte/CERTFR-2025-ALE-012" "CGA-": "https://images.chainguard.dev/security/", # e.g. https://images.chainguard.dev/security/CGA-24pq-h5fw-43v3 "CONFSERVER-": "https://jira.atlassian.com/browse/", # e.g. https://jira.atlassian.com/browse/CONFSERVER-93361 "CVE-": "https://nvd.nist.gov/vuln/detail/", # e.g. https://nvd.nist.gov/vuln/detail/cve-2022-22965 diff --git a/dojo/tasks.py b/dojo/tasks.py index ad489a0c229..f74ffbd6389 100644 --- a/dojo/tasks.py +++ b/dojo/tasks.py @@ -265,7 +265,7 @@ def update_watson_search_index_for_model(model_name, pk_list, *args, **kwargs): # Let watson handle the bulk indexing context_manager.end() - logger.info(f"Completed async watson index update: {instances_added} updated, {instances_skipped} skipped") + logger.debug(f"Completed async watson index update: {instances_added} updated, {instances_skipped} skipped") except Exception as e: logger.error(f"Watson async index update failed for {model_name}: {e}") diff --git a/dojo/tools/trivy/parser.py b/dojo/tools/trivy/parser.py index ea01d38b6db..184aa88c2ac 100644 --- a/dojo/tools/trivy/parser.py +++ b/dojo/tools/trivy/parser.py @@ -255,6 +255,7 @@ def get_result_items(self, test, results, service_name=None, artifact_name=""): cvssclass = None cvssv3 = None cvssv3_score = None + severity = TRIVY_SEVERITIES[vuln["Severity"]] if vuln.get("Severity") else None # Iterate over the possible severity sources tom find the first match for severity_source in [detected_severity_source, *CVSS_SEVERITY_SOURCES]: cvssclass = cvss.get(severity_source, None) @@ -265,16 +266,17 @@ def get_result_items(self, test, results, service_name=None, artifact_name=""): if cvss_data := parse_cvss_data(cvssclass.get("V3Vector", "")): cvssv3 = cvss_data.get("cvssv3") cvssv3_score = cvss_data.get("cvssv3_score") - severity = cvss_data.get("severity") + if severity is None: + severity = cvss_data.get("severity") elif (cvss_v3_score := cvssclass.get("V3Score")) is not None: cvssv3_score = cvss_v3_score - severity = self.convert_cvss_score(cvss_v3_score) + if severity is None: + severity = self.convert_cvss_score(cvss_v3_score) elif (cvss_v2_score := cvssclass.get("V2Score")) is not None: - severity = self.convert_cvss_score(cvss_v2_score) - else: - severity = self.convert_cvss_score(None) - else: - severity = TRIVY_SEVERITIES[vuln["Severity"]] + if severity is None: + severity = self.convert_cvss_score(cvss_v2_score) + if severity is None: + severity = self.convert_cvss_score(None) if target_class in {"os-pkgs", "lang-pkgs"}: file_path = vuln.get("PkgPath") if file_path is None: diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index 70cb2841277..182d0bfd1e7 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.52.2" +appVersion: "2.52.3" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.8.2 +version: 1.8.3 icon: https://defectdojo.com/hubfs/DefectDojo_favicon.png maintainers: - name: madchap @@ -34,8 +34,4 @@ dependencies: # description: Critical bug annotations: artifacthub.io/prerelease: "false" - artifacthub.io/changes: | - - kind: changed - description: Location of HELM development hints has been changed - - kind: changed - description: Bump DefectDojo to 2.52.2 + artifacthub.io/changes: "- kind: changed\n description: Bump DefectDojo to 2.52.3\n" diff --git a/helm/defectdojo/README.md b/helm/defectdojo/README.md index 7e3d8421060..02c9da17348 100644 --- a/helm/defectdojo/README.md +++ b/helm/defectdojo/README.md @@ -512,7 +512,7 @@ The HELM schema will be generated for you. # General information about chart values -![Version: 1.8.2](https://img.shields.io/badge/Version-1.8.2-informational?style=flat-square) ![AppVersion: 2.52.2](https://img.shields.io/badge/AppVersion-2.52.2-informational?style=flat-square) +![Version: 1.8.3](https://img.shields.io/badge/Version-1.8.3-informational?style=flat-square) ![AppVersion: 2.52.3](https://img.shields.io/badge/AppVersion-2.52.3-informational?style=flat-square) A Helm chart for Kubernetes to install DefectDojo diff --git a/unittests/scans/github_vulnerability/github_h2.json b/unittests/scans/github_vulnerability/github_h2.json index 89a4e72b0bd..6d51ccc574a 100644 --- a/unittests/scans/github_vulnerability/github_h2.json +++ b/unittests/scans/github_vulnerability/github_h2.json @@ -1 +1,76 @@ -{"data":{"repository":{"vulnerabilityAlerts":{"nodes":[{"createdAt":"2022-05-09T09:43:40Z","dismissedAt":null,"id":"RVA_kwDOAQoNos6MAo1b","vulnerableManifestPath":"apache/cxf/syncope/cxf-syncope/pom.xml", "securityVulnerability":{"severity":"CRITICAL","package":{"name":"com.h2database:h2"},"advisory":{"description":"### Impact\nH2 Console in versions since 1.1.100 (2008-10-14) to 2.0.204 (2021-12-21) inclusive allows loading of custom classes from remote servers through JNDI.\n\nH2 Console doesn't accept remote connections by default. If remote access was enabled explicitly and some protection method (such as security constraint) wasn't set, an intruder can load own custom class and execute its code in a process with H2 Console (H2 Server process or a web server with H2 Console servlet).\n\nIt is also possible to load them by creation a linked table in these versions, but it requires `ADMIN` privileges and user with `ADMIN` privileges has full access to the Java process by design. These privileges should never be granted to untrusted users.\n\n### Patches\nSince version 2.0.206 H2 Console and linked tables explicitly forbid attempts to specify LDAP URLs for JNDI. Only local data sources can be used.\n\n### Workarounds\nH2 Console should never be available to untrusted users.\n\n`-webAllowOthers` is a dangerous setting that should be avoided.\n\nH2 Console Servlet deployed on a web server can be protected with a security constraint:\nhttps://h2database.com/html/tutorial.html#usingH2ConsoleServlet\nIf `webAllowOthers` is specified, you need to uncomment and edit `` and `` as necessary. See documentation of your web server for more details.\n\n### References\nThis issue was found and privately reported to H2 team by [JFrog Security](https://www.jfrog.com/)'s vulnerability research team with detailed information.\n","summary":"RCE in H2 Console","identifiers":[{"type":"GHSA","value":"GHSA-h376-j262-vhq6"},{"type":"CVE","value":"CVE-2021-42392"}],"cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"cwes":{"nodes":[{"cweId":"CWE-502"}]},"references":[{"url":"https://github.com/h2database/h2database/security/advisories/GHSA-h376-j262-vhq6"},{"url":"https://github.com/h2database/h2database/releases/tag/version-2.0.206"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2021-42392"},{"url":"https://jfrog.com/blog/the-jndi-strikes-back-unauthenticated-rce-in-h2-database-console/"},{"url":"https://security.netapp.com/advisory/ntap-20220119-0001/"},{"url":"https://lists.debian.org/debian-lts-announce/2022/02/msg00017.html"},{"url":"https://www.debian.org/security/2022/dsa-5076"},{"url":"https://www.oracle.com/security-alerts/cpuapr2022.html"},{"url":"https://github.com/advisories/GHSA-h376-j262-vhq6"}]}}}]}}}} +{ + "data": { + "repository": { + "vulnerabilityAlerts": { + "nodes": [ + { + "createdAt": "2022-05-09T09:43:40Z", + "dismissedAt": null, + "id": "RVA_kwDOAQoNos6MAo1b", + "vulnerableManifestPath": "apache/cxf/syncope/cxf-syncope/pom.xml", + "securityVulnerability": { + "severity": "CRITICAL", + "package": { + "name": "com.h2database:h2" + }, + "advisory": { + "description": "### Impact\nH2 Console in versions since 1.1.100 (2008-10-14) to 2.0.204 (2021-12-21) inclusive allows loading of custom classes from remote servers through JNDI.\n\nH2 Console doesn't accept remote connections by default. If remote access was enabled explicitly and some protection method (such as security constraint) wasn't set, an intruder can load own custom class and execute its code in a process with H2 Console (H2 Server process or a web server with H2 Console servlet).\n\nIt is also possible to load them by creation a linked table in these versions, but it requires `ADMIN` privileges and user with `ADMIN` privileges has full access to the Java process by design. These privileges should never be granted to untrusted users.\n\n### Patches\nSince version 2.0.206 H2 Console and linked tables explicitly forbid attempts to specify LDAP URLs for JNDI. Only local data sources can be used.\n\n### Workarounds\nH2 Console should never be available to untrusted users.\n\n`-webAllowOthers` is a dangerous setting that should be avoided.\n\nH2 Console Servlet deployed on a web server can be protected with a security constraint:\nhttps://h2database.com/html/tutorial.html#usingH2ConsoleServlet\nIf `webAllowOthers` is specified, you need to uncomment and edit `` and `` as necessary. See documentation of your web server for more details.\n\n### References\nThis issue was found and privately reported to H2 team by [JFrog Security](https://www.jfrog.com/)'s vulnerability research team with detailed information.\n", + "summary": "RCE in H2 Console", + "identifiers": [ + { + "type": "GHSA", + "value": "GHSA-h376-j262-vhq6" + }, + { + "type": "CVE", + "value": "CVE-2021-42392" + } + ], + "cvss": { + "score": 9.8, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "cwes": { + "nodes": [ + { + "cweId": "CWE-502" + } + ] + }, + "references": [ + { + "url": "https://github.com/h2database/h2database/security/advisories/GHSA-h376-j262-vhq6" + }, + { + "url": "https://github.com/h2database/h2database/releases/tag/version-2.0.206" + }, + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-42392" + }, + { + "url": "https://jfrog.com/blog/the-jndi-strikes-back-unauthenticated-rce-in-h2-database-console/" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20220119-0001/" + }, + { + "url": "https://lists.debian.org/debian-lts-announce/2022/02/msg00017.html" + }, + { + "url": "https://www.debian.org/security/2022/dsa-5076" + }, + { + "url": "https://www.oracle.com/security-alerts/cpuapr2022.html" + }, + { + "url": "https://github.com/advisories/GHSA-h376-j262-vhq6" + } + ] + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/unittests/scans/github_vulnerability/github_shiro.json b/unittests/scans/github_vulnerability/github_shiro.json index a2640ffe01d..2c1dd713964 100644 --- a/unittests/scans/github_vulnerability/github_shiro.json +++ b/unittests/scans/github_vulnerability/github_shiro.json @@ -1 +1,68 @@ -{"data":{"repository":{"vulnerabilityAlerts":{"nodes":[{"createdAt":"2021-09-20T20:33:13Z","dismissedAt":null,"id":"RVA_kwDOAQoNos5VQZSk","vulnerableManifestPath":"apache/cxf/cxf-shiro/pom.xml","state":"FIXED","securityVulnerability":{"severity":"CRITICAL","package":{"name":"org.apache.shiro:shiro-core"},"advisory":{"description":"Apache Shiro before 1.8.0, when using Apache Shiro with Spring Boot, a specially crafted HTTP request may cause an authentication bypass. Users should update to Apache Shiro 1.8.0.","summary":"Apache Shiro vulnerable to a specially crafted HTTP request causing an authentication bypass","identifiers":[{"type":"GHSA","value":"GHSA-f6jp-j6w3-w9hm"},{"type":"CVE","value":"CVE-2021-41303"}],"cvss":{"score":9.8,"vectorString":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H"},"cwes":{"nodes":[{"cweId":"CWE-287"}]},"references":[{"url":"https://nvd.nist.gov/vuln/detail/CVE-2021-41303"},{"url":"https://lists.apache.org/thread.html/re470be1ffea44bca28ccb0e67a4cf5d744e2d2b981d00fdbbf5abc13%40%3Cannounce.shiro.apache.org%3E"},{"url":"https://lists.apache.org/thread.html/raae98bb934e4bde304465896ea02d9798e257e486d04a42221e2c41b@%3Cuser.shiro.apache.org%3E"},{"url":"https://security.netapp.com/advisory/ntap-20220609-0001/"},{"url":"https://www.oracle.com/security-alerts/cpujul2022.html"},{"url":"https://github.com/advisories/GHSA-f6jp-j6w3-w9hm"}]}}}]}}}} +{ + "data": { + "repository": { + "vulnerabilityAlerts": { + "nodes": [ + { + "createdAt": "2021-09-20T20:33:13Z", + "dismissedAt": null, + "id": "RVA_kwDOAQoNos5VQZSk", + "vulnerableManifestPath": "apache/cxf/cxf-shiro/pom.xml", + "state": "FIXED", + "securityVulnerability": { + "severity": "CRITICAL", + "package": { + "name": "org.apache.shiro:shiro-core" + }, + "advisory": { + "description": "Apache Shiro before 1.8.0, when using Apache Shiro with Spring Boot, a specially crafted HTTP request may cause an authentication bypass. Users should update to Apache Shiro 1.8.0.", + "summary": "Apache Shiro vulnerable to a specially crafted HTTP request causing an authentication bypass", + "identifiers": [ + { + "type": "GHSA", + "value": "GHSA-f6jp-j6w3-w9hm" + }, + { + "type": "CVE", + "value": "CVE-2021-41303" + } + ], + "cvss": { + "score": 9.8, + "vectorString": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + "cwes": { + "nodes": [ + { + "cweId": "CWE-287" + } + ] + }, + "references": [ + { + "url": "https://nvd.nist.gov/vuln/detail/CVE-2021-41303" + }, + { + "url": "https://lists.apache.org/thread.html/re470be1ffea44bca28ccb0e67a4cf5d744e2d2b981d00fdbbf5abc13%40%3Cannounce.shiro.apache.org%3E" + }, + { + "url": "https://lists.apache.org/thread.html/raae98bb934e4bde304465896ea02d9798e257e486d04a42221e2c41b@%3Cuser.shiro.apache.org%3E" + }, + { + "url": "https://security.netapp.com/advisory/ntap-20220609-0001/" + }, + { + "url": "https://www.oracle.com/security-alerts/cpujul2022.html" + }, + { + "url": "https://github.com/advisories/GHSA-f6jp-j6w3-w9hm" + } + ] + } + } + } + ] + } + } + } +} \ No newline at end of file diff --git a/unittests/scans/trivy/severity_prio.json b/unittests/scans/trivy/severity_prio.json new file mode 100644 index 00000000000..07f4d22d218 --- /dev/null +++ b/unittests/scans/trivy/severity_prio.json @@ -0,0 +1,148 @@ +{ + "SchemaVersion": 2, + "ArtifactName": "sbom.json", + "ArtifactType": "cyclonedx", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "requirements.txt", + "Class": "lang-pkgs", + "Type": "pip", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2023-46218", + "PkgID": "curl@7.81.0-1ubuntu1.14", + "PkgName": "curl", + "PkgIdentifier": { + "PURL": "pkg:deb/ubuntu/curl@7.81.0-1ubuntu1.14?arch=amd64\u0026distro=ubuntu-22.04", + "UID": "43a41104920d137" + }, + "InstalledVersion": "7.81.0-1ubuntu1.14", + "FixedVersion": "7.81.0-1ubuntu1.15", + "Status": "fixed", + "Layer": { + "DiffID": "sha256:3a9073a4d18e5ed2ae6f9fd9fee81ea43774907ce603ba955bba8fc0819aa250" + }, + "SeveritySource": "ubuntu", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2023-46218", + "DataSource": { + "ID": "ubuntu", + "Name": "Ubuntu CVE Tracker", + "URL": "https://git.launchpad.net/ubuntu-cve-tracker" + }, + "Title": "curl: information disclosure by exploiting a mixed case flaw", + "Description": "This flaw allows a malicious HTTP server to set \"super cookies\" in curl that\nare then passed back to more origins than what is otherwise allowed or\npossible. This allows a site to set cookies that then would get sent to\ndifferent and unrelated sites and domains.\n\nIt could do this by exploiting a mixed case flaw in curl's function that\nverifies a given cookie domain against the Public Suffix List (PSL). For\nexample a cookie could be set with `domain=co.UK` when the URL used a lower\ncase hostname `curl.co.uk`, even though `co.uk` is listed as a PSL domain.", + "Severity": "LOW", + "VendorSeverity": { + "alma": 2, + "amazon": 2, + "azure": 2, + "cbl-mariner": 2, + "nvd": 2, + "oracle-oval": 2, + "photon": 2, + "redhat": 2, + "rocky": 2, + "ubuntu": 2 + }, + "CVSS": { + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", + "V3Score": 6.5 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N", + "V3Score": 5.3 + } + }, + "References": [ + "https://access.redhat.com/errata/RHSA-2024:1129", + "https://access.redhat.com/security/cve/CVE-2023-46218", + "https://bugzilla.redhat.com/2252030", + "https://bugzilla.redhat.com/show_bug.cgi?id=2196793", + "https://bugzilla.redhat.com/show_bug.cgi?id=2240033", + "https://bugzilla.redhat.com/show_bug.cgi?id=2241938", + "https://bugzilla.redhat.com/show_bug.cgi?id=2252030", + "https://curl.se/docs/CVE-2023-46218.html", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-28322", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-38546", + "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-46218", + "https://errata.almalinux.org/9/ALSA-2024-1129.html", + "https://errata.rockylinux.org/RLSA-2024:1601", + "https://hackerone.com/reports/2212193", + "https://linux.oracle.com/cve/CVE-2023-46218.html", + "https://linux.oracle.com/errata/ELSA-2024-1601.html", + "https://lists.debian.org/debian-lts-announce/2023/12/msg00015.html", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/3ZX3VW67N4ACRAPMV2QS2LVYGD7H2MVE/", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/UOGXU25FMMT2X6UUITQ7EZZYMJ42YWWD/", + "https://nvd.nist.gov/vuln/detail/CVE-2023-46218", + "https://security.netapp.com/advisory/ntap-20240125-0007/", + "https://ubuntu.com/security/notices/USN-6535-1", + "https://ubuntu.com/security/notices/USN-6641-1", + "https://www.cve.org/CVERecord?id=CVE-2023-46218", + "https://www.debian.org/security/2023/dsa-5587" + ], + "PublishedDate": "2023-12-07T01:15:07.16Z", + "LastModifiedDate": "2025-02-13T18:15:33.843Z" + }, + { + "VulnerabilityID": "CVE-2023-37920", + "PkgName": "certifi", + "InstalledVersion": "2022.5.18.1", + "FixedVersion": "2023.7.22", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2023-37920", + "Ref": "pkg:pypi/certifi@2022.5.18.1", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "python-certifi: Removal of e-Tugra root certificate", + "Description": "Certifi is a curated collection of Root Certificates for validating the trustworthiness of SSL certificates while verifying the identity of TLS hosts. Certifi prior to version 2023.07.22 recognizes \"e-Tugra\" root certificates. e-Tugra's root certificates were subject to an investigation prompted by reporting of security issues in their systems. Certifi 2023.07.22 removes root certificates from \"e-Tugra\" from the root store.", + "Severity": "CRITICAL", + "CweIDs": ["CWE-345"], + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "V3Score": 7.5 + }, + "nvd": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", + "V3Score": 7.5 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2023-37920", + "https://github.com/certifi/python-certifi", + "https://github.com/certifi/python-certifi/commit/8fb96ed81f71e7097ed11bc4d9b19afd7ea5c909", + "https://github.com/certifi/python-certifi/security/advisories/GHSA-xqr8-7jwr-rhp7", + "https://github.com/pypa/advisory-database/tree/main/vulns/certifi/PYSEC-2023-135.yaml", + "https://groups.google.com/a/mozilla.org/g/dev-security-policy/c/C-HrP1SEq1A", + "https://lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/5EX6NG7WUFNUKGFHLM35KHHU3GAKXRTG/", + "https://nvd.nist.gov/vuln/detail/CVE-2023-37920", + "https://www.cve.org/CVERecord?id=CVE-2023-37920" + ], + "PublishedDate": "2023-07-25T21:15:00Z", + "LastModifiedDate": "2023-08-12T06:16:00Z" + } + ] + } + ] +} diff --git a/unittests/test_system_settings.py b/unittests/test_system_settings.py index d2e25630b0c..e988d887bcc 100644 --- a/unittests/test_system_settings.py +++ b/unittests/test_system_settings.py @@ -1,7 +1,12 @@ -from django.test import TestCase, override_settings +from unittest.mock import Mock + +from django.db import models +from django.http import HttpResponse +from django.test import RequestFactory, TestCase, override_settings from django.urls import reverse from django.utils.timezone import now +from dojo.middleware import DojoSytemSettingsMiddleware from dojo.models import ( Engagement, Finding, @@ -86,3 +91,187 @@ def test_post_request_initializes_form_with_finding_instance(self): data = {"close_reason": "Mitigated", "notes": "Closing this finding"} response = self.client.post(self.url, data) self.assertIn(response.status_code, [200, 302]) + + +class TestSystemSettingsMiddlewareIntegration(DojoTestCase): + + """Integration tests for DojoSytemSettingsMiddleware using RequestFactory.""" + + def setUp(self): + """Set up test environment.""" + super().setUp() + self.factory = RequestFactory() + # Ensure signal is connected + models.signals.post_save.disconnect(DojoSytemSettingsMiddleware.cleanup, sender=System_Settings) + models.signals.post_save.connect(DojoSytemSettingsMiddleware.cleanup, sender=System_Settings) + + def test_middleware_loads_cache_on_request(self): + """Test that middleware loads settings into cache when processing a request.""" + # Ensure cache is empty + DojoSytemSettingsMiddleware.cleanup() + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + # Create middleware with mock get_response + mock_response = HttpResponse("OK") + mock_get_response = Mock(return_value=mock_response) + middleware = DojoSytemSettingsMiddleware(mock_get_response) + + # Create a request + request = self.factory.get("/test/") + + # Process request through middleware + response = middleware(request) + + # Verify response is returned + self.assertEqual(response, mock_response) + mock_get_response.assert_called_once_with(request) + + # Verify cache was populated during request processing + # Note: cache should be cleaned up after request, but we can check during processing + # Since cleanup happens in finally block, cache should be empty after __call__ returns + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + def test_middleware_cleans_up_cache_after_request(self): + """Test that middleware cleans up cache after request processing.""" + # Manually load cache first + DojoSytemSettingsMiddleware.load() + self.assertIsNotNone(DojoSytemSettingsMiddleware.get_system_settings()) + + # Create middleware + middleware = DojoSytemSettingsMiddleware(lambda _r: HttpResponse("OK")) + + # Process request + request = self.factory.get("/test/") + middleware(request) + + # Verify cache is cleaned up after request + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + def test_middleware_cleans_up_cache_on_exception(self): + """Test that middleware cleans up cache even when exception occurs.""" + # Load cache first + DojoSytemSettingsMiddleware.load() + self.assertIsNotNone(DojoSytemSettingsMiddleware.get_system_settings()) + + # Create middleware that raises an exception + def failing_get_response(request): + msg = "Test exception" + raise ValueError(msg) + + middleware = DojoSytemSettingsMiddleware(failing_get_response) + + # Process request - should raise exception + request = self.factory.get("/test/") + with self.assertRaises(ValueError): + middleware(request) + + # Verify cache is cleaned up even after exception + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + def test_middleware_process_exception_cleans_up_cache(self): + """Test that process_exception method cleans up cache.""" + # Load cache first + DojoSytemSettingsMiddleware.load() + self.assertIsNotNone(DojoSytemSettingsMiddleware.get_system_settings()) + + # Create middleware + middleware = DojoSytemSettingsMiddleware(lambda _r: HttpResponse("OK")) + + # Call process_exception directly + request = self.factory.get("/test/") + exception = ValueError("Test exception") + middleware.process_exception(request, exception) + + # Verify cache is cleaned up + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + def test_middleware_cache_isolation_between_requests(self): + """Test that cache is isolated between requests (thread-local).""" + # Create middleware + middleware = DojoSytemSettingsMiddleware(lambda _r: HttpResponse("OK")) + + # First request + request1 = self.factory.get("/test1/") + middleware(request1) + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + # Second request - cache should be empty at start + request2 = self.factory.get("/test2/") + middleware(request2) + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + def test_middleware_cache_during_request_processing(self): + """Test that cache is available during request processing.""" + # Track if cache was available during request + cache_available_during_request = [] + + def get_response_with_cache_check(request): + # Check if cache is available during request processing + cached = DojoSytemSettingsMiddleware.get_system_settings() + cache_available_during_request.append(cached is not None) + return HttpResponse("OK") + + middleware = DojoSytemSettingsMiddleware(get_response_with_cache_check) + + # Process request + request = self.factory.get("/test/") + middleware(request) + + # Verify cache was available during request processing + self.assertTrue(cache_available_during_request[0], "Cache should be available during request processing") + + # But cleaned up after request + self.assertIsNone(DojoSytemSettingsMiddleware.get_system_settings()) + + def test_multiple_get_calls_use_cache(self): + """Test that multiple calls to System_Settings.objects.get() use cache instead of multiple DB queries.""" + # Ensure cache is empty + DojoSytemSettingsMiddleware.cleanup() + + # First call should hit DB (cache is empty) + with self.assertNumQueries(1): + settings1 = System_Settings.objects.get() + + # Load into cache via middleware + DojoSytemSettingsMiddleware.load() + + # Now multiple calls should use cache (no additional DB queries) + with self.assertNumQueries(0): + settings2 = System_Settings.objects.get() + settings3 = System_Settings.objects.get() + settings4 = System_Settings.objects.get() + + # All calls should return the same cached object instance + self.assertEqual(settings1.id, settings2.id) + self.assertEqual(settings2.id, settings3.id) + self.assertEqual(settings3.id, settings4.id) + # Verify they're the same object instance (same memory address) + self.assertIs(settings2, settings3) + self.assertIs(settings3, settings4) + + def test_multiple_get_calls_within_request_use_cache(self): + """Test that multiple get() calls within a single request use cache.""" + retrieved_settings = [] + + def get_response_with_multiple_gets(request): + # Make multiple calls to get() during request processing + retrieved_settings.append(System_Settings.objects.get()) + retrieved_settings.append(System_Settings.objects.get()) + retrieved_settings.append(System_Settings.objects.get()) + return HttpResponse("OK") + + middleware = DojoSytemSettingsMiddleware(get_response_with_multiple_gets) + + # Process request - should only hit DB once (when loading cache) + # Then all subsequent get() calls should use cache + request = self.factory.get("/test/") + with self.assertNumQueries(1): # Only one query to load settings into cache + middleware(request) + + # Verify we got 3 settings objects + self.assertEqual(len(retrieved_settings), 3) + + # All should be the same cached instance + self.assertIs(retrieved_settings[0], retrieved_settings[1]) + self.assertIs(retrieved_settings[1], retrieved_settings[2]) + self.assertEqual(retrieved_settings[0].id, retrieved_settings[1].id) diff --git a/unittests/tools/test_trivy_parser.py b/unittests/tools/test_trivy_parser.py index 0ae377d33e6..f6c08156fee 100644 --- a/unittests/tools/test_trivy_parser.py +++ b/unittests/tools/test_trivy_parser.py @@ -319,3 +319,21 @@ def test_cvss_severity_sources(self): self.assertEqual("High", finding.severity) self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", finding.cvssv3) self.assertEqual(7.5, finding.cvssv3_score) + + def test_severity_prio(self): + # this tests issue #13647. The unittest file is just a copy of cvss_severity_source.json with edited severities + with sample_path("severity_prio.json").open(encoding="utf-8") as test_file: + parser = TrivyParser() + findings = parser.get_findings(test_file, Test()) + self.assertEqual(len(findings), 2) + with self.subTest("SeveritySource matches the CVSS entry"): + finding = findings[0] + self.assertEqual("Low", finding.severity) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N", finding.cvssv3) + self.assertEqual(6.5, finding.cvssv3_score) + + with self.subTest("SeveritySource does not match the CVSS entry"): + finding = findings[1] + self.assertEqual("Critical", finding.severity) + self.assertEqual("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N", finding.cvssv3) + self.assertEqual(7.5, finding.cvssv3_score)