diff --git a/components/package.json b/components/package.json index bc3de215a48..338888b224a 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.55.2", + "version": "2.55.3", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/docs/assets/images/OS_RA_image1.png b/docs/assets/images/OS_RA_image1.png new file mode 100644 index 00000000000..2e3ee96df15 Binary files /dev/null and b/docs/assets/images/OS_RA_image1.png differ diff --git a/docs/assets/images/OS_RA_image2.png b/docs/assets/images/OS_RA_image2.png new file mode 100644 index 00000000000..0b52ed32d7f Binary files /dev/null and b/docs/assets/images/OS_RA_image2.png differ diff --git a/docs/assets/images/OS_RA_image3.png b/docs/assets/images/OS_RA_image3.png new file mode 100644 index 00000000000..90a75fb408e Binary files /dev/null and b/docs/assets/images/OS_RA_image3.png differ diff --git a/docs/assets/images/OS_RA_image4.png b/docs/assets/images/OS_RA_image4.png new file mode 100644 index 00000000000..1fa0f32c2f0 Binary files /dev/null and b/docs/assets/images/OS_RA_image4.png differ diff --git a/docs/assets/images/OS_RA_image5.png b/docs/assets/images/OS_RA_image5.png new file mode 100644 index 00000000000..0803d4ae8c7 Binary files /dev/null and b/docs/assets/images/OS_RA_image5.png differ diff --git a/docs/assets/images/OS_RA_image6.png b/docs/assets/images/OS_RA_image6.png new file mode 100644 index 00000000000..8250ab66dd1 Binary files /dev/null and b/docs/assets/images/OS_RA_image6.png differ diff --git a/docs/assets/images/OS_RA_image7.png b/docs/assets/images/OS_RA_image7.png new file mode 100644 index 00000000000..dd835f7c6ad Binary files /dev/null and b/docs/assets/images/OS_RA_image7.png differ diff --git a/docs/assets/images/OS_RA_image8.png b/docs/assets/images/OS_RA_image8.png new file mode 100644 index 00000000000..e710f7f6872 Binary files /dev/null and b/docs/assets/images/OS_RA_image8.png differ diff --git a/docs/config/_default/hugo.toml b/docs/config/_default/hugo.toml index 0b623542125..c21cf152354 100644 --- a/docs/config/_default/hugo.toml +++ b/docs/config/_default/hugo.toml @@ -1,6 +1,6 @@ title = "DefectDojo Documentation" baseurl = "http://localhost/" -disableAliases = true +disableAliases = false disableHugoGeneratorInject = true disableKinds = ["taxonomy", "term"] enableEmoji = true diff --git a/docs/content/admin/notifications/about_notifications.md b/docs/content/admin/notifications/about_notifications.md index cb27b5e0995..34462e51fc0 100644 --- a/docs/content/admin/notifications/about_notifications.md +++ b/docs/content/admin/notifications/about_notifications.md @@ -1,8 +1,9 @@ --- title: "About Notifications & 🔔 Alerts" description: "Learn about notifications, in-app alerts" +aliases: + - /en/customize_dojo/notifications/about_notifications --- - DefectDojo keeps you up to date in a variety of ways. Notifications can be sent for upcoming Engagements, user Mentions, SLA expiry, and other events in the software. This article contains an overview of notifications at both System\-wide and Personal levels. diff --git a/docs/content/admin/notifications/configure_personal_notifs.md b/docs/content/admin/notifications/configure_personal_notifs.md index 1aed2b41efc..cae2d03b2ef 100644 --- a/docs/content/admin/notifications/configure_personal_notifs.md +++ b/docs/content/admin/notifications/configure_personal_notifs.md @@ -1,8 +1,9 @@ --- title: "Set Personal Notifications" description: "Configure notifications for a personal account" +aliases: + - /en/customize_dojo/notifications/configure_personal_notifs --- - ## Configure Personal notifications Personal Notifications are sent in addition to System\-Wide Notifications, and will apply to any Product, Product Type or other data type that you have access to. Personal Notification preferences only apply to a single user, and can only be set on the account which is configuring them. diff --git a/docs/content/admin/notifications/configure_system_notifs.md b/docs/content/admin/notifications/configure_system_notifs.md index cc09adb3a45..24dcca2c810 100644 --- a/docs/content/admin/notifications/configure_system_notifs.md +++ b/docs/content/admin/notifications/configure_system_notifs.md @@ -1,8 +1,9 @@ --- title: "Set System-Wide Notifications" description: "How to configure Personal & System notifications" +aliases: + - /en/customize_dojo/notifications/configure_system_notifs --- - DefectDojo has two different kinds of notifications: **Personal** (sent to a single account) and **System** (which are sent to all users). Both an account’s Personal Notifications and the global System Notifications can be configured from the same page: **⚙️Configuration \> Notifications** in the sidebar. diff --git a/docs/content/admin/notifications/email_slack_teams.md b/docs/content/admin/notifications/email_slack_teams.md index 248defa7b70..665fb34ab42 100644 --- a/docs/content/admin/notifications/email_slack_teams.md +++ b/docs/content/admin/notifications/email_slack_teams.md @@ -1,8 +1,9 @@ --- title: "Set up Email, Slack or Teams notifications" description: "Set up Microsoft Teams to receive notifications" +aliases: + - /en/customize_dojo/notifications/email_slack_teams --- - **You will need Superuser access to use the System Settings page, which is required to complete this process.** Notifications can be pushed to Slack or Teams when certain events trigger in DefectDojo. diff --git a/docs/content/admin/user_management/about_perms_and_roles.md b/docs/content/admin/user_management/about_perms_and_roles.md index 05a9da19acd..fdc27288eb9 100644 --- a/docs/content/admin/user_management/about_perms_and_roles.md +++ b/docs/content/admin/user_management/about_perms_and_roles.md @@ -2,8 +2,9 @@ title: "Permissions in DefectDojo" description: "Summary of all DefectDojo permission options, in detail" weight: 2 +aliases: + - /en/customize_dojo/user_management/about_perms_and_roles --- - If you have a team of users working in DefectDojo, it's important to set up Role\-Based Access Control (RBAC) appropriately so that users can only access specific data. Security data is highly sensitive, and DefectDojo's options for access control allow you to be specific about each team member’s access to information. This article is an overview of how permissions in DefectDojo work. If you would prefer to see a detailed breakdown of **each action** that can be controlled by Permissions, see our **[Permissions Chart](../user_permission_chart/)** article. diff --git a/docs/content/admin/user_management/audit_logging.md b/docs/content/admin/user_management/audit_logging.md index eb4a2c8cc63..3ee27209f82 100644 --- a/docs/content/admin/user_management/audit_logging.md +++ b/docs/content/admin/user_management/audit_logging.md @@ -2,8 +2,9 @@ title: "Audit Logs" description: "Access audit logs for DefectDojo objects" weight: 1 +aliases: + - /en/customize_dojo/user_management/audit_logging --- - Audit logs for DefectDojo can be accessed in a few different ways. ## Individual Object Logs diff --git a/docs/content/admin/user_management/configure_sso.md b/docs/content/admin/user_management/configure_sso.md index 67d804d6e7d..864b852b11f 100644 --- a/docs/content/admin/user_management/configure_sso.md +++ b/docs/content/admin/user_management/configure_sso.md @@ -2,8 +2,9 @@ title: "SSO Configuration (OAuth, SAML)" description: "Sign in to DefectDojo using OAuth or SAML login options" pro-feature: true +aliases: + - /en/customize_dojo/user_management/configure_sso --- - Users can connect to DefectDojo with a Username and Password, but if you prefer, you can allow users to authenticate using a Single Sign\-On or SSO method. You can set up DefectDojo to work with your own SAML Identity Provider, but we also support many OAuth methods for authentication: * **[Auth0](./#auth0-setup)** diff --git a/docs/content/admin/user_management/create_user_group.md b/docs/content/admin/user_management/create_user_group.md index 3c68e5cf3a5..1a4f9ed7305 100644 --- a/docs/content/admin/user_management/create_user_group.md +++ b/docs/content/admin/user_management/create_user_group.md @@ -2,8 +2,9 @@ title: "Share permissions: User Groups" description: "Share and maintain permissions for many users" weight: 3 +aliases: + - /en/customize_dojo/user_management/create_user_group --- - If you have a significant number of DefectDojo users, you may want to create one or more **Groups**, in order to set the same Role\-Based Access Control (RBAC) rules for many users simultaneously. Only Superusers can create User Groups. Groups can work in multiple ways: diff --git a/docs/content/admin/user_management/pro_permissions_overhaul.md b/docs/content/admin/user_management/pro_permissions_overhaul.md index ba174ac7eee..6aa71094bb6 100644 --- a/docs/content/admin/user_management/pro_permissions_overhaul.md +++ b/docs/content/admin/user_management/pro_permissions_overhaul.md @@ -3,6 +3,8 @@ title: "Set Permissions in Pro" description: "Overhaul, pro feature" weight: 3 audience: pro +aliases: + - /en/customize_dojo/user_management/pro_permissions_overhaul --- ## Introduction to Permission Types diff --git a/docs/content/admin/user_management/set_user_permissions.md b/docs/content/admin/user_management/set_user_permissions.md index 538cde02d26..d783e79d791 100644 --- a/docs/content/admin/user_management/set_user_permissions.md +++ b/docs/content/admin/user_management/set_user_permissions.md @@ -3,8 +3,9 @@ title: "Set a User's permissions" description: "How to grant Roles & Permissions to a user, as well as superuser status" weight: 2 audience: opensource +aliases: + - /en/customize_dojo/user_management/set_user_permissions --- - ## Introduction to Permission Types Individual users have four different kinds of permission that they can be assigned: diff --git a/docs/content/admin/user_management/user_permission_chart.md b/docs/content/admin/user_management/user_permission_chart.md index b3532e25acf..15a63d1afae 100644 --- a/docs/content/admin/user_management/user_permission_chart.md +++ b/docs/content/admin/user_management/user_permission_chart.md @@ -2,8 +2,9 @@ title: "Action permission charts" description: "All user permissions in detail" weight: 4 +aliases: + - /en/customize_dojo/user_management/user_permission_chart --- - ## Role Permission Chart This chart is intended to list all permissions related to a Product or Product Type, as well as which permissions are available to each role. diff --git a/docs/content/asset_modelling/hierarchy/OS__sla_configuration.md b/docs/content/asset_modelling/hierarchy/OS__sla_configuration.md index 5dc7957adb5..87cf294abae 100644 --- a/docs/content/asset_modelling/hierarchy/OS__sla_configuration.md +++ b/docs/content/asset_modelling/hierarchy/OS__sla_configuration.md @@ -3,8 +3,9 @@ title: "SLA Configuration" description: "Configure Service Level Agreements for different Products" weight: 2 audience: opensource +aliases: + - /en/working_with_findings/sla_configuration --- - Each Product in DefectDojo can have its own Service Level Agreement (SLA) configuration, which represents the days your organization has to remediate or otherwise manage a Finding. SLA can be set based on either **[Finding Severity](/asset_modelling/hierarchy/product_hierarchy/#findings)** or **[Finding Risk](/asset_modelling/hierarchy/pro__priority_sla/)** (in DefectDojo Pro). diff --git a/docs/content/asset_modelling/hierarchy/OS__source-code-repositories.md b/docs/content/asset_modelling/hierarchy/OS__source-code-repositories.md index dfd25c66c6b..a6c41c90d79 100644 --- a/docs/content/asset_modelling/hierarchy/OS__source-code-repositories.md +++ b/docs/content/asset_modelling/hierarchy/OS__source-code-repositories.md @@ -4,8 +4,9 @@ description: "Integration of repositories to navigate to the location of finding draft: false weight: 5 audience: opensource +aliases: + - /en/working_with_findings/organizing_engagements_tests/source-code-repositories --- - Certain tools (particularly SAST tools) will include the associated file name and line number in vulnerability data. If the repository of the source code is specified in the Engagement, DefectDojo will present the filepath as a link and the user can navigate directly to the location of the vulnerability. ## Setting the repository in the Engagement and Test diff --git a/docs/content/asset_modelling/hierarchy/PRO__assets_organizations.md b/docs/content/asset_modelling/hierarchy/PRO__assets_organizations.md index 65c56d584b5..c1588159dce 100644 --- a/docs/content/asset_modelling/hierarchy/PRO__assets_organizations.md +++ b/docs/content/asset_modelling/hierarchy/PRO__assets_organizations.md @@ -3,8 +3,9 @@ title: "Assets and Organization structure" description: "DefectDojo Pro - Product Hierarchy Overhaul" audience: pro weight: 1 +aliases: + - /en/working_with_findings/organizing_engagements_tests/pro_assets_organizations --- - DefectDojo Pro is extending the Product/Product Type object classes to provide greater flexibility with the data model. Currently, this feature is in Beta. Pro users who are interested in opting in can do so by emailing [support@defectdojo.com](mailto:support@defectdojo.com). diff --git a/docs/content/asset_modelling/hierarchy/PRO__priority_sla.md b/docs/content/asset_modelling/hierarchy/PRO__priority_sla.md index 75abb100e54..5baf8753d9a 100644 --- a/docs/content/asset_modelling/hierarchy/PRO__priority_sla.md +++ b/docs/content/asset_modelling/hierarchy/PRO__priority_sla.md @@ -3,6 +3,9 @@ title: "Assign Priority, Risk and SLAs" description: "How DefectDojo ranks your Findings" weight: 1 audience: pro +aliases: + - /en/working_with_findings/finding_priority + - /en/working_with_findings/priority_adjustments --- ![image](images/pro_finding_priority.png) diff --git a/docs/content/asset_modelling/hierarchy/product_health_grade.md b/docs/content/asset_modelling/hierarchy/product_health_grade.md index 30e883f5e3c..53961251fd3 100644 --- a/docs/content/asset_modelling/hierarchy/product_health_grade.md +++ b/docs/content/asset_modelling/hierarchy/product_health_grade.md @@ -1,8 +1,9 @@ --- title: "Product Health Grade" description: "How DefectDojo calculates a Product Health Grade" +aliases: + - /en/working_with_findings/organizing_engagements_tests/product_health_grade --- - DefectDojo can calculate a grade for your Products based on the amount of Findings contained within. Grades are ranked from A \- F. Note that only Active \& Verified Findings contribute to a Product Grade \- unverified Findings will not have an impact. diff --git a/docs/content/asset_modelling/hierarchy/product_hierarchy.md b/docs/content/asset_modelling/hierarchy/product_hierarchy.md index 77458d98df1..36e0149b34a 100644 --- a/docs/content/asset_modelling/hierarchy/product_hierarchy.md +++ b/docs/content/asset_modelling/hierarchy/product_hierarchy.md @@ -3,8 +3,9 @@ title: "Product Hierarchy: Overview" description: "Understand Product Types, Products, Engagements, Tests and Findings" weight: 1 audience: opensource +aliases: + - /en/working_with_findings/organizing_engagements_tests/product_hierarchy --- - DefectDojo uses five main data classes to organize your work: **Product Types, Products**, **Engagements**, **Tests**, and **Findings**. DefectDojo is made to be flexible to conform to your team, rather than making your team conform to the tool. You'll be able to design a robust, adaptable workspace once you understand how these data classes can be used to organize your work. diff --git a/docs/content/asset_modelling/tags/PRO__tagging_objects copy.md b/docs/content/asset_modelling/tags/PRO__tagging_objects copy.md index cc729ee5141..bb9e69720b5 100644 --- a/docs/content/asset_modelling/tags/PRO__tagging_objects copy.md +++ b/docs/content/asset_modelling/tags/PRO__tagging_objects copy.md @@ -5,8 +5,9 @@ draft: false weight: 2 exclude_search: false audience: pro +aliases: + - /en/working_with_findings/organizing_engagements_tests/tagging_objects --- - Tags are ideal for grouping objects in a manner that can be filtered out into smaller, more digestible chunks. They can be used to denote status, or to create custom sets of Product Type, Products, Engagements or Findings across the data model. In DefectDojo, tags are a first class citizen and are recognized as the facilitators diff --git a/docs/content/automation/api/api-v2-docs.md b/docs/content/automation/api/api-v2-docs.md index df8ba8b6d70..1d55a413a7b 100644 --- a/docs/content/automation/api/api-v2-docs.md +++ b/docs/content/automation/api/api-v2-docs.md @@ -3,8 +3,9 @@ title: "DefectDojo API v2" description: "DefectDojo's API lets you automate tasks, e.g. uploading scan reports in CI/CD pipelines." draft: false weight: 2 +aliases: + - /en/api/api-v2-docs --- - DefectDojo\'s API is created using [Django Rest Framework](http://www.django-rest-framework.org/). The documentation of each endpoint is available within each DefectDojo installation at diff --git a/docs/content/automation/rules_engine/about.md b/docs/content/automation/rules_engine/about.md index 86700c3853c..150d889ae04 100644 --- a/docs/content/automation/rules_engine/about.md +++ b/docs/content/automation/rules_engine/about.md @@ -3,8 +3,9 @@ title: "Rules Engine Automation" description: "Working with Rules Engine Automation" weight: 1 audience: pro +aliases: + - /en/customize_dojo/rules_engine --- - Note: Rules Engine is a DefectDojo Pro-only feature. DefectDojo's Rules Engine allows you to build custom workflows and bulk actions to handle Findings and other objects. Rules Engine allows you to build automated actions that are triggered when an object matches a Rule. diff --git a/docs/content/get_started/about/PRO__new_user_checklist.md b/docs/content/get_started/about/PRO__new_user_checklist.md index bb321882b24..3b6e7b2726d 100644 --- a/docs/content/get_started/about/PRO__new_user_checklist.md +++ b/docs/content/get_started/about/PRO__new_user_checklist.md @@ -4,6 +4,8 @@ description: "Get Started With DefectDojo" draft: "false" weight: 3 audience: pro +aliases: + - /en/about_defectdojo/new_user_checklist --- The essence of DefectDojo is to import security data, organize it, and present it to the folks who need to know. Here's a quick reference you can use to ensure successful implementation, from a blank canvas to a fully functional app. diff --git a/docs/content/get_started/about/about_defectdojo.md b/docs/content/get_started/about/about_defectdojo.md index d86ae4c646a..5e9fb0c4bc6 100644 --- a/docs/content/get_started/about/about_defectdojo.md +++ b/docs/content/get_started/about/about_defectdojo.md @@ -4,8 +4,9 @@ date: 2021-02-02T20:46:29+01:00 draft: false type: docs weight: 1 +aliases: + - /en/about_defectdojo/about_docs --- - ![image](images/dashboard.png) diff --git a/docs/content/get_started/about/faq.md b/docs/content/get_started/about/faq.md index 4f668222763..9132248fd89 100644 --- a/docs/content/get_started/about/faq.md +++ b/docs/content/get_started/about/faq.md @@ -4,8 +4,9 @@ description: "DefectDojo FAQ" draft: "false" weight: 2 chapter: true +aliases: + - /en/about_defectdojo/faq --- - Here are some frequently asked questions about working with DefectDojo - both in DefectDojo Pro or DefectDojo OS. ## General Questions diff --git a/docs/content/get_started/about/ui_pro_vs_os.md b/docs/content/get_started/about/ui_pro_vs_os.md index 93bb7204886..e01fa48fbef 100644 --- a/docs/content/get_started/about/ui_pro_vs_os.md +++ b/docs/content/get_started/about/ui_pro_vs_os.md @@ -4,8 +4,9 @@ description: "Working with different UIs in DefectDojo" draft: "false" weight: 5 audience: pro +aliases: + - /en/about_defectdojo/ui_pro_vs_os --- - In late 2023, DefectDojo Inc. released a new UI for DefectDojo Pro, which is now the default UI for this edition. The Pro UI brings the following enhancements to DefectDojo: diff --git a/docs/content/get_started/common_use_cases/common_use_cases.md b/docs/content/get_started/common_use_cases/common_use_cases.md index de2d3c143e5..0565035ddfd 100644 --- a/docs/content/get_started/common_use_cases/common_use_cases.md +++ b/docs/content/get_started/common_use_cases/common_use_cases.md @@ -4,8 +4,9 @@ description: "Use Cases and examples" draft: "false" weight: 2 chapter: true +aliases: + - /en/about_defectdojo/examples_of_use --- - This article is based on DefectDojo Inc's February 2025 Office Hours: "Tackling Common Use Cases". diff --git a/docs/content/get_started/open_source/architecture.md b/docs/content/get_started/open_source/architecture.md index e34e29d089c..971291a9afc 100644 --- a/docs/content/get_started/open_source/architecture.md +++ b/docs/content/get_started/open_source/architecture.md @@ -4,8 +4,9 @@ description: "The DefectDojo platform consists of several components that work t draft: false weight: 1 audience: opensource +aliases: + - /en/open_source/installation/architecture --- - ![image](images/dd-architecture.png) ## NGINX diff --git a/docs/content/get_started/open_source/configuration.md b/docs/content/get_started/open_source/configuration.md index e97709392e4..df7bcb14e63 100644 --- a/docs/content/get_started/open_source/configuration.md +++ b/docs/content/get_started/open_source/configuration.md @@ -4,8 +4,9 @@ description: "DefectDojo is highly configurable." draft: false weight: 2 audience: opensource +aliases: + - /en/open_source/installation/configuration --- - ## dojo/settings/settings.dist.py The main settings are stored in [`dojo/settings/settings.dist.py`](https://github.com/DefectDojo/django-DefectDojo/blob/master/dojo/settings/settings.dist.py). It is great to use this file as a reference for what can be configured, but it shouldn\'t be edited directly, because changes will be overwritten when updating DefectDojo. There are several methods to change the default settings: diff --git a/docs/content/get_started/open_source/installation.md b/docs/content/get_started/open_source/installation.md index 105ac4f119a..0515e41419b 100644 --- a/docs/content/get_started/open_source/installation.md +++ b/docs/content/get_started/open_source/installation.md @@ -4,8 +4,9 @@ description: "DefectDojo supports various installation options." draft: false weight: 1 audience: opensource +aliases: + - /en/open_source/installation/installation --- - ## **Recommended Options** --- diff --git a/docs/content/get_started/open_source/running-in-production.md b/docs/content/get_started/open_source/running-in-production.md index ef96670d11b..e22e9b69516 100644 --- a/docs/content/get_started/open_source/running-in-production.md +++ b/docs/content/get_started/open_source/running-in-production.md @@ -4,8 +4,9 @@ description: "For use in Production environments, performance tweaks and backups draft: false weight: 4 audience: opensource +aliases: + - /en/open_source/installation/running-in-production --- - ## Production Use (with Docker compose) The docker-compose.yml file in this repository is fully functional to evaluate DefectDojo in your local environment. diff --git a/docs/content/get_started/pro/cloud/additional-cloud-instance.md b/docs/content/get_started/pro/cloud/additional-cloud-instance.md index 806fc9cb9b2..9921fede8ee 100644 --- a/docs/content/get_started/pro/cloud/additional-cloud-instance.md +++ b/docs/content/get_started/pro/cloud/additional-cloud-instance.md @@ -3,8 +3,9 @@ title: "Set up an additional Cloud instance" description: "Add a test, dev, or other DefectDojo instance to your account" weight: 3 audience: pro +aliases: + - /en/cloud_management/additional-cloud-instance --- - The process for adding a second Cloud instance is more or less the same as adding your first instance. This guide assumes you've already set up your initial DefectDojo server, and have an agreement with our Sales team to add another instance. If you have not already requested an additional Cloud instance, please contact [info@defectdojo.com](mailto:info@defectdojo.com) before proceeding. diff --git a/docs/content/get_started/pro/cloud/connectivity-troubleshooting.md b/docs/content/get_started/pro/cloud/connectivity-troubleshooting.md index ed38ce94939..384640f3893 100644 --- a/docs/content/get_started/pro/cloud/connectivity-troubleshooting.md +++ b/docs/content/get_started/pro/cloud/connectivity-troubleshooting.md @@ -3,8 +3,9 @@ title: "Connectivity Troubleshooting" description: "Reconnect to your DefectDojo Instance" weight: 2 audience: pro +aliases: + - /en/cloud_management/connectivity-troubleshooting --- - If you have difficulty accessing your DefectDojo instance, here are some steps you can follow to get reconnected: ## I can access the site, but I can't log in diff --git a/docs/content/get_started/pro/cloud/using-cloud-manager.md b/docs/content/get_started/pro/cloud/using-cloud-manager.md index 73fe4eca735..7eef9f8802b 100644 --- a/docs/content/get_started/pro/cloud/using-cloud-manager.md +++ b/docs/content/get_started/pro/cloud/using-cloud-manager.md @@ -4,8 +4,9 @@ description: "Manage your subscription and account settings" weight: 1 collapsed: true audience: pro +aliases: + - /en/cloud_management/using-cloud-manager --- - Logging into DefectDojo's Cloud Manager allows you to configure your account settings and manage your subscription with DefectDojo Cloud. ## **New Subscription** diff --git a/docs/content/get_started/pro/pro_features.md b/docs/content/get_started/pro/pro_features.md index afdacfb3e71..9d5d7600545 100644 --- a/docs/content/get_started/pro/pro_features.md +++ b/docs/content/get_started/pro/pro_features.md @@ -6,8 +6,9 @@ weight: 4 chapter: true exclude_search: true audience: pro +aliases: + - /en/about_defectdojo/pro_features --- - Here is a list of DefectDojo Pro’s many additional features, along with links to documentation to see them in action: ## Improved UX diff --git a/docs/content/help/contact_sales.md b/docs/content/help/contact_sales.md index 310b262528e..687b6d29ecc 100644 --- a/docs/content/help/contact_sales.md +++ b/docs/content/help/contact_sales.md @@ -4,8 +4,9 @@ description: "How to request and work with a trial of DefectDojo Cloud" draft: "false" weight: 6 pro-feature: true +aliases: + - /en/about_defectdojo/request_a_trial --- - If your team requires an on-premise DefectDojo installation, please connect with our Sales team by emailing → [hello@defectdojo.com](mailto:hello@defectdojo.com) . This trial setup process only applies to DefectDojo Cloud users. All DefectDojo plans include a free 2-week trial, which you can use to evaluate our software. DefectDojo Trial instances are fully-featured and can be immediately converted into paid instances by our team; no need to set everything up again, or reupload any data when your trial period ends. diff --git a/docs/content/help/contact_support.md b/docs/content/help/contact_support.md index 02d8d8c8d9d..89af196c0c4 100644 --- a/docs/content/help/contact_support.md +++ b/docs/content/help/contact_support.md @@ -4,8 +4,9 @@ description: "For Pro users: support@defectdojo.com + other options" draft: "false" pro-feature: true weight: 7 +aliases: + - /en/about_defectdojo/contact_defectdojo_support --- - Need help with DefectDojo? Here are some ways to get assistance. ## Open-Source Support diff --git a/docs/content/import_data/import_intro/comparison.md b/docs/content/import_data/import_intro/comparison.md index 3135fc227e5..3a1cdb2d042 100644 --- a/docs/content/import_data/import_intro/comparison.md +++ b/docs/content/import_data/import_intro/comparison.md @@ -2,8 +2,9 @@ title: "Import Method Comparison" description: "Learn how to import data manually, through the API, or via a connector" weight: 1 +aliases: + - /en/connecting_your_tools/import_intro --- - One of the things we understand at DefectDojo is that every company’s security needs are completely different. There is no one-size-fits-all approach. As your organization changes, having a flexible approach is key, and DefectDojo allows you to connect your security tools in a flexible way to match those changes. ## Scan Upload Methods diff --git a/docs/content/import_data/import_intro/import_vs_reimport.md b/docs/content/import_data/import_intro/import_vs_reimport.md index c73e84c86dd..8b21ce9d2a2 100644 --- a/docs/content/import_data/import_intro/import_vs_reimport.md +++ b/docs/content/import_data/import_intro/import_vs_reimport.md @@ -2,8 +2,9 @@ title: "Import vs Reimport" description: "Learn how to import data manually, through the API, or via a connector" weight: 2 +aliases: + - /en/connecting_your_tools/import_scan_files/using_reimport --- - When a Test is created in DefectDojo (either in advance or by importing a scan file), the Test can be extended with new Finding data. For example, let’s say you have a CI/CD pipeline, which is designed to send a new report to DefectDojo every day. Rather than create a new Test or Engagement for each ‘run’ of the pipeline, you could have each report flow into the same Test using **Reimport**. diff --git a/docs/content/import_data/import_scan_files/PRO__import_scan_ui.md b/docs/content/import_data/import_scan_files/PRO__import_scan_ui.md index 741f850c6fa..bf4058ecb17 100644 --- a/docs/content/import_data/import_scan_files/PRO__import_scan_ui.md +++ b/docs/content/import_data/import_scan_files/PRO__import_scan_ui.md @@ -3,8 +3,9 @@ title: "Add Findings form" description: "" weight: 1 audience: pro +aliases: + - /en/connecting_your_tools/import_scan_files/import_scan_ui --- - If you have a brand new DefectDojo instance, the Import Scan Form is a logical first step to learn the software and set up your environment. From this form, you upload a scan file from a supported tool, which will create Findings to represent those vulnerabilities. While filling out the form, you can decide whether to: * Store these Findings under an existing Product Type / Product / Engagement **or** diff --git a/docs/content/import_data/import_scan_files/api_pipeline_modelling.md b/docs/content/import_data/import_scan_files/api_pipeline_modelling.md index dd8e01ed410..f415727df17 100644 --- a/docs/content/import_data/import_scan_files/api_pipeline_modelling.md +++ b/docs/content/import_data/import_scan_files/api_pipeline_modelling.md @@ -1,8 +1,9 @@ --- title: "Import from API" description: "" +aliases: + - /en/connecting_your_tools/import_scan_files/api_pipeline_modelling --- - DefectDojo’s API allows for robust pipeline solutions, which automatically ingest new scans to your instance. Automation like this can take a few different forms: * A daily import which scans your environment on a daily basis, and then imports the results of the scan to DefectDojo (similar to our **Connectors** feature) diff --git a/docs/content/import_data/pro/connectors/about_connectors.md b/docs/content/import_data/pro/connectors/about_connectors.md index b1502a2c714..f3ddf75b540 100644 --- a/docs/content/import_data/pro/connectors/about_connectors.md +++ b/docs/content/import_data/pro/connectors/about_connectors.md @@ -15,8 +15,9 @@ seo: canonical: "" # custom canonical URL (optional) robots: "" # custom robot tags (optional) pro-feature: true +aliases: + - /en/connecting_your_tools/connectors/about_connectors --- - Note: Connectors are a DefectDojo Pro-only feature. DefectDojo allows users to build sophisticated API integrations, and gives users full control over how their vulnerability data is organized. diff --git a/docs/content/import_data/pro/connectors/add_edit_connectors.md b/docs/content/import_data/pro/connectors/add_edit_connectors.md index 1d067e59a51..78f9d3a6263 100644 --- a/docs/content/import_data/pro/connectors/add_edit_connectors.md +++ b/docs/content/import_data/pro/connectors/add_edit_connectors.md @@ -1,8 +1,9 @@ --- title: "Add or Edit a Connector" description: "Connect to a supported security tool" +aliases: + - /en/connecting_your_tools/connectors/add_edit_connectors --- - Note: Connectors are a DefectDojo Pro-only feature. The process for adding and configuring a connector is similar, regardless of the tool you’re trying to connect. However, certain tools may require you to create API keys or complete additional steps. diff --git a/docs/content/import_data/pro/connectors/connectors_tool_reference.md b/docs/content/import_data/pro/connectors/connectors_tool_reference.md index 67c6b892e0c..89198061147 100644 --- a/docs/content/import_data/pro/connectors/connectors_tool_reference.md +++ b/docs/content/import_data/pro/connectors/connectors_tool_reference.md @@ -1,8 +1,9 @@ --- title: "Tool-Specific Connector Setup" description: "Our list of supported Connector tools, and how to set them up with DefectDojo" +aliases: + - /en/connecting_your_tools/connectors/connectors_tool_reference --- - Note: Connectors are a DefectDojo Pro-only feature. When setting up a Connector for a supported tool, you'll need to give DefectDojo specific information related to the tool's API. At a base level, you'll need: diff --git a/docs/content/import_data/pro/connectors/manage_operations.md b/docs/content/import_data/pro/connectors/manage_operations.md index a5652aed310..c965fad55ea 100644 --- a/docs/content/import_data/pro/connectors/manage_operations.md +++ b/docs/content/import_data/pro/connectors/manage_operations.md @@ -1,8 +1,9 @@ --- title: "Managing Operations" description: "Check the status of your Connector's Discover & Sync Operations" +aliases: + - /en/connecting_your_tools/connectors/manage_operations --- - Note: Connectors are a DefectDojo Pro-only feature. Once an API connector is set up, it will run two Operations on a recurring basis: diff --git a/docs/content/import_data/pro/connectors/manage_records.md b/docs/content/import_data/pro/connectors/manage_records.md index 8da74053483..2b605ee3cb2 100644 --- a/docs/content/import_data/pro/connectors/manage_records.md +++ b/docs/content/import_data/pro/connectors/manage_records.md @@ -1,8 +1,9 @@ --- title: "Managing Records" description: "Direct the flow of data from your tool into DefectDojo" +aliases: + - /en/connecting_your_tools/connectors/manage_records --- - Note: Connectors are a DefectDojo Pro-only feature. Once you have run your first Discover operation, you should see a list of Mapped or Unmapped records on the **Manage Records and Operations** page. diff --git a/docs/content/import_data/pro/specialized_import/external_tools.md b/docs/content/import_data/pro/specialized_import/external_tools.md index 625d12e5070..78b012193db 100644 --- a/docs/content/import_data/pro/specialized_import/external_tools.md +++ b/docs/content/import_data/pro/specialized_import/external_tools.md @@ -4,8 +4,9 @@ description: "Import files to DefectDojo from the command line" draft: false weight: 2 audience: pro +aliases: + - /en/connecting_your_tools/external_tools --- - Note: The following external tools are DefectDojo Pro-only features. These binaries will not work unless they are connected to an instance with a DefectDojo Pro license. ## About External Tools diff --git a/docs/content/import_data/pro/specialized_import/smart_upload.md b/docs/content/import_data/pro/specialized_import/smart_upload.md index 97e6201ccb2..42061ab4269 100644 --- a/docs/content/import_data/pro/specialized_import/smart_upload.md +++ b/docs/content/import_data/pro/specialized_import/smart_upload.md @@ -3,8 +3,9 @@ title: "Infrastructure scans / Smart Upload" description: "Automatically route incoming Findings to the correct Product" weight: 3 audience: pro +aliases: + - /en/connecting_your_tools/import_scan_files/smart_upload --- - Note: Smart Upload is only available in DefectDojo Pro. Smart upload is a specialized importer that ingests reports from **infrastructure scanning tools**, including: diff --git a/docs/content/import_data/pro/specialized_import/universal_parser.md b/docs/content/import_data/pro/specialized_import/universal_parser.md index 24043d5de20..e5abd41bff0 100644 --- a/docs/content/import_data/pro/specialized_import/universal_parser.md +++ b/docs/content/import_data/pro/specialized_import/universal_parser.md @@ -4,8 +4,9 @@ description: "" draft: "false" weight: 1 audience: pro +aliases: + - /en/connecting_your_tools/universal_parser --- - Note: The Universal Parser is only available in DefectDojo Pro. The Universal Parser is currently in Beta. See our [announcement presentation](https://community.defectdojo.com/universalparser) for more information. diff --git a/docs/content/issue_tracking/jira/jira_guide.md b/docs/content/issue_tracking/jira/jira_guide.md index baeaca65094..9fcd50530be 100644 --- a/docs/content/issue_tracking/jira/jira_guide.md +++ b/docs/content/issue_tracking/jira/jira_guide.md @@ -2,8 +2,9 @@ title: "📋 Jira Integration Guide" description: "Work with the Jira integration" weight: 1 +aliases: + - /en/share_your_findings/jira_guide --- - 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 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. diff --git a/docs/content/issue_tracking/jira/troubleshooting_jira.md b/docs/content/issue_tracking/jira/troubleshooting_jira.md index 2b671b9e12d..131bc4fbbe8 100644 --- a/docs/content/issue_tracking/jira/troubleshooting_jira.md +++ b/docs/content/issue_tracking/jira/troubleshooting_jira.md @@ -2,6 +2,8 @@ title: "Troubleshooting Jira errors" description: "Fixing issues with a Jira integration" weight: 2 +aliases: + - /en/share_your_findings/troubleshooting_jira/ --- Here are some common issues with the Jira integration, and ways to address them. diff --git a/docs/content/issue_tracking/pro_integration/integrations.md b/docs/content/issue_tracking/pro_integration/integrations.md index 28244d9bfa2..a52b35848fb 100644 --- a/docs/content/issue_tracking/pro_integration/integrations.md +++ b/docs/content/issue_tracking/pro_integration/integrations.md @@ -2,8 +2,9 @@ title: "Pro Integrations" weight: 1 audience: pro +aliases: + - /en/share_your_findings/integrations --- - DefectDojo Pro's Integrations let you push your Findings and Finding Groups to ticket tracking systems to easily integrate security remediation with your teams existing development workflow. Supported Integrations: diff --git a/docs/content/issue_tracking/pro_integration/integrations_toolreference.md b/docs/content/issue_tracking/pro_integration/integrations_toolreference.md index da74c650acd..1ed3c82bc67 100644 --- a/docs/content/issue_tracking/pro_integration/integrations_toolreference.md +++ b/docs/content/issue_tracking/pro_integration/integrations_toolreference.md @@ -3,8 +3,9 @@ title: "Integrators Tool Reference" description: "Detailed setup guides for Integrators" weight: 1 audience: pro +aliases: + - /en/share_your_findings/integrations_toolreference --- - Here are specific instructions detailing how to set up a DefectDojo Integration with a third party Issue Tracker. ## Azure DevOps Boards diff --git a/docs/content/metrics_reports/ai/mcp_server_pro.md b/docs/content/metrics_reports/ai/mcp_server_pro.md index fde352f9b5f..97516f19616 100644 --- a/docs/content/metrics_reports/ai/mcp_server_pro.md +++ b/docs/content/metrics_reports/ai/mcp_server_pro.md @@ -3,8 +3,9 @@ title: "MCP Server (Pro)" description: "DefectDojo's MCP Server allows you to use LLMs with DefectDojo Pro" draft: false weight: 2 +aliases: + - /en/ai/mcp_server_pro --- - Note: AI features are a DefectDojo Pro-only feature. The DefectDojo Model Context Protocol (MCP) Server enables Large Language Models (LLMs) to intelligently interact with DefectDojo's vulnerability management data. Unlike traditional API integrations that simply transfer data, the MCP server provides structured context and semantic meaning that enables AI assistants to perform sophisticated security analysis and generate actionable insights. diff --git a/docs/content/metrics_reports/dashboards/Introduction_dashboard.md b/docs/content/metrics_reports/dashboards/Introduction_dashboard.md index 62ed14c5421..783e1e57df9 100644 --- a/docs/content/metrics_reports/dashboards/Introduction_dashboard.md +++ b/docs/content/metrics_reports/dashboards/Introduction_dashboard.md @@ -2,8 +2,10 @@ title: "DefectDojo Main Dashboard" description: "Working with the front page of DefectDojo" weight: 1 +aliases: + - /en/customize_dojo/dashboards/Introduction_dashboard + - /en/customize_dojo/dashboards/pro_dashboards --- - The Dashboard is likely the first page you'll see when you open DefectDojo. It summarizes your team’s performance, and provides tracking tools to monitor specific areas of your vulnerability tracking environment. ![image](images/Introduction_to_Dashboard_Features.png) diff --git a/docs/content/metrics_reports/dashboards/about_custom_dashboard_tiles.md b/docs/content/metrics_reports/dashboards/about_custom_dashboard_tiles.md index 8dd7e0fa3b0..44750888055 100644 --- a/docs/content/metrics_reports/dashboards/about_custom_dashboard_tiles.md +++ b/docs/content/metrics_reports/dashboards/about_custom_dashboard_tiles.md @@ -2,8 +2,9 @@ title: "Custom Dashboard Tiles" description: "How to make Dashboard Tiles work for you, with examples" weight: 2 +aliases: + - /en/customize_dojo/dashboards/about_custom_dashboard_tiles --- - Dashboard Tiles are customizable sets of filters for your DefectDojo instance, which can be added to your 🏠 **Home** dashboard. Tiles are designed to provide relevant information and speed up navigation within DefectDojo. ![image](images/About_Custom_Dashboard_Tiles.png) diff --git a/docs/content/metrics_reports/reports/using_the_report_builder.md b/docs/content/metrics_reports/reports/using_the_report_builder.md index 70982a0dafb..b7f2c905f4e 100644 --- a/docs/content/metrics_reports/reports/using_the_report_builder.md +++ b/docs/content/metrics_reports/reports/using_the_report_builder.md @@ -2,8 +2,9 @@ title: "Using the Report Builder" description: "Build and publish custom reports for external audiences, or your own records" weight: 1 +aliases: + - /en/share_your_findings/pro_reports/using_the_report_builder --- - DefectDojo allows you to create Custom Reports for external audiences, which summarize the Findings or Endpoints that you wish to report on. Custom Reports can include branding and boilerplate text, and can also be used as **[Templates](https://docs.defectdojo.com/en/pro_reports/working-with-generated-reports/)** for future reports. ## Opening the Report Builder diff --git a/docs/content/metrics_reports/reports/working_with_generated_reports.md b/docs/content/metrics_reports/reports/working_with_generated_reports.md index a63f07cf01f..74f211f41a8 100644 --- a/docs/content/metrics_reports/reports/working_with_generated_reports.md +++ b/docs/content/metrics_reports/reports/working_with_generated_reports.md @@ -2,8 +2,9 @@ title: "Templates and Historical Reports" description: "Use a report as a template, or re-run an existing report with updated data" weight: 2 +aliases: + - /en/share_your_findings/pro_reports/working_with_generated_reports --- - Once you have created one or more **Reports** in DefectDojo you can take further actions, including: * Using a report as a template for subsequent reports diff --git a/docs/content/navigation/PRO__filter_index.md b/docs/content/navigation/PRO__filter_index.md index 168f25bb841..92f9b44c7ac 100644 --- a/docs/content/navigation/PRO__filter_index.md +++ b/docs/content/navigation/PRO__filter_index.md @@ -2,9 +2,9 @@ title: "Filter Index" description: "Reference for all filters in DefectDojo" weight: 5 - +aliases: + - /en/working_with_findings/organizing_engagements_tests/filter_index --- - **Note: Currently this article only covers Finding Filters available in the DefectDojo Pro UI, but this article will be expanded in the future to govern more object types, along with Open-Source filters.** Here is a list of filters that can be applied in the DefectDojo Pro UI to sort lists of Findings. DefectDojo Filters can be used to assist with navigating through lists of Objects, creating custom [Dashboard Tiles](/metrics_reports/dashboards/about_custom_dashboard_tiles/), or creating automation via [Rules Engine](/automation/rules_engine/about). diff --git a/docs/content/open_source/archived_docs/burp-plugin.md b/docs/content/open_source/archived_docs/burp-plugin.md index 874444b17da..5d8cf1f41a6 100644 --- a/docs/content/open_source/archived_docs/burp-plugin.md +++ b/docs/content/open_source/archived_docs/burp-plugin.md @@ -4,8 +4,9 @@ description: "Export findings directly from Burp to DefectDojo." draft: false weight: 9 exclude_search: true +aliases: + - /en/open_source/archived_docs/burp-plugin --- - **Please note: The DefectDojo Burp Plugin has been sunset and is no longer a supported feature.** Burp is still a supported tool, and all the results from it can be imported into DefectDojo. Burp can produce XML reports and these can be uploaded to DefectDojo using the graphical user interface or the API. Our documentation at https://documentation.defectdojo.com/integrations/parsers/file/burp/ describes this usage. diff --git a/docs/content/open_source/archived_docs/google-sheets-sync.md b/docs/content/open_source/archived_docs/google-sheets-sync.md index 8af6772e17d..7bb238b6ed2 100644 --- a/docs/content/open_source/archived_docs/google-sheets-sync.md +++ b/docs/content/open_source/archived_docs/google-sheets-sync.md @@ -4,8 +4,9 @@ description: "Export finding details to Google Sheets and upload changes from Go draft: false weight: 8 exclude_search: true +aliases: + - /en/open_source/archived_docs/google-sheets-sync --- - **Please note - the Google Sheets feature has been removed in DefectDojo version 2.21.0 - these documents are for reference only.** With the Google Sheets sync feature, DefectDojo allow the users to diff --git a/docs/content/open_source/archived_docs/usage/features.md b/docs/content/open_source/archived_docs/usage/features.md index f03d23100cb..0635177a8d3 100644 --- a/docs/content/open_source/archived_docs/usage/features.md +++ b/docs/content/open_source/archived_docs/usage/features.md @@ -4,8 +4,9 @@ description: "Various features help manage vulnerabilities." draft: false weight: 2 exclude_search: false +aliases: + - /en/open_source/archived_docs/usage/features --- - ## Tags In DefectDojo, tags are a first class citizen and are recognized as the facilitators diff --git a/docs/content/open_source/archived_docs/usage/questionnaires.md b/docs/content/open_source/archived_docs/usage/questionnaires.md index 36cc7c28501..bf39e369aa7 100644 --- a/docs/content/open_source/archived_docs/usage/questionnaires.md +++ b/docs/content/open_source/archived_docs/usage/questionnaires.md @@ -3,8 +3,9 @@ title: "Questionnaires" description: "Collect information from people internal or external to DefectDojo." weight: 3 draft: false +aliases: + - /en/open_source/archived_docs/usage/questionnaires --- - ## Questionnaires Questionnaires provide a means for collecting information from developers and respective stakeholders. DefectDojo includes functionality to create new questionnaires with custom questions, open questionnaires to receive responses for certain time periods from insiders or outsiders, and connect questionnaires with new or existing engagements. diff --git a/docs/content/open_source/contributing/branching-model.md b/docs/content/open_source/contributing/branching-model.md index 9fc0b817532..58dcf75a14c 100644 --- a/docs/content/open_source/contributing/branching-model.md +++ b/docs/content/open_source/contributing/branching-model.md @@ -3,8 +3,9 @@ title: "Open-Source Branching & Releases" description: "How we create releases" draft: false weight: 3 +aliases: + - /en/open_source/contributing/branching-model --- - ## Regular releases The DefectDojo team aims to maintain the following cadence: diff --git a/docs/content/open_source/contributing/documentation.md b/docs/content/open_source/contributing/documentation.md index fff04de6d29..aa99d3c65e7 100644 --- a/docs/content/open_source/contributing/documentation.md +++ b/docs/content/open_source/contributing/documentation.md @@ -3,8 +3,9 @@ title: "Amend Documentation" description: "How to amend the documentation" draft: false weight: 2 +aliases: + - /en/open_source/contributing/documentation --- - The documentation is built with [Hugo](https://gohugo.io/) and uses the theme [Docsy](https://www.docsy.dev). Static files for the webside are build with github actions and are publish in the gh-pages branch. diff --git a/docs/content/open_source/contributing/how-to-write-a-parser.md b/docs/content/open_source/contributing/how-to-write-a-parser.md index bd00c8abff5..c47e3cd8c41 100644 --- a/docs/content/open_source/contributing/how-to-write-a-parser.md +++ b/docs/content/open_source/contributing/how-to-write-a-parser.md @@ -3,8 +3,9 @@ title: "Contribute to Parsers" description: "How to contribute to parsers" draft: false weight: 1 +aliases: + - /en/open_source/contributing/how-to-write-a-parser --- - All commands assume that you're located at the root of the django-DefectDojo cloned repo. ## Pre-requisites diff --git a/docs/content/open_source/contributing/parser-documentation-template.md b/docs/content/open_source/contributing/parser-documentation-template.md index 943da145349..ed3bfc0dce9 100644 --- a/docs/content/open_source/contributing/parser-documentation-template.md +++ b/docs/content/open_source/contributing/parser-documentation-template.md @@ -2,8 +2,9 @@ title: "Parser Documentation Template" toc_hide: true weight: 1 +aliases: + - /en/open_source/contributing/parser-documentation-template --- - This template is designed to document a new or existing parser. Please feel free to improve with any additional information that might help your fellow security professionals. * Copy this .md file and add it to `/docs/content/supported_tools/file` in the GitHub repository. diff --git a/docs/content/open_source/exporting.md b/docs/content/open_source/exporting.md index b52f12989c3..c87163e0199 100644 --- a/docs/content/open_source/exporting.md +++ b/docs/content/open_source/exporting.md @@ -3,8 +3,9 @@ title: "Export Findings" description: "DefectDojo has the ability to export findings." draft: false weight: 12 +aliases: + - /en/open_source/exporting --- - ## Export Findings Pages that show a list of findings or a list of engagements have a CSV and Excel Export functionality in the top right dropdown menu. diff --git a/docs/content/open_source/languages.md b/docs/content/open_source/languages.md index 47929b7bfdc..c96f08a4381 100644 --- a/docs/content/open_source/languages.md +++ b/docs/content/open_source/languages.md @@ -3,8 +3,9 @@ title: "Languages and lines of code (Open-Source)" description: "You can import an analysis of languages used in a project, including lines of code." draft: false weight: 10 +aliases: + - /en/open_source/languages --- - ## Import of languages for a project You can import JSON reports generated by the [cloc tool](https://github.com/AlDanial/cloc) via the API: diff --git a/docs/content/open_source/ldap-authentication.md b/docs/content/open_source/ldap-authentication.md index e8db98ff232..d1d8f64a4cb 100644 --- a/docs/content/open_source/ldap-authentication.md +++ b/docs/content/open_source/ldap-authentication.md @@ -3,8 +3,9 @@ title: "Authentication via LDAP (Open-Source)" description: "Authenticate users using LDAP" draft: false weight: 4 +aliases: + - /en/open_source/ldap-authentication --- - ## LDAP Authentication Out of the box DefectDojo does not support LDAP authentication. diff --git a/docs/content/open_source/notification_webhooks/engagement_added.md b/docs/content/open_source/notification_webhooks/engagement_added.md index 2a782999034..ab9cce6e8e2 100644 --- a/docs/content/open_source/notification_webhooks/engagement_added.md +++ b/docs/content/open_source/notification_webhooks/engagement_added.md @@ -2,8 +2,9 @@ title: "Event: engagement_added" weight: 4 exclude_search: true +aliases: + - /en/open_source/notification_webhooks/engagement_added --- - ## Event HTTP header ```yaml X-DefectDojo-Event: engagement_added diff --git a/docs/content/open_source/notification_webhooks/how_to.md b/docs/content/open_source/notification_webhooks/how_to.md index e4caa61470b..5a1309d9330 100644 --- a/docs/content/open_source/notification_webhooks/how_to.md +++ b/docs/content/open_source/notification_webhooks/how_to.md @@ -2,8 +2,9 @@ title: "Notification Webhooks Setup" weight: 1 chapter: true +aliases: + - /en/open_source/notification_webhooks/how_to --- - Webhooks are HTTP requests coming from the DefectDojo instance towards a user-defined webserver which expects this kind of incoming traffic. ## Transition graph: diff --git a/docs/content/open_source/notification_webhooks/ping.md b/docs/content/open_source/notification_webhooks/ping.md index fcd1e9f93ae..9d2233bee04 100644 --- a/docs/content/open_source/notification_webhooks/ping.md +++ b/docs/content/open_source/notification_webhooks/ping.md @@ -3,8 +3,9 @@ title: "Event: ping" weight: 7 chapter: true exclude_search: true +aliases: + - /en/open_source/notification_webhooks/ping --- - An event `ping` is sent during Webhook setup to test whether the endpoint is up and responding with the expected status code. ## Event HTTP header diff --git a/docs/content/open_source/notification_webhooks/product_added.md b/docs/content/open_source/notification_webhooks/product_added.md index 0291fc56b07..b14fce04571 100644 --- a/docs/content/open_source/notification_webhooks/product_added.md +++ b/docs/content/open_source/notification_webhooks/product_added.md @@ -3,8 +3,9 @@ title: "Event: product_added" weight: 3 chapter: true exclude_search: true +aliases: + - /en/open_source/notification_webhooks/product_added --- - ## Event HTTP header ```yaml X-DefectDojo-Event: product_added diff --git a/docs/content/open_source/notification_webhooks/product_type_added.md b/docs/content/open_source/notification_webhooks/product_type_added.md index 5f76658a29b..223888e5f75 100644 --- a/docs/content/open_source/notification_webhooks/product_type_added.md +++ b/docs/content/open_source/notification_webhooks/product_type_added.md @@ -3,8 +3,9 @@ title: "Event: product_type_added" weight: 2 chapter: true exclude_search: true +aliases: + - /en/open_source/notification_webhooks/product_type_added --- - ## Event HTTP header ```yaml X-DefectDojo-Event: product_type_added diff --git a/docs/content/open_source/notification_webhooks/scan_added.md b/docs/content/open_source/notification_webhooks/scan_added.md index ea9dae28c40..e1950ac6312 100644 --- a/docs/content/open_source/notification_webhooks/scan_added.md +++ b/docs/content/open_source/notification_webhooks/scan_added.md @@ -3,8 +3,9 @@ title: "Event: scan_added and scan_added_empty" weight: 6 chapter: true exclude_search: true +aliases: + - /en/open_source/notification_webhooks/scan_added --- - Event `scan_added_empty` describes a situation when reimport did not affect the existing test (no finding has been created or closed). ## Event HTTP header for scan_added diff --git a/docs/content/open_source/notification_webhooks/test_added.md b/docs/content/open_source/notification_webhooks/test_added.md index f3b44a8a9a4..dba53ff5cda 100644 --- a/docs/content/open_source/notification_webhooks/test_added.md +++ b/docs/content/open_source/notification_webhooks/test_added.md @@ -3,8 +3,9 @@ title: "Event: test_added" weight: 5 chapter: true exclude_search: true +aliases: + - /en/open_source/notification_webhooks/test_added --- - ## Event HTTP header ```yaml X-DefectDojo-Event: test_added diff --git a/docs/content/open_source/rate_limiting.md b/docs/content/open_source/rate_limiting.md index 494a0371d6f..bbd9285a9ec 100644 --- a/docs/content/open_source/rate_limiting.md +++ b/docs/content/open_source/rate_limiting.md @@ -3,8 +3,9 @@ title: "Rate Limiting (Open-Source)" description: "Configurable rate limiting on the login page to mitigate brute force attacks" draft: false weight: 11 +aliases: + - /en/open_source/rate_limiting --- - DefectDojo has protection against brute force attacks through rate limiting. ## Configuration diff --git a/docs/content/releases/os_upgrading/2.56.md b/docs/content/releases/os_upgrading/2.56.md new file mode 100644 index 00000000000..59d3171af47 --- /dev/null +++ b/docs/content/releases/os_upgrading/2.56.md @@ -0,0 +1,27 @@ +--- +title: 'Upgrading to DefectDojo Version 2.56.x' +toc_hide: true +weight: -20260215 +description: Deprecation of Questionnaire API Endpoints +--- + +## Deprecation: Questionnaire API Endpoints + +The following Questionnaire API endpoints are being deprecated and will be removed in DefectDojo 2.59.0 on June 1st, 2026: + +- `/api/v2/questionnaire_answered_questionnaires/` +- `/api/v2/questionnaire_answers/` +- `/api/v2/questionnaire_engagement_questionnaires/` +- `/api/v2/questionnaire_general_questionnaires/` +- `/api/v2/questionnaire_questions` + +### Required Actions + +Support for these endpoints will be fully removed in DefectDojo 2.59.0 (scheduled for June 1st, 2026). After this date, any requests to these endpoints will return a 404 Not Found error. + +### Timeline + +- **DefectDojo 2.56.x onwards**: Endpoints are deprecated with deprecation headers +- **DefectDojo 2.59.0 (June 1st, 2026)**: Endpoints will be removed entirely + +For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.56.0). diff --git a/docs/content/releases/os_upgrading/2.59.md b/docs/content/releases/os_upgrading/2.59.md new file mode 100644 index 00000000000..d5bb68b3ab6 --- /dev/null +++ b/docs/content/releases/os_upgrading/2.59.md @@ -0,0 +1,22 @@ +--- +title: 'Upgrading to DefectDojo Version 2.59.x' +toc_hide: true +weight: -20260602 +description: Removal of Questionnaire API Endpoints +--- + +## Removal: Questionnaire API Endpoints + +As announced in DefectDojo 2.56.0, the following Questionnaire API endpoints have been removed: + +- `/api/v2/questionnaire_answered_questionnaires/` +- `/api/v2/questionnaire_answers/` +- `/api/v2/questionnaire_engagement_questionnaires/` +- `/api/v2/questionnaire_general_questionnaires/` +- `/api/v2/questionnaire_questions` + +### Required Actions + +Any requests to these endpoints will now return a 404 Not Found error. + +For more information, check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.59.0). diff --git a/docs/content/releases/os_upgrading/upgrading_guide.md b/docs/content/releases/os_upgrading/upgrading_guide.md index 18a07166141..cd3ff2542c2 100644 --- a/docs/content/releases/os_upgrading/upgrading_guide.md +++ b/docs/content/releases/os_upgrading/upgrading_guide.md @@ -5,6 +5,8 @@ draft: false sidebar: collapsed: true weight: -900000000 +aliases: + - /en/open_source/upgrading/ --- ## Docker compose diff --git a/docs/content/releases/pro/changelog.md b/docs/content/releases/pro/changelog.md index d328fb1dcfc..5ca25645e75 100644 --- a/docs/content/releases/pro/changelog.md +++ b/docs/content/releases/pro/changelog.md @@ -2,6 +2,8 @@ title: "DefectDojo Pro Changelog" description: "DefectDojo Changelog" exclude_search: true +aliases: + - /changelog/pro_changelog/ --- Here are the release notes for **DefectDojo Pro (Cloud Version)**. These release notes are focused on UX, so will not include all code changes. diff --git a/docs/content/supported_tools/parsers/file/openreports.md b/docs/content/supported_tools/parsers/file/openreports.md index d19f81c1d4e..6c6a8d1d557 100644 --- a/docs/content/supported_tools/parsers/file/openreports.md +++ b/docs/content/supported_tools/parsers/file/openreports.md @@ -1,6 +1,8 @@ --- title: "OpenReports" toc_hide: true +aliases: + - /en/connecting_your_tools/parsers/file/openreports --- Import JSON reports from [OpenReports](https://github.com/openreports/reports-api). diff --git a/docs/content/triage_findings/finding_deduplication/OS__deduplication_tuning.md b/docs/content/triage_findings/finding_deduplication/OS__deduplication_tuning.md index f88b72ed562..82968cb87f5 100644 --- a/docs/content/triage_findings/finding_deduplication/OS__deduplication_tuning.md +++ b/docs/content/triage_findings/finding_deduplication/OS__deduplication_tuning.md @@ -3,8 +3,10 @@ title: "Deduplication Tuning" description: "Configure deduplication in DefectDojo Open Source: algorithms, hash fields, endpoints, and service" weight: 5 audience: opensource +aliases: + - /en/working_with_findings/finding_deduplication/deduplication_tuning_os + - /en/working_with_findings/finding_deduplication/deduplication_algorithms --- - The Open Source edition of DefectDojo uses settings files and environment variables tune deduplication. See also: [Open Source Configuration](/get_started/open_source/configuration/) for details on environment variables and `local_settings.py` overrides. diff --git a/docs/content/triage_findings/finding_deduplication/PRO__deduplication_tuning.md b/docs/content/triage_findings/finding_deduplication/PRO__deduplication_tuning.md index 9da8005d8a1..a3cbfa6dd03 100644 --- a/docs/content/triage_findings/finding_deduplication/PRO__deduplication_tuning.md +++ b/docs/content/triage_findings/finding_deduplication/PRO__deduplication_tuning.md @@ -3,8 +3,9 @@ title: "Deduplication Tuning" description: "Configure how DefectDojo identifies and manages duplicate findings" weight: 4 audience: pro +aliases: + - /en/working_with_findings/finding_deduplication/tune_deduplication --- - Deduplication Tuning is a DefectDojo Pro feature that gives you fine-grained control over how findings are deduplicated, allowing you to optimize duplicate detection for your specific security testing workflow. ## Deduplication Settings diff --git a/docs/content/triage_findings/finding_deduplication/PRO_enabling_product_deduplication.md b/docs/content/triage_findings/finding_deduplication/PRO_enabling_product_deduplication.md index 5f2f12e4abc..12b5ce3dfdd 100644 --- a/docs/content/triage_findings/finding_deduplication/PRO_enabling_product_deduplication.md +++ b/docs/content/triage_findings/finding_deduplication/PRO_enabling_product_deduplication.md @@ -3,8 +3,9 @@ title: "Enabling Deduplication" description: "How to enable Deduplication at the Product level" weight: 2 audience: pro +aliases: + - /en/working_with_findings/finding_deduplication/enabling_product_deduplication --- - Deduplication can be implemented at either a Product level or at a more narrow Engagement level. ## Deduplication for Products diff --git a/docs/content/triage_findings/finding_deduplication/about_deduplication.md b/docs/content/triage_findings/finding_deduplication/about_deduplication.md index 14f203a3afe..5e18a8c21cb 100644 --- a/docs/content/triage_findings/finding_deduplication/about_deduplication.md +++ b/docs/content/triage_findings/finding_deduplication/about_deduplication.md @@ -2,6 +2,10 @@ title: "About Deduplication" description: "Deduplication fundamentals and key concepts" weight: 1 +aliases: + - /en/working_with_findings/finding_deduplication/about_deduplication + - /en/working_with_findings/finding_deduplication/delete_deduplicates + - /en/working_with_findings/findings_workflows/manage_duplicate_findings --- DefectDojo is designed to ingest bulk reports from tools, creating one or more Findings based on the content of the report. When using DefectDojo, you’ll most likely be ingesting reports from the same tool on a regular basis, which means that duplicate Findings are highly likely. diff --git a/docs/content/triage_findings/finding_deduplication/avoid_excess_duplicates.md b/docs/content/triage_findings/finding_deduplication/avoid_excess_duplicates.md index bd144df6dd3..b30a187e098 100644 --- a/docs/content/triage_findings/finding_deduplication/avoid_excess_duplicates.md +++ b/docs/content/triage_findings/finding_deduplication/avoid_excess_duplicates.md @@ -2,8 +2,9 @@ title: "Avoiding Excess Duplicates" description: "" weight: 4 +aliases: + - /en/working_with_findings/finding_deduplication/avoiding_duplicates_via_reimport --- - One of DefectDojo’s strengths is that the data model can accommodate many different use\-cases and applications. You’ll likely change your approach as you master the software and discover ways to optimize your workflow. By default, DefectDojo does not delete any duplicate Findings that are created. Each Finding is considered to be a separate instance of a vulnerability. So in this case, **Duplicate Findings** can be an indicator that a process change is required to your workflow. diff --git a/docs/content/triage_findings/findings_workflows/OS__risk_acceptance.md b/docs/content/triage_findings/findings_workflows/OS__risk_acceptance.md new file mode 100644 index 00000000000..bff51d2135c --- /dev/null +++ b/docs/content/triage_findings/findings_workflows/OS__risk_acceptance.md @@ -0,0 +1,101 @@ +--- +title: "Risk Acceptances" +description: "Leveraging Risk Acceptances in DefectDojo OS" +audience: opensource +weight: 2 +--- + +**Risk Acceptances** are a special status that can be applied to Findings to formally document and operationalize the decision to acknowledge them without immediately remediating them. + +Contrary to DefectDojo Pro, Risk Acceptances in OS DefectDojo are not independent objects. Rather, Risk Acceptances are only linked to Engagements. As such, they can only contain Findings from the Engagement they live in. If 3 instances of the same Finding appear in a Test in 3 different Engagements, 3 different Risk Acceptances will be required to fully accept those Findings. + +### Accessing Risk Acceptances + +Risk Acceptances include Findings that are particular to the Test(s) within each Engagement. As such, they can be accessed from the Engagement that contains the Test those Findings are from. + +![image](images/OS_RA_image1.png) + +A complete list of individual risk-accepted Findings is viewable in the **Risk Accepted Findings** submenu of the **Findings** section in the sidebar. + +![image](images/OS_RA_image2.png) + +## Creating Risk Acceptances + +When a Finding is Risk Accepted, the following will occur: +- The Finding’s status will no longer be “Active” but it will remain queryable, reportable, and auditable. +- The Finding’s status will be changed to “Risk Accepted.” +- The Finding will no longer be counted toward Metrics, but will still appear within the Test it originated from. + +Findings can be Risk Accepted in one of two ways: They can either be manually added to a **Full Risk Acceptance**, or by using the **Simple Risk Acceptance** workflow. + +### Full Risk Acceptances + +A Full Risk Acceptance allows Users to accept the risk of multiple Findings within an Engagement and bundle them into a single unit. If organizational policy requires formal, documented risk acceptances, or Users wish to trigger certain actions once a Risk Acceptance expires, Full Risk Acceptances are the best choice, as they capture the internal decision-making process and can serve as a source of truth. + +Each Full Risk Acceptance adds additional context, such as: +- The name of the Risk Acceptance. +- The owner of the Risk Acceptance. +- The security recommendation and decision regarding how to handle the Finding(s). +- Any proof associated with the recommendation or decision. +- Details regarding the recommendation or decision. +- The User who accepts the risk associated with the decision. +- The expiration date. + - Whether the Finding’s status will return to “Active” upon expiration. + - Whether the SLA will restart upon expiration. + +Expiration is unique to Full Risk Acceptances, and allows any Findings that have been Risk Accepted to be re-examined at an appropriate time. Once a Full Risk Acceptance expires, any Findings will be set to Active again. If you don’t specify a date, the Default Risk Acceptance / Default Risk Acceptance Expiration date will be used from the System Settings page. + +Importantly, as Full Risk Acceptances are restricted to individual Engagements, there is no single section in which to view all Full Risk Acceptances. They can only be viewed within the respective Engagement that includes the Findings that the Full Risk Acceptance contains. + +#### How to Create a Full Risk Acceptance + +In order to create a Full Risk Acceptance, navigate to the Engagement view and click the **+** symbol in the Risk Acceptance box. + +![image](images/OS_RA_image3.png) + +From there, fill out the details of the Full Risk Acceptance and select the Findings to be included. **Accepted Findings** contains a dropdown list of all available Findings to be added to the Risk Acceptance. The list of Findings within the Engagement will appear in descending order of severity (Critical Findings at the top, Low Findings at the bottom). If a Finding has been previously Risk Accepted, it will not appear in the dropdown list. + +Once completed, the Full Risk Acceptance will appear within the Risk Acceptance box in the Engagement view. + +A Risk Acceptance can also be created by clicking the **Add Risk Acceptance** button from within an individual Finding's ⋮ kebab menu. + +![image](images/OS_RA_image7.png) + +#### Interacting with Full Risk Acceptances + +Once a Full Risk Acceptance has been created, it can be opened to view the Findings that were added to it as well as any details that were input when it was created (e.g., the date, owner, decision, expiration, etc.). + +To remove a Finding from a Full Risk Acceptance, click the **Remove** button within the Findings Accepted table. + +![image](images/OS_RA_image8.png) + +The Full Risk Acceptance's view also includes a table at the bottom for all other Findings from Tests within that Engagement. From there, you may select additional Findings and add them to that Full Risk Acceptance. + +Additionally, there is a Notes function that allows Users to include additional context to the Full Risk Acceptance. All public notes will appear in any Reports that are generated for the Full Risk Acceptance, whereas notes that are toggled as **Private** will not appear in reports. + +Importantly, if a Full Risk Acceptance is deleted entirely, the Findings within will have their status automatically reverted to “Active.” + +### Simple Risk Acceptances + +While Full Risk Acceptance is enabled by default, Simple Risk Acceptance must be enabled manually, either upon the creation of an Asset or within the Asset’s settings. + +![image](images/OS_RA_image4.png) + +A Simple Risk Acceptance can be performed in either one of two ways: +1. Within a Test view using the Bulk Edits menu that appears after selecting one or more Findings from within the Findings table. + +![image](images/OS_RA_image5.png) + +2. Clicking **Accept Risk** from within an individual Finding’s ⋮ kebab menu. + +![image](images/OS_RA_image6.png) + +Once a Finding has been Simple Risk Accepted, it will still appear in the Test's Findings table, but the status will be changed to **Inactive, Risk Accepted.** A complete list of individual risk-accepted Findings is viewable in the **Risk Accepted Findings** submenu of the **Findings** section in the sidebar. + +If you Simple Risk Accept a Finding and later wish to add it to a Full Risk Acceptance, the Risk must be unaccepted prior to adding it to a Full Risk Acceptance. + +### Risk Acceptance Best Practices + +As a standard practice, it is generally preferable to use either Full Risk Acceptances or Simple Risk Acceptances exclusively, rather than leveraging both. + +For example, if Full Risk Acceptances are the default approach, if a Finding is Simple Risk Accepted, it may cause confusion if there is no associated Full Risk Acceptance that contains the affected Finding. Similarly, if Findings are typically Simple Risk Accepted, it may also create confusion to then add some Findings to a Full Risk Acceptance when there are no such objects for most other Findings. diff --git a/docs/content/triage_findings/findings_workflows/PRO__risk_acceptance.md b/docs/content/triage_findings/findings_workflows/PRO__risk_acceptance.md index 8ddf6e6a1e5..04eeba030cd 100644 --- a/docs/content/triage_findings/findings_workflows/PRO__risk_acceptance.md +++ b/docs/content/triage_findings/findings_workflows/PRO__risk_acceptance.md @@ -3,6 +3,8 @@ title: "Risk Acceptances" description: "Leveraging Risk Acceptances in DefectDojo Pro" audience: pro weight: 2 +aliases: + - /en/working_with_findings/findings_workflows/risk_acceptances/ --- **Risk Acceptances** are a special status that can be applied to Findings using either **Full Risk Acceptance** objects or the **Simple Risk Acceptance** workflow. Risk Acceptances are used to formally document and operationalize the decision to acknowledge a vulnerable Finding without immediately remediating it. diff --git a/docs/content/triage_findings/findings_workflows/create_findings_manually.md b/docs/content/triage_findings/findings_workflows/create_findings_manually.md index 639ac411fd3..6bad932086c 100644 --- a/docs/content/triage_findings/findings_workflows/create_findings_manually.md +++ b/docs/content/triage_findings/findings_workflows/create_findings_manually.md @@ -2,8 +2,9 @@ title: "Creating Findings Manually" description: "Track vulnerability information without using a scan tool" weight: 2 +aliases: + - /en/working_with_findings/findings_workflows/create_findings_manually --- - Normally, most of the Findings in your environment will be imported from other security tools. If you wish, you can add manual Finding entries as well, if you have vulnerabilities or work you wish to manage that was not created from a scan tool. 1. From the DefectDojo Sidebar, open the New Finding link by clicking **Manage \> Findings \> New Finding**. diff --git a/docs/content/triage_findings/findings_workflows/editing_findings.md b/docs/content/triage_findings/findings_workflows/editing_findings.md index adece9325ff..368ade22a8d 100644 --- a/docs/content/triage_findings/findings_workflows/editing_findings.md +++ b/docs/content/triage_findings/findings_workflows/editing_findings.md @@ -2,8 +2,9 @@ title: "Editing Findings" description: "Change a Finding’s Status, or add more metadata as you resolve an issue" weight: 2 +aliases: + - /en/working_with_findings/findings_workflows/editing_findings --- - If you want to add notes or update the language on a Finding to be more relevant to the current situation, you can do so through the Edit Finding form. ## Open the Edit Finding Form diff --git a/docs/content/triage_findings/findings_workflows/finding_status_definitions.md b/docs/content/triage_findings/findings_workflows/finding_status_definitions.md index 6b680923ded..327f4f9aed8 100644 --- a/docs/content/triage_findings/findings_workflows/finding_status_definitions.md +++ b/docs/content/triage_findings/findings_workflows/finding_status_definitions.md @@ -2,8 +2,9 @@ title: "Finding Status Definitions" description: "A quick reference to Finding status: Open, Verified, Accepted.." weight: 2 +aliases: + - /en/working_with_findings/findings_workflows/finding_status_definitions --- - Each Finding created in DefectDojo has a Status which communicates relevant information. Statuses help your team keep track of their progress in resolving issues. Each Finding status has a context\-specific meaning which will need to be defined by your own team. These are our suggestions, but your team's usage may vary. diff --git a/docs/content/triage_findings/findings_workflows/intro_to_findings.md b/docs/content/triage_findings/findings_workflows/intro_to_findings.md index 8d455dd4150..70fd854ec15 100644 --- a/docs/content/triage_findings/findings_workflows/intro_to_findings.md +++ b/docs/content/triage_findings/findings_workflows/intro_to_findings.md @@ -2,8 +2,9 @@ title: "Introduction to Findings" description: "The main workflow and vulnerability tracking system of DefectDojo" weight: 1 +aliases: + - /en/working_with_findings/intro_to_findings --- - Findings are the main way that DefectDojo standardizes and guides the reporting and remediation process of your security tools. Regardless of whether a vulnerability was reported in SonarQube, Acunetix, or your team’s custom tool, Findings give you the ability to manage each vulnerability in the same way. ## What are Findings? diff --git a/dojo/__init__.py b/dojo/__init__.py index b98285c0f30..9d76bb6de3f 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.55.2" +__version__ = "2.55.3" __url__ = "https://github.com/DefectDojo/django-DefectDojo" # noqa: RUF067 __docs__ = "https://documentation.defectdojo.com" # noqa: RUF067 diff --git a/dojo/api_v2/permissions.py b/dojo/api_v2/permissions.py index 2fdd1eb48f9..807bbcf7b2a 100644 --- a/dojo/api_v2/permissions.py +++ b/dojo/api_v2/permissions.py @@ -226,103 +226,87 @@ def has_object_permission(self, request, view, obj): class UserHasDojoMetaPermission(permissions.BasePermission): + permission_map = { + "product": { + "model": Product, + "permissions": { + "get_permission": Permissions.Product_View, + "put_permission": Permissions.Product_Edit, + "delete_permission": Permissions.Product_Edit, + "post_permission": Permissions.Product_Edit, + }, + }, + "finding": { + "model": Finding, + "permissions": { + "get_permission": Permissions.Finding_View, + "put_permission": Permissions.Finding_Edit, + "delete_permission": Permissions.Finding_Edit, + "post_permission": Permissions.Finding_Edit, + }, + }, + "location": { + "model": Location, + "permissions": { + "get_permission": Permissions.Location_View, + "put_permission": Permissions.Location_Edit, + "delete_permission": Permissions.Location_Edit, + "post_permission": Permissions.Location_Edit, + }, + }, + # TODO: Delete this after the move to Locations + "endpoint": { + "model": Endpoint if not settings.V3_FEATURE_LOCATIONS else Location, + "permissions": { + "get_permission": Permissions.Location_View, + "put_permission": Permissions.Location_Edit, + "delete_permission": Permissions.Location_Edit, + "post_permission": Permissions.Location_Edit, + }, + }, + } + def has_permission(self, request, view): - if request.method == "POST": - has_permission_result = True - product_id = request.data.get("product", None) - if product_id: - obj = get_object_or_404(Product, pk=product_id) - has_permission_result = ( - has_permission_result - and user_has_permission( - request.user, obj, Permissions.Product_Edit, - ) - ) - finding_id = request.data.get("finding", None) - if finding_id: - obj = get_object_or_404(Finding, pk=finding_id) - has_permission_result = ( - has_permission_result - and user_has_permission( - request.user, obj, Permissions.Finding_Edit, - ) - ) - location_id = request.data.get("location", None) - if location_id: - obj = get_object_or_404(Location, pk=location_id) - has_permission_result = ( - has_permission_result - and user_has_permission( - request.user, obj, Permissions.Location_Edit, - ) - ) - # TODO: Delete this after the move to Locations - endpoint_id = request.data.get("endpoint", None) - if endpoint_id: - if settings.V3_FEATURE_LOCATIONS: - obj = get_object_or_404(Location, pk=endpoint_id) - else: - obj = get_object_or_404(Endpoint, pk=endpoint_id) - has_permission_result = ( - has_permission_result - and user_has_permission( - request.user, obj, Permissions.Location_Edit, - ) - ) - return has_permission_result + method_to_permission_map = { + "GET": "get_permission", + "POST": "post_permission", + # PATCH is generally not used here, but this endpoint is sorta odd... + "PATCH": "put_permission", + } + for request_method, permission_type in method_to_permission_map.items(): + if request.method == request_method: + has_permission_result = True + for model_field, schema in self.permission_map.items(): + if (object_id := request.data.get(model_field)) is not None: + obj = get_object_or_404( + schema["model"], + pk=object_id, + ) + has_permission_result = ( + has_permission_result + and user_has_permission( + request.user, + obj, + schema["permissions"][permission_type], + ) + ) + return has_permission_result + # If we exit the loop at some point, we must not checking perms for that request method return True def has_object_permission(self, request, view, obj): has_permission_result = True - product = obj.product - if product: - has_permission_result = ( - has_permission_result - and check_object_permission( - request, - product, - Permissions.Product_View, - Permissions.Product_Edit, - Permissions.Product_Edit, - ) - ) - finding = obj.finding - if finding: - has_permission_result = ( - has_permission_result - and check_object_permission( - request, - finding, - Permissions.Finding_View, - Permissions.Finding_Edit, - Permissions.Finding_Edit, - ) - ) - location = obj.location - if location: - has_permission_result = ( - has_permission_result - and check_object_permission( - request, - location, - Permissions.Location_View, - Permissions.Location_Edit, - Permissions.Location_Edit, - ) - ) - # TODO: Delete this after the move to Locations - endpoint = obj.endpoint - if endpoint: - has_permission_result = ( + for model_field, schema in self.permission_map.items(): + if (object_model := getattr(obj, model_field, None)) is not None: + has_permission_result = ( has_permission_result and check_object_permission( request, - endpoint, - Permissions.Location_View, - Permissions.Location_Edit, - Permissions.Location_Edit, + object_model, + **schema["permissions"], ) ) + return has_permission_result @@ -401,6 +385,15 @@ class UserHasEngagementRelatedObjectPermission(BaseRelatedObjectPermission): } +class UserHasEngagementNotePermission(BaseRelatedObjectPermission): + permission_map = { + "get_permission": Permissions.Engagement_View, + "put_permission": Permissions.Engagement_Edit, + "delete_permission": Permissions.Engagement_Edit, + "post_permission": Permissions.Engagement_View, + } + + class UserHasRiskAcceptancePermission(permissions.BasePermission): def has_permission(self, request, view): # The previous implementation only checked for the object permission if the path was @@ -453,6 +446,15 @@ class UserHasFindingRelatedObjectPermission(BaseRelatedObjectPermission): } +class UserHasFindingNotePermission(BaseRelatedObjectPermission): + permission_map = { + "get_permission": Permissions.Finding_View, + "put_permission": Permissions.Finding_Edit, + "delete_permission": Permissions.Finding_Edit, + "post_permission": Permissions.Finding_View, + } + + class UserHasImportPermission(permissions.BasePermission): def has_permission(self, request, view): # permission check takes place before validation, so we don't have access to serializer.validated_data() @@ -833,6 +835,15 @@ class UserHasTestRelatedObjectPermission(BaseRelatedObjectPermission): } +class UserHasTestNotePermission(BaseRelatedObjectPermission): + permission_map = { + "get_permission": Permissions.Test_View, + "put_permission": Permissions.Test_Edit, + "delete_permission": Permissions.Test_Edit, + "post_permission": Permissions.Test_View, + } + + class UserHasTestImportPermission(permissions.BasePermission): def has_permission(self, request, view): return check_post_permission( diff --git a/dojo/api_v2/serializers.py b/dojo/api_v2/serializers.py index f9bac76264d..1eeb021d165 100644 --- a/dojo/api_v2/serializers.py +++ b/dojo/api_v2/serializers.py @@ -1890,7 +1890,9 @@ def update(self, instance, validated_data): if push_to_jira or finding_helper.is_keep_in_sync_with_jira(instance): # Push synchronously so that we can see jira errors in real time - jira_helper.push_to_jira(instance, sync=True) + success, message = jira_helper.push_to_jira(instance, sync=True) + if not success: + raise serializers.ValidationError(message) return instance diff --git a/dojo/api_v2/views.py b/dojo/api_v2/views.py index 15ba41e3d01..3461e54b25a 100644 --- a/dojo/api_v2/views.py +++ b/dojo/api_v2/views.py @@ -121,6 +121,7 @@ Languages, Network_Locations, Note_Type, + NoteHistory, Notes, Notification_Webhooks, Notifications, @@ -242,6 +243,19 @@ class PrefetchDojoModelViewSet( pass +class DeprecationNoticeMixin: + + deprecated: bool | None = None + end_of_life_date: datetime | None = None + + def finalize_response(self, request, response, *args, **kwargs): + if self.deprecated is not None: + response["X-Deprecated"] = self.deprecated + if self.end_of_life_date is not None: + response["X-End-Of-Life-Date"] = self.end_of_life_date.isoformat() + return super().finalize_response(request, response, *args, **kwargs) + + # Authorization: authenticated users class RoleViewSet(viewsets.ReadOnlyModelViewSet): serializer_class = serializers.RoleSerializer @@ -504,7 +518,7 @@ def generate_report(self, request, pk=None): request=serializers.AddNewNoteOptionSerializer, responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, ) - @action(detail=True, methods=["get", "post"], permission_classes=[IsAuthenticated, permissions.UserHasEngagementRelatedObjectPermission]) + @action(detail=True, methods=["get", "post"], permission_classes=[IsAuthenticated, permissions.UserHasEngagementNotePermission]) def notes(self, request, pk=None): engagement = self.get_object() if request.method == "POST": @@ -532,6 +546,10 @@ def notes(self, request, pk=None): note_type=note_type, ) note.save() + # Add an entry to the note history + history = NoteHistory.objects.create(data=note.entry, time=note.date, current_editor=note.author) + note.history.add(history) + # Now add the note to the object engagement.notes.add(note) # Determine if we need to send any notifications for user mentioned process_tag_notifications( @@ -1096,7 +1114,7 @@ def request_response(self, request, pk=None): request=serializers.AddNewNoteOptionSerializer, responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, ) - @action(detail=True, methods=["get", "post"], permission_classes=(IsAuthenticated, permissions.UserHasFindingRelatedObjectPermission)) + @action(detail=True, methods=["get", "post"], permission_classes=(IsAuthenticated, permissions.UserHasFindingNotePermission)) def notes(self, request, pk=None): finding = self.get_object() if request.method == "POST": @@ -1125,6 +1143,10 @@ def notes(self, request, pk=None): note_type=note_type, ) note.save() + # Add an entry to the note history + history = NoteHistory.objects.create(data=note.entry, time=note.date, current_editor=note.author) + note.history.add(history) + # Now add the note to the object finding.last_reviewed = note.date finding.last_reviewed_by = author finding.save(update_fields=["last_reviewed", "last_reviewed_by", "updated"]) @@ -1226,7 +1248,7 @@ def download_file(self, request, file_id, pk=None): request=serializers.FindingNoteSerializer, responses={status.HTTP_204_NO_CONTENT: ""}, ) - @action(detail=True, methods=["patch"], permission_classes=(IsAuthenticated, permissions.UserHasFindingRelatedObjectPermission)) + @action(detail=True, methods=["patch"], permission_classes=(IsAuthenticated, permissions.UserHasFindingNotePermission)) def remove_note(self, request, pk=None): """Remove Note From Finding Note""" finding = self.get_object() @@ -1707,10 +1729,12 @@ def batch(self, request, pk=None): if serialized_data.is_valid(raise_exception=True): if request.method == "POST": self.process_post(request.data) + status_code = status.HTTP_201_CREATED if request.method == "PATCH": self.process_patch(request.data) + status_code = status.HTTP_200_OK - return Response(status=status.HTTP_201_CREATED, data=serialized_data.data) + return Response(status=status_code, data=serialized_data.data) def process_post(self: object, data: dict): product = Product.objects.filter(id=data.get("product")).first() @@ -2160,7 +2184,7 @@ def generate_report(self, request, pk=None): request=serializers.AddNewNoteOptionSerializer, responses={status.HTTP_201_CREATED: serializers.NoteSerializer}, ) - @action(detail=True, methods=["get", "post"], permission_classes=(IsAuthenticated, permissions.UserHasTestRelatedObjectPermission)) + @action(detail=True, methods=["get", "post"], permission_classes=(IsAuthenticated, permissions.UserHasTestNotePermission)) def notes(self, request, pk=None): test = self.get_object() if request.method == "POST": @@ -2188,6 +2212,10 @@ def notes(self, request, pk=None): note_type=note_type, ) note.save() + # Add an entry to the note history + history = NoteHistory.objects.create(data=note.entry, time=note.date, current_editor=note.author) + note.history.add(history) + # Now add the note to the object test.notes.add(note) # Determine if we need to send any notifications for user mentioned process_tag_notifications( @@ -3171,7 +3199,10 @@ def get_queryset(self): class QuestionnaireQuestionViewSet( viewsets.ReadOnlyModelViewSet, dojo_mixins.QuestionSubClassFieldsMixin, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.QuestionnaireQuestionSerializer queryset = Question.objects.none() filter_backends = (DjangoFilterBackend,) @@ -3183,11 +3214,28 @@ class QuestionnaireQuestionViewSet( def get_queryset(self): return Question.objects.all().order_by("id") + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + class QuestionnaireAnswerViewSet( viewsets.ReadOnlyModelViewSet, dojo_mixins.AnswerSubClassFieldsMixin, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.QuestionnaireAnswerSerializer queryset = Answer.objects.none() filter_backends = (DjangoFilterBackend,) @@ -3199,10 +3247,27 @@ class QuestionnaireAnswerViewSet( def get_queryset(self): return Answer.objects.all().order_by("id") + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + class QuestionnaireGeneralSurveyViewSet( viewsets.ReadOnlyModelViewSet, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.QuestionnaireGeneralSurveySerializer queryset = General_Survey.objects.none() filter_backends = (DjangoFilterBackend,) @@ -3214,10 +3279,27 @@ class QuestionnaireGeneralSurveyViewSet( def get_queryset(self): return General_Survey.objects.all().order_by("id") + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + class QuestionnaireEngagementSurveyViewSet( viewsets.ReadOnlyModelViewSet, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.QuestionnaireEngagementSurveySerializer queryset = Engagement_Survey.objects.none() filter_backends = (DjangoFilterBackend,) @@ -3230,13 +3312,29 @@ def get_queryset(self): return Engagement_Survey.objects.all().order_by("id") @extend_schema( - request=OpenApiTypes.NONE, - parameters=[ - OpenApiParameter( - "engagement_id", OpenApiTypes.INT, OpenApiParameter.PATH, - ), - ], - responses={status.HTTP_200_OK: serializers.QuestionnaireAnsweredSurveySerializer}, + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + request=OpenApiTypes.NONE, + parameters=[ + OpenApiParameter( + "engagement_id", OpenApiTypes.INT, OpenApiParameter.PATH, + ), + ], + responses={status.HTTP_200_OK: serializers.QuestionnaireAnsweredSurveySerializer}, ) @action( detail=True, methods=["post"], url_path=r"link_engagement/(?P\d+)", @@ -3258,7 +3356,10 @@ class QuestionnaireAnsweredSurveyViewSet( prefetch.PrefetchListMixin, prefetch.PrefetchRetrieveMixin, viewsets.ReadOnlyModelViewSet, + DeprecationNoticeMixin, ): + deprecated = True + end_of_life_date = datetime(2026, 6, 1) serializer_class = serializers.QuestionnaireAnsweredSurveySerializer queryset = Answered_Survey.objects.none() filter_backends = (DjangoFilterBackend,) @@ -3270,6 +3371,20 @@ class QuestionnaireAnsweredSurveyViewSet( def get_queryset(self): return Answered_Survey.objects.all().order_by("id") + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def list(self, request, *args, **kwargs): + return super().list(request, *args, **kwargs) + + @extend_schema( + deprecated=True, + description="This endpoint is deprecated and will be removed on 2026-06-01.", + ) + def retrieve(self, request, *args, **kwargs): + return super().retrieve(request, *args, **kwargs) + # Authorization: configuration class AnnouncementViewSet( diff --git a/dojo/celery.py b/dojo/celery.py index 3cf09e1bc2c..a1456debf61 100644 --- a/dojo/celery.py +++ b/dojo/celery.py @@ -18,11 +18,36 @@ class DojoAsyncTask(Task): Base task class that provides dojo_async_task functionality without using a decorator. This class: - - Injects user context into task kwargs + - Injects user context into task kwargs (on dispatch via apply_async) + - Restores user context in the worker (on execution via __call__) - Tracks task calls for performance testing - Supports all Celery features (signatures, chords, groups, chains) """ + def __call__(self, *args, **kwargs): + """ + Restore user context in the celery worker via crum.impersonate. + + The apply_async method injects ``async_user`` into kwargs when a task + is dispatched. Here we pop it and set it as the current user in + thread-local storage so that all downstream code — including nested + dojo_dispatch_task calls — sees the correct user via + get_current_user(). + + When a task is called directly (not via apply_async), async_user is + not in kwargs. In that case we leave the existing crum context + intact so that callers who already set a user (e.g. via + crum.impersonate in tests or request middleware) are not disrupted. + """ + if "async_user" not in kwargs: + return super().__call__(*args, **kwargs) + + import crum # noqa: PLC0415 + + user = kwargs.pop("async_user") + with crum.impersonate(user): + return super().__call__(*args, **kwargs) + def apply_async(self, args=None, kwargs=None, **options): """Override apply_async to inject user context and track tasks.""" from dojo.decorators import dojo_async_task_counter # noqa: PLC0415 circular import diff --git a/dojo/celery_dispatch.py b/dojo/celery_dispatch.py index f4ce0e3241b..c835717efe2 100644 --- a/dojo/celery_dispatch.py +++ b/dojo/celery_dispatch.py @@ -84,8 +84,6 @@ def dojo_dispatch_task(task_or_sig: _SupportsSi | _SupportsApplyAsync | Signatur # Track foreground execution as a "created task" as well (matches historical dojo_async_task behavior) dojo_async_task_counter.incr(str(sig.task), args=sig.args, kwargs=sig_kwargs) - sig_kwargs.pop("sync", None) - sig = sig.clone(kwargs=sig_kwargs) eager = sig.apply() try: return eager.get(propagate=True) diff --git a/dojo/db_migrations/0260_alter_engagement_status_alter_engagementevent_status.py b/dojo/db_migrations/0260_alter_engagement_status_alter_engagementevent_status.py new file mode 100644 index 00000000000..59fe1dac00f --- /dev/null +++ b/dojo/db_migrations/0260_alter_engagement_status_alter_engagementevent_status.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.11 on 2026-02-13 20:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0259_locations'), + ] + + operations = [ + migrations.AlterField( + model_name='engagement', + name='status', + field=models.CharField(choices=[('Not Started', 'Not Started'), ('Blocked', 'Blocked'), ('Cancelled', 'Cancelled'), ('Completed', 'Completed'), ('In Progress', 'In Progress'), ('On Hold', 'On Hold'), ('Scheduled', 'Scheduled'), ('Waiting for Resource', 'Waiting for Resource')], default='Not Started', max_length=2000, null=True), + ), + migrations.AlterField( + model_name='engagementevent', + name='status', + field=models.CharField(choices=[('Not Started', 'Not Started'), ('Blocked', 'Blocked'), ('Cancelled', 'Cancelled'), ('Completed', 'Completed'), ('In Progress', 'In Progress'), ('On Hold', 'On Hold'), ('Scheduled', 'Scheduled'), ('Waiting for Resource', 'Waiting for Resource')], default='Not Started', max_length=2000, null=True), + ), + ] diff --git a/dojo/db_migrations/max_migration.txt b/dojo/db_migrations/max_migration.txt index ba12c472015..d07ef3c519e 100644 --- a/dojo/db_migrations/max_migration.txt +++ b/dojo/db_migrations/max_migration.txt @@ -1 +1 @@ -0259_locations +0260_alter_engagement_status_alter_engagementevent_status diff --git a/dojo/decorators.py b/dojo/decorators.py index a2a5b91e56b..e8a78841bba 100644 --- a/dojo/decorators.py +++ b/dojo/decorators.py @@ -62,7 +62,7 @@ def we_want_async(*args, func=None, **kwargs): logger.debug("dojo_async_task %s: running task in the foreground as sync=True has been found as kwarg", func) return False - user = kwargs.get("async_user", get_current_user()) + user = get_current_user() logger.debug("async user: %s", user) if not user: diff --git a/dojo/finding/helper.py b/dojo/finding/helper.py index 09a95af07dd..a43986bd7ea 100644 --- a/dojo/finding/helper.py +++ b/dojo/finding/helper.py @@ -358,8 +358,6 @@ def group_findings_by(finds, finding_group_by_option): def add_findings_to_auto_group(name, findings, group_by, *, create_finding_groups_for_all_findings=True, **kwargs): if name is not None and findings is not None and len(findings) > 0: creator = get_current_user() - if not creator: - creator = kwargs.get("async_user") test = findings[0].test if create_finding_groups_for_all_findings or len(findings) > 1: @@ -470,6 +468,7 @@ def post_process_findings_batch( push_to_jira=False, jira_instance_id=None, user=None, + sync=False, **kwargs, ): @@ -513,7 +512,7 @@ def post_process_findings_batch( if product_grading_option and system_settings.enable_product_grade: from dojo.celery_dispatch import dojo_dispatch_task # noqa: PLC0415 circular import - dojo_dispatch_task(calculate_grade, findings[0].test.engagement.product.id) + dojo_dispatch_task(calculate_grade, findings[0].test.engagement.product.id, sync=sync) # If we received the ID of a jira instance, then we need to determine the keep in sync behavior jira_instance = None diff --git a/dojo/finding/views.py b/dojo/finding/views.py index 5dfd50d601b..932db5d1b2d 100644 --- a/dojo/finding/views.py +++ b/dojo/finding/views.py @@ -2793,14 +2793,10 @@ def _bulk_update_simple_fields(finds, form): def _bulk_update_risk_acceptance(finds, form, request, prods): """Helper function to handle risk acceptance updates.""" skipped_risk_accept_count = 0 - skipped_active_risk_accept_count = 0 if form.cleaned_data["risk_acceptance"]: for finding in finds: - if finding.active: - skipped_active_risk_accept_count += 1 - # Allow risk acceptance for inactive findings (whether duplicate or not) - elif form.cleaned_data["risk_accept"]: + if form.cleaned_data["risk_accept"]: if ( not finding.test.engagement.product.enable_simple_risk_acceptance ): @@ -2822,15 +2818,7 @@ def _bulk_update_risk_acceptance(finds, form, request, prods): extra_tags="alert-warning", ) - if skipped_active_risk_accept_count > 0: - messages.add_message( - request, - messages.WARNING, - f"Skipped risk acceptance of {skipped_active_risk_accept_count} active findings. Active findings cannot be risk accepted.", - extra_tags="alert-warning", - ) - - return skipped_risk_accept_count, skipped_active_risk_accept_count + return skipped_risk_accept_count def _bulk_update_finding_groups(finds, form): @@ -3095,7 +3083,7 @@ def finding_bulk_update_all(request, pid=None): _bulk_update_simple_fields(finds, form) - _skipped_risk_accept_count, _skipped_active_risk_accept_count = _bulk_update_risk_acceptance( + _skipped_risk_accept_count = _bulk_update_risk_acceptance( finds, form, request, prods, ) diff --git a/dojo/group/queries.py b/dojo/group/queries.py index deee04a346a..11a6718bf62 100644 --- a/dojo/group/queries.py +++ b/dojo/group/queries.py @@ -1,7 +1,7 @@ from crum import get_current_user from django.db.models import Subquery -from dojo.authorization.authorization import get_roles_for_permission +from dojo.authorization.authorization import get_roles_for_permission, user_has_configuration_permission from dojo.authorization.roles_permissions import Permissions from dojo.models import Dojo_Group, Dojo_Group_Member, Product_Group, Product_Type_Group, Role from dojo.request_cache import cache_for_request @@ -18,6 +18,10 @@ def get_authorized_groups(permission): if user.is_superuser: return Dojo_Group.objects.all().order_by("name") + # Check for the case of the view_group config permission + if user_has_configuration_permission(user, "auth.view_group") or user_has_configuration_permission(user, "auth.add_group"): + return Dojo_Group.objects.all().order_by("name") + roles = get_roles_for_permission(permission) # Get authorized group IDs via subquery diff --git a/dojo/importers/base_importer.py b/dojo/importers/base_importer.py index ec7c09829cf..68682a91c64 100644 --- a/dojo/importers/base_importer.py +++ b/dojo/importers/base_importer.py @@ -293,7 +293,7 @@ def sync_process_findings( Processes findings in a synchronous manner such that all findings will be processed in a worker/process/thread """ - return self.process_findings(parsed_findings, sync=True, **kwargs) + return self.process_findings(parsed_findings, **kwargs) def determine_process_method( self, diff --git a/dojo/importers/default_importer.py b/dojo/importers/default_importer.py index db838fb8b3b..37192149269 100644 --- a/dojo/importers/default_importer.py +++ b/dojo/importers/default_importer.py @@ -2,7 +2,6 @@ from django.conf import settings from django.core.files.uploadedfile import TemporaryUploadedFile -from django.core.serializers import serialize from django.db.models.query_utils import Q from django.urls import reverse @@ -300,9 +299,6 @@ def process_findings( # Always perform an initial grading, even though it might get overwritten later. perform_product_grading(self.test.engagement.product) - sync = kwargs.get("sync", True) - if not sync: - return [serialize("json", [finding]) for finding in new_findings] return new_findings def close_old_findings( diff --git a/dojo/importers/default_reimporter.py b/dojo/importers/default_reimporter.py index 8ac156cde01..dbd3e7532ca 100644 --- a/dojo/importers/default_reimporter.py +++ b/dojo/importers/default_reimporter.py @@ -2,7 +2,6 @@ from django.conf import settings from django.core.files.uploadedfile import TemporaryUploadedFile -from django.core.serializers import serialize from django.db.models.query_utils import Q import dojo.finding.helper as finding_helper @@ -1013,29 +1012,7 @@ def process_results( self, **kwargs: dict, ) -> tuple[list[Finding], list[Finding], list[Finding], list[Finding]]: - """ - Determine how to to return the results based on whether the process was - ran asynchronous or not - """ - if not kwargs.get("sync"): - serialized_new_items = [ - serialize("json", [finding]) for finding in self.new_items - ] - serialized_reactivated_items = [ - serialize("json", [finding]) for finding in self.reactivated_items - ] - serialized_to_mitigate = [ - serialize("json", [finding]) for finding in self.to_mitigate - ] - serialized_untouched = [ - serialize("json", [finding]) for finding in self.untouched - ] - return ( - serialized_new_items, - serialized_reactivated_items, - serialized_to_mitigate, - serialized_untouched, - ) + """Return the finding lists collected during process_findings.""" return self.new_items, self.reactivated_items, self.to_mitigate, self.untouched def calculate_unsaved_finding_hash_code( diff --git a/dojo/jira_link/helper.py b/dojo/jira_link/helper.py index d5f5eb2da1a..feff72003ef 100644 --- a/dojo/jira_link/helper.py +++ b/dojo/jira_link/helper.py @@ -757,7 +757,7 @@ def jira_environment(obj): return "" -def push_to_jira(obj, *args, **kwargs): +def push_to_jira(obj, *args, **kwargs) -> tuple[str, bool]: if obj is None: msg = "Cannot push None to JIRA" raise ValueError(msg) @@ -773,17 +773,19 @@ def push_to_jira(obj, *args, **kwargs): if isinstance(obj, Engagement): return dojo_dispatch_task(push_engagement_to_jira, obj.id, *args, **kwargs) - logger.error("unsupported object passed to push_to_jira: %s %i %s", obj.__name__, obj.id, obj) - return None + message = f"unsupported object passed to push_to_jira: {obj.__class__.__name__} {obj.id} {obj}" + logger.error(message) + return False, message # we need thre separate celery tasks due to the decorators we're using to map to/from ids @app.task -def push_finding_to_jira(finding_id, *args, **kwargs): +def push_finding_to_jira(finding_id, *args, **kwargs) -> tuple[str, bool]: finding = get_object_or_none(Finding, id=finding_id) if not finding: - logger.warning("Finding with id %s does not exist, skipping push_finding_to_jira", finding_id) - return None + message = f"Finding with id {finding_id} does not exist, skipping push_finding_to_jira" + logger.warning(message) + return False, message if finding.has_jira_issue: return update_jira_issue(finding, *args, **kwargs) @@ -791,11 +793,12 @@ def push_finding_to_jira(finding_id, *args, **kwargs): @app.task -def push_finding_group_to_jira(finding_group_id, *args, **kwargs): +def push_finding_group_to_jira(finding_group_id, *args, **kwargs) -> tuple[str, bool]: finding_group = get_object_or_none(Finding_Group, id=finding_group_id) if not finding_group: - logger.warning("Finding_Group with id %s does not exist, skipping push_finding_group_to_jira", finding_group_id) - return None + message = f"Finding_Group with id {finding_group_id} does not exist, skipping push_finding_group_to_jira" + logger.warning(message) + return False, message # Look for findings that have single ticket associations separate from the group for finding in finding_group.findings.filter(jira_issue__isnull=False): @@ -807,11 +810,12 @@ def push_finding_group_to_jira(finding_group_id, *args, **kwargs): @app.task -def push_engagement_to_jira(engagement_id, *args, **kwargs): +def push_engagement_to_jira(engagement_id, *args, **kwargs) -> tuple[str, bool]: engagement = get_object_or_none(Engagement, id=engagement_id) if not engagement: - logger.warning("Engagement with id %s does not exist, skipping push_engagement_to_jira", engagement_id) - return None + message = f"Engagement with id {engagement_id} does not exist, skipping push_engagement_to_jira" + logger.warning(message) + return False, message if engagement.has_jira_issue: return dojo_dispatch_task(update_epic, engagement.id, *args, **kwargs) @@ -894,18 +898,18 @@ def prepare_jira_issue_fields( return fields -def add_jira_issue(obj, *args, **kwargs): - def failure_to_add_message(message: str, exception: Exception, _: Any) -> bool: +def add_jira_issue(obj, *args, **kwargs) -> tuple[str, bool]: + def failure_to_add_message(message: str, exception: Exception, _: Any) -> tuple[str, bool]: if exception: logger.error("Exception occurred", exc_info=exception) logger.error(message) log_jira_alert(message, obj) - return False + return False, message logger.info("trying to create a new jira issue for %d:%s", obj.id, to_str_typed(obj)) if not is_jira_enabled(): - return False + return False, "JIRA integration is not enabled." if not is_jira_configured_and_enabled(obj): message = f"Object {obj.id} cannot be pushed to JIRA as there is no JIRA configuration for {to_str_typed(obj)}." @@ -932,20 +936,20 @@ def failure_to_add_message(message: str, exception: Exception, _: Any) -> bool: # not sure why this check is not part of can_be_pushed_to_jira, but afraid to change it if isinstance(obj, Finding) and obj.duplicate and not obj.active: - logger.info("%s will not be pushed to JIRA as it's a duplicate finding", to_str_typed(obj)) + message = f"{to_str_typed(obj)} is a duplicate and inactive finding, and will not be pushed to JIRA: {error_message}." # Duplicates are expected, don't create alerts - logger.info("%s cannot be pushed to JIRA: %s (expected - duplicate finding)", - to_str_typed(obj), error_message) + logger.info(message) elif error_code in expected_validation_errors: + message = f"{to_str_typed(obj)} cannot be pushed to JIRA: {error_message}." # These are expected when auto-pushing, only log, don't alert - logger.info("%s cannot be pushed to JIRA: %s (expected - finding not ready yet)", - to_str_typed(obj), error_message) + logger.info(message) else: # Unexpected errors (configuration issues, etc.) should still alert - log_jira_cannot_be_pushed_reason(error_message, obj) - logger.warning("%s cannot be pushed to JIRA: %s.", to_str_typed(obj), error_message) + message = f"{to_str_typed(obj)} cannot be pushed to JIRA due to an unexpected error: {error_message}." + log_jira_cannot_be_pushed_reason(message, obj) + logger.warning("%s cannot be pushed to JIRA: %s.", to_str_typed(obj), message) logger.warning("The JIRA issue will NOT be created.") - return False + return False, message logger.debug("Trying to create a new JIRA issue for %s...", to_str_typed(obj)) # Attempt to get the jira connection try: @@ -1056,21 +1060,21 @@ def failure_to_add_message(message: str, exception: Exception, _: Any) -> bool: message = f"Failed to assign jira issue to existing epic: {e}" return failure_to_add_message(message, e, obj) - return True + return True, "JIRA issue created successfully." -def update_jira_issue(obj, *args, **kwargs): - def failure_to_update_message(message: str, exception: Exception, obj: Any) -> bool: +def update_jira_issue(obj, *args, **kwargs) -> tuple[str, bool]: + def failure_to_update_message(message: str, exception: Exception, obj: Any) -> tuple[str, bool]: if exception: logger.error(exception) logger.error(message) log_jira_alert(message, obj) - return False + return False, message logger.debug("trying to update a linked jira issue for %d:%s", obj.id, to_str_typed(obj)) if not is_jira_enabled(): - return False + return False, "JIRA integration is not enabled." jira_project = get_jira_project(obj) jira_instance = get_jira_instance(obj) @@ -1181,7 +1185,7 @@ def failure_to_update_message(message: str, exception: Exception, obj: Any) -> b message = f"Failed to assign jira issue to existing epic: {e}" return failure_to_update_message(message, e, obj) - return True + return True, "JIRA issue updated successfully." def get_jira_issue_from_jira(find): @@ -1447,13 +1451,14 @@ def close_epic(engagement_id, push_to_jira, **kwargs): def update_epic(engagement_id, **kwargs): engagement = get_object_or_none(Engagement, id=engagement_id) if not engagement: - logger.warning("Engagement with id %s does not exist, skipping update_epic", engagement_id) - return False + message = f"Engagement with id {engagement_id} does not exist, skipping update_epic" + logger.warning(message) + return False, message logger.debug("trying to update jira EPIC for %d:%s", engagement.id, engagement.name) if not is_jira_configured_and_enabled(engagement): - return False + return False, "JIRA integration is not properly configured for this engagement." logger.debug("config found") @@ -1479,35 +1484,40 @@ def update_epic(engagement_id, **kwargs): jira_issue_update_kwargs["priority"] = {"name": epic_priority} issue.update(**jira_issue_update_kwargs) except JIRAError as e: - logger.exception("Jira Engagement/Epic Update Error") - log_jira_generic_alert("Jira Engagement/Epic Update Error", str(e)) - return False + message = f"Failed to update the JIRA EPIC for engagement {engagement.id} - {e}" + logger.exception(message) + log_jira_generic_alert(message) + return False, message - return True + return True, "JIRA EPIC updated successfully." - add_error_message_to_response("Push to JIRA for Epic skipped because enable_engagement_epic_mapping is not checked for this engagement") - return False + message = f"Push to JIRA for Epic skipped because enable_engagement_epic_mapping is not checked for engagement {engagement.id}." + add_error_message_to_response(message) + return False, message @app.task def add_epic(engagement_id, **kwargs): engagement = get_object_or_none(Engagement, id=engagement_id) if not engagement: - logger.warning("Engagement with id %s does not exist, skipping add_epic", engagement_id) - return False + message = f"Engagement with id {engagement_id} does not exist, skipping add_epic" + logger.warning(message) + return False, message logger.debug("trying to create a new jira EPIC for %d:%s", engagement.id, engagement.name) if not is_jira_configured_and_enabled(engagement): - return False + message = f"JIRA integration is not properly configured for engagement {engagement.id}." + return False, message logger.debug("config found") jira_project = get_jira_project(engagement) jira_instance = get_jira_instance(engagement) if not jira_instance: - logger.warning("JIRA add epic failed: jira_instance is None") - return False + message = f"JIRA add epic failed for engagement {engagement.id}: jira_instance is None" + logger.warning(message) + return False, message if jira_project and jira_project.enable_engagement_epic_mapping: epic_name = kwargs.get("epic_name") @@ -1555,15 +1565,15 @@ def add_epic(engagement_id, **kwargs): message = "The 'Epic name id' in your DefectDojo Jira Configuration does not appear to be correct. Please visit, " + jira_instance.url + \ "/rest/api/2/field and search for Epic Name. Copy the number out of cf[number] and place in your DefectDojo settings for Jira and try again. For example, if your results are cf[100001] then copy 100001 and place it in 'Epic name id'. (Your Epic Id will be different.) \n\n" logger.exception(message) + message = f"JIRA add epic failed for engagement {engagement.id}: {message}" + log_jira_generic_alert(message) + return False, message - log_jira_generic_alert("Jira Engagement/Epic Creation Error", - message + error) - return False - - return True + return True, "JIRA EPIC created successfully." - add_error_message_to_response("Push to JIRA for Epic skipped because enable_engagement_epic_mapping is not checked for this engagement") - return False + message = f"Push to JIRA for Epic skipped because enable_engagement_epic_mapping is not checked for engagement {engagement.id}." + add_error_message_to_response(message) + return False, message def jira_get_issue(jira_project, issue_key): @@ -1847,7 +1857,8 @@ def process_jira_epic_form(request, engagement=None): epic_priority = None if jira_epic_form.cleaned_data.get("epic_priority"): epic_priority = jira_epic_form.cleaned_data.get("epic_priority") - if push_to_jira(engagement, epic_name=epic_name, epic_priority=epic_priority): + success, message = push_to_jira(engagement, epic_name=epic_name, epic_priority=epic_priority) + if success: logger.debug("Push to JIRA for Epic queued successfully") messages.add_message( request, @@ -1856,11 +1867,11 @@ def process_jira_epic_form(request, engagement=None): extra_tags="alert-success") else: error = True - logger.debug("Push to JIRA for Epic failey") + logger.debug("Push to JIRA for Epic failed") messages.add_message( request, messages.ERROR, - "Push to JIRA for Epic failed, check alerts on the top right for errors", + message, extra_tags="alert-danger") else: logger.debug("invalid jira epic form") diff --git a/dojo/location/api/serializers.py b/dojo/location/api/serializers.py index 5051f5454a1..2695736390e 100644 --- a/dojo/location/api/serializers.py +++ b/dojo/location/api/serializers.py @@ -1,5 +1,6 @@ from __future__ import annotations +from rest_framework.relations import PrimaryKeyRelatedField from rest_framework.serializers import CharField from dojo.api_helpers.serializers import BaseModelSerializer @@ -13,6 +14,7 @@ class AbstractedLocationSerializer(BaseModelSerializer): + id = PrimaryKeyRelatedField(source="location.id", read_only=True) string = CharField(source="location.location_value", read_only=True) type = CharField(source="location.location_type", read_only=True) tags = TagListSerializerField(source="location.tags", required=False) diff --git a/dojo/middleware.py b/dojo/middleware.py index b23e94027af..8d274202f90 100644 --- a/dojo/middleware.py +++ b/dojo/middleware.py @@ -64,17 +64,19 @@ def __call__(self, request): fullURL = f"{settings.LOGIN_URL}?next={quote(request.get_full_path())}" return HttpResponseRedirect(fullURL) + if request.user.is_authenticated: + path = request.path_info.lstrip("/") + if Dojo_User.force_password_reset(request.user) and path != "change_password": + return HttpResponseRedirect(reverse("change_password")) + + response = self.get_response(request) if request.user.is_authenticated: logger.debug("Authenticated user: %s", request.user) with suppress(ModuleNotFoundError): # to avoid unittests to fail uwsgi = __import__("uwsgi", globals(), locals(), ["set_logvar"], 0) # this populates dd_user log var, so can appear in the uwsgi logs uwsgi.set_logvar("dd_user", str(request.user)) - path = request.path_info.lstrip("/") - if Dojo_User.force_password_reset(request.user) and path != "change_password": - return HttpResponseRedirect(reverse("change_password")) - - return self.get_response(request) + return response class CustomSocialAuthExceptionMiddleware(SocialAuthExceptionMiddleware): diff --git a/dojo/models.py b/dojo/models.py index 5f470c83d93..cf6ad3787d6 100644 --- a/dojo/models.py +++ b/dojo/models.py @@ -1510,6 +1510,7 @@ def __str__(self): ("Completed", "Completed"), ("In Progress", "In Progress"), ("On Hold", "On Hold"), + ("Scheduled", "Scheduled"), ("Waiting for Resource", "Waiting for Resource")) diff --git a/dojo/templates/dojo/view_eng.html b/dojo/templates/dojo/view_eng.html index ab09dadb7c5..71f660809ac 100644 --- a/dojo/templates/dojo/view_eng.html +++ b/dojo/templates/dojo/view_eng.html @@ -426,7 +426,7 @@

Risk Acceptance {{ risk_acceptance.get_decision_display|default_if_none:"" }} {% if risk_acceptance.decision_details %} -   {% endif %} @@ -441,7 +441,7 @@

Risk Acceptance {{ risk_acceptance.accepted_findings_count }} {% if risk_acceptance.filename %} Yes -   {% else %} diff --git a/dojo/templates/dojo/view_risk_acceptance.html b/dojo/templates/dojo/view_risk_acceptance.html index 5086fc2d6c3..16c43b579a4 100644 --- a/dojo/templates/dojo/view_risk_acceptance.html +++ b/dojo/templates/dojo/view_risk_acceptance.html @@ -142,13 +142,13 @@

Decision & Recommendation

{{ risk_acceptance.get_recommendation_display }} - {{ risk_acceptance.recommendation_details|markdown_render }} + {{ risk_acceptance.recommendation_details }} {{ risk_acceptance.get_decision_display }} - {{ risk_acceptance.decision_details|markdown_render }} + {{ risk_acceptance.decision_details }} diff --git a/dojo/url/api/serializer.py b/dojo/url/api/serializer.py index f35a9abf726..516193cba4c 100644 --- a/dojo/url/api/serializer.py +++ b/dojo/url/api/serializer.py @@ -11,4 +11,4 @@ class Meta: """Meta class for URLSerializer.""" model = URL - fields = "__all__" + exclude = ("location", "hash") diff --git a/dojo/utils.py b/dojo/utils.py index ba1b5ed0d7c..a5d8a13ed81 100644 --- a/dojo/utils.py +++ b/dojo/utils.py @@ -140,11 +140,6 @@ def do_false_positive_history(finding, *args, **kwargs): existing_non_fp_findings = existing_findings.filter(active=True).exclude(false_p=True) to_mark_as_fp.update(set(existing_non_fp_findings)) - # Remove the async user kwarg because save() really does not like it - # Would rather not add anything to Finding.save() - if "async_user" in kwargs: - kwargs.pop("async_user") - for find in to_mark_as_fp: deduplicationLogger.debug( "FALSE_POSITIVE_HISTORY: Marking Finding %i:%s from %s as false positive", @@ -1971,7 +1966,7 @@ def mass_model_updater(model_type, models, function, fields, page_size=1000, ord def to_str_typed(obj): """For code that handles multiple types of objects, print not only __str__ but prefix the type of the object""" - return f"{type(obj)}: {obj}" + return f"{type(obj).__name__}: {obj}" def get_product(obj): @@ -2065,7 +2060,7 @@ def async_delete_chunk_task(objects, **kwargs): """ Module-level Celery task to delete a chunk of objects. - Accepts **kwargs for async_user and _pgh_context injected by dojo_dispatch_task. + Accepts **kwargs for _pgh_context injected by dojo_dispatch_task. Uses PgHistoryTask base class (default) to preserve pghistory context for audit trail. """ max_retries = 3 @@ -2119,7 +2114,7 @@ def async_delete_crawl_task(obj, model_list, **kwargs): """ Module-level Celery task to crawl and delete related objects. - Accepts **kwargs for async_user and _pgh_context injected by dojo_dispatch_task. + Accepts **kwargs for _pgh_context injected by dojo_dispatch_task. Uses PgHistoryTask base class (default) to preserve pghistory context for audit trail. """ from dojo.celery_dispatch import dojo_dispatch_task # noqa: PLC0415 circular import @@ -2158,7 +2153,7 @@ def async_delete_task(obj, **kwargs): """ Module-level Celery task to delete an object and its related objects. - Accepts **kwargs for async_user and _pgh_context injected by dojo_dispatch_task. + Accepts **kwargs for _pgh_context injected by dojo_dispatch_task. Uses PgHistoryTask base class (default) to preserve pghistory context for audit trail. """ from dojo.celery_dispatch import dojo_dispatch_task # noqa: PLC0415 circular import @@ -2196,7 +2191,7 @@ def delete(self, obj, **kwargs): Entry point to delete an object asynchronously. Dispatches to async_delete_task via dojo_dispatch_task to ensure proper - handling of async_user and _pgh_context. + handling of user context and _pgh_context. """ from dojo.celery_dispatch import dojo_dispatch_task # noqa: PLC0415 circular import diff --git a/helm/defectdojo/Chart.yaml b/helm/defectdojo/Chart.yaml index fe3807b46a0..cac4e2ce84a 100644 --- a/helm/defectdojo/Chart.yaml +++ b/helm/defectdojo/Chart.yaml @@ -1,8 +1,8 @@ apiVersion: v2 -appVersion: "2.55.2" +appVersion: "2.55.3" description: A Helm chart for Kubernetes to install DefectDojo name: defectdojo -version: 1.9.12 +version: 1.9.13 icon: https://defectdojo.com/hubfs/DefectDojo_favicon.png maintainers: - name: madchap @@ -34,4 +34,4 @@ dependencies: # description: Critical bug annotations: artifacthub.io/prerelease: "false" - artifacthub.io/changes: "- kind: changed\n description: Bump DefectDojo to 2.55.2\n" + artifacthub.io/changes: "- kind: changed\n description: Bump DefectDojo to 2.55.3\n" diff --git a/helm/defectdojo/README.md b/helm/defectdojo/README.md index 480b35e158c..27e93da4c3f 100644 --- a/helm/defectdojo/README.md +++ b/helm/defectdojo/README.md @@ -511,7 +511,7 @@ The HELM schema will be generated for you. # General information about chart values -![Version: 1.9.12](https://img.shields.io/badge/Version-1.9.12-informational?style=flat-square) ![AppVersion: 2.55.2](https://img.shields.io/badge/AppVersion-2.55.2-informational?style=flat-square) +![Version: 1.9.13](https://img.shields.io/badge/Version-1.9.13-informational?style=flat-square) ![AppVersion: 2.55.3](https://img.shields.io/badge/AppVersion-2.55.3-informational?style=flat-square) A Helm chart for Kubernetes to install DefectDojo diff --git a/requirements.txt b/requirements.txt index 52396d021d7..0bf77d07125 100644 --- a/requirements.txt +++ b/requirements.txt @@ -29,9 +29,9 @@ PyGithub==2.8.1 lxml==6.0.2 Markdown==3.10.1 openpyxl==3.1.5 -Pillow==12.1.0 # required by django-imagekit +Pillow==12.1.1 # required by django-imagekit psycopg[c]==3.3.2 -cryptography==46.0.4 +cryptography==46.0.5 python-dateutil==2.9.0.post0 redis==7.1.0 requests==2.32.5 diff --git a/unittests/test_async_delete.py b/unittests/test_async_delete.py index 341723e8296..b8320d24707 100644 --- a/unittests/test_async_delete.py +++ b/unittests/test_async_delete.py @@ -2,10 +2,10 @@ Unit tests for async_delete functionality. These tests verify that the async_delete class works correctly with dojo_dispatch_task, -which injects async_user and _pgh_context kwargs into task calls. +which injects user context and _pgh_context kwargs into task calls. The original bug was that @app.task decorated instance methods didn't properly handle -the injected kwargs, causing TypeError: unexpected keyword argument 'async_user'. +the injected kwargs, causing TypeError for unexpected keyword arguments. """ import logging @@ -120,8 +120,8 @@ def test_async_delete_simple_object(self): # Use impersonate to set current user context (required for block_execution to work) with impersonate(self.testuser): - # This would raise TypeError before the fix: - # TypeError: delete() got an unexpected keyword argument 'async_user' + # This would raise TypeError before the fix when injected kwargs + # were not handled properly by task functions async_del = async_delete() async_del.delete(finding) diff --git a/unittests/test_bulk_edit_validation.py b/unittests/test_bulk_edit_validation.py index 84ce98e396f..63c84568b02 100644 --- a/unittests/test_bulk_edit_validation.py +++ b/unittests/test_bulk_edit_validation.py @@ -409,8 +409,8 @@ def test_bulk_edit_duplicate_finding_severity_update_works(self): # View-Level Validation Tests (Active + Risk Acceptance) - def test_bulk_edit_active_finding_cannot_accept_risk(self): - """Test that active findings cannot accept risk via bulk edit""" + def test_bulk_edit_active_finding_can_accept_risk(self): + """Test that active findings can accept risk via bulk edit (matching individual behavior)""" # Enable simple risk acceptance on product self.product.enable_simple_risk_acceptance = True self.product.save() @@ -427,22 +427,26 @@ def test_bulk_edit_active_finding_cannot_accept_risk(self): follow=True, ) - # Verify finding is NOT risk accepted + # Verify finding IS risk accepted and becomes inactive self.active_finding.refresh_from_db() - self.assertFalse( + self.assertTrue( self.active_finding.risk_accepted, - "Active finding should not be risk accepted", + "Active finding should be risk accepted", + ) + self.assertFalse( + self.active_finding.active, + "Risk accepted finding should become inactive", ) - # Verify warning message + # Verify no warning message about active findings messages = self._get_messages_text(response) warning_messages = [ m for m in messages if "active findings" in m.lower() and "risk" in m.lower() ] - self.assertGreater( + self.assertEqual( len(warning_messages), 0, - f"Expected warning about active findings and risk acceptance, got: {messages}", + f"Unexpected warning about active findings: {warning_messages}", ) def test_bulk_edit_inactive_finding_can_accept_risk(self): @@ -553,12 +557,15 @@ def test_bulk_edit_shows_success_message_with_actual_count(self): self._assert_finding_status(normal2, active=True) def test_bulk_edit_shows_multiple_warning_messages(self): - """Test that multiple warning messages appear for different conflicts""" + """ + Test that warning messages appear for conflicts (duplicate status) + and that active findings can now be risk accepted successfully + """ # Enable simple risk acceptance self.product.enable_simple_risk_acceptance = True self.product.save() - # First, try to set duplicate finding as active (will be skipped) + # First, try to set duplicate finding as active (will be skipped with warning) post_data1 = self._bulk_edit_post_data( [self.duplicate_finding.id], active=True, # Will conflict with duplicate @@ -569,11 +576,11 @@ def test_bulk_edit_shows_multiple_warning_messages(self): follow=True, ) - # Then, try to risk accept active finding (will be skipped) + # Then, risk accept active finding (should succeed - no longer a conflict) post_data2 = self._bulk_edit_post_data( [self.active_finding.id], risk_acceptance=True, - risk_accept=True, # Will conflict with active + risk_accept=True, # Should work now! ) response2 = self.client.post( reverse("finding_bulk_update_all"), @@ -586,25 +593,34 @@ def test_bulk_edit_shows_multiple_warning_messages(self): messages2 = self._get_messages_text(response2) all_messages = messages1 + messages2 + # Verify duplicate warning appears duplicate_warnings = [ m for m in all_messages if "duplicate findings" in m.lower() ] + self.assertGreater( + len(duplicate_warnings), + 0, + f"Expected duplicate warning, got: {all_messages}", + ) + + # Verify NO warning about active findings and risk acceptance active_warnings = [ m for m in all_messages if "active findings" in m.lower() and "risk" in m.lower() ] - - self.assertGreater( - len(duplicate_warnings), - 0, - f"Expected duplicate warning, got: {all_messages}", - ) - self.assertGreater( + self.assertEqual( len(active_warnings), 0, - f"Expected active risk acceptance warning, got: {all_messages}", + f"Unexpected active risk acceptance warning: {active_warnings}", + ) + + # Verify active finding was successfully risk accepted + self.active_finding.refresh_from_db() + self.assertTrue( + self.active_finding.risk_accepted, + "Active finding should be risk accepted successfully", ) def test_bulk_edit_no_warning_when_no_conflicts(self): diff --git a/unittests/test_middleware_login_required.py b/unittests/test_middleware_login_required.py new file mode 100644 index 00000000000..8f25e7dd3ae --- /dev/null +++ b/unittests/test_middleware_login_required.py @@ -0,0 +1,65 @@ +from types import SimpleNamespace +from unittest.mock import Mock, patch + +from django.contrib.auth.models import AnonymousUser +from django.http import HttpResponse +from django.test import RequestFactory +from rest_framework.authentication import TokenAuthentication +from rest_framework.authtoken.models import Token +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from dojo.middleware import LoginRequiredMiddleware +from dojo.models import User + +from .dojo_test_case import DojoTestCase, versioned_fixtures + + +class TokenAuthenticatedView(APIView): + authentication_classes = (TokenAuthentication,) + permission_classes = (IsAuthenticated,) + + def get(self, request): + return Response({"username": request.user.username}) + + +@versioned_fixtures +class TestLoginRequiredMiddlewareDdUser(DojoTestCase): + fixtures = ["dojo_testdata.json"] + + def setUp(self): + super().setUp() + self.factory = RequestFactory() + self.admin = User.objects.get(username="admin") + + def test_sets_dd_user_for_session_authenticated_request(self): + request = self.factory.get("/dashboard") + request.user = self.admin + + middleware = LoginRequiredMiddleware(lambda _request: HttpResponse("OK")) + fake_uwsgi = SimpleNamespace(set_logvar=Mock()) + + with patch.dict("sys.modules", {"uwsgi": fake_uwsgi}): + response = middleware(request) + + self.assertEqual(200, response.status_code) + fake_uwsgi.set_logvar.assert_called_once_with("dd_user", str(self.admin)) + + def test_sets_dd_user_for_drf_token_authenticated_request(self): + token, _ = Token.objects.get_or_create(user=self.admin) + + request = self.factory.get( + "/api/v2/mock-endpoint/", + HTTP_AUTHORIZATION=f"Token {token.key}", + ) + request.user = AnonymousUser() + + middleware = LoginRequiredMiddleware(TokenAuthenticatedView.as_view()) + fake_uwsgi = SimpleNamespace(set_logvar=Mock()) + + with patch.dict("sys.modules", {"uwsgi": fake_uwsgi}): + response = middleware(request) + + self.assertEqual(200, response.status_code) + fake_uwsgi.set_logvar.assert_called_once_with("dd_user", str(self.admin)) diff --git a/unittests/test_rest_framework.py b/unittests/test_rest_framework.py index 3354dce111a..5666f4de1d8 100644 --- a/unittests/test_rest_framework.py +++ b/unittests/test_rest_framework.py @@ -856,6 +856,63 @@ def test_detail_configuration_not_authorized(self): response = self.client.get(relative_url) self.assertEqual(200, response.status_code, response.content[:1000]) + class RelatedObjectsTest(BaseClassTest): + def test_notes_can_be_added_by_users_with_read_only_permissions(self): + self.setUp_global_reader() + response = self.client.get(self.url, format="json") + self.assertEqual(200, response.status_code, response.content[:1000]) + engagement_id = response.data["results"][0]["id"] + # Attempt to add a note with reader permissions + relative_url = f"{self.url}{engagement_id}/notes/" + response = self.client.post(relative_url, {"entry": "string"}) + self.assertEqual(201, response.status_code, response.content[:1000]) + + @parameterized.expand( + [ + ("files", {"title": "test"}), + ("tags", {"tags": ["apple", "banana", "cherry"]}), + ], + ) + def test_related_objects(self, related_object_path, payload): + """ + Tests that BaseRelatedObjectPermission enforces the permissions not associated + with the base object. For example, even though a request to add a tag to an + engagement is a POST, we do not need engagement add permissions, but rather + engagement edit permissions since that is what is defined in the + UserHasEngagementRelatedObjectPermission class + """ + self.setUp_global_reader() + # Skip tags for engagement and tests + if related_object_path == "tags" and self.endpoint_model in {Engagement, Test}: + return + # Get an object + response = self.client.get(self.url, format="json") + self.assertEqual(200, response.status_code, response.content[:1000]) + object_id = response.data["results"][0]["id"] + # Attempt to add a related object + relative_url = f"{self.url}{object_id}/{related_object_path}/" + response = self.client.post(relative_url, payload) + self.assertEqual(403, response.status_code, response.content[:1000]) + # Now switch to a user with edit permissions (but not create) + self.setUp_global_writer() + # Retry adding the related object + if related_object_path == "files": + # Convert bytes to a mock uploaded file + response = self.client.post( + relative_url, + { + "file": SimpleUploadedFile( + name="test_file.txt", + content=b"empty", + content_type="text/plain", + ), + **payload, + }, + ) + else: + response = self.client.post(relative_url, payload) + self.assertIn(response.status_code, [200, 201], response.content[:1000]) + @versioned_fixtures class AppAnalysisTest(BaseClass.BaseClassTest): @@ -1487,7 +1544,7 @@ def test_update_object_not_authorized(self): @versioned_fixtures -class EngagementTest(BaseClass.BaseClassTest): +class EngagementTest(BaseClass.RelatedObjectsTest, BaseClass.BaseClassTest): fixtures = ["dojo_testdata.json"] def __init__(self, *args, **kwargs): @@ -1517,42 +1574,6 @@ def __init__(self, *args, **kwargs): self.deleted_objects = 23 BaseClass.RESTEndpointTest.__init__(self, *args, **kwargs) - @parameterized.expand( - [ - ("files", {"title": "test", "file": b"empty"}), - ("notes", {"entry": "string"}), - ], - ) - def test_related_objects(self, related_object_path, payload): - """ - Tests that BaseRelatedObjectPermission enforces the permissions not associated - with the base object. For example, even though a request to add a note to an - engagement is a POST, we do not need engagement add permissions, but rather - engagement edit permissions since that is what is defined in the - UserHasEngagementRelatedObjectPermission class - """ - self.setUp_global_reader() - # Get an engagement - response = self.client.get(self.url, format="json") - self.assertEqual(200, response.status_code, response.content[:1000]) - engagement_id = response.data["results"][0]["id"] - # Attempt to add a related object - relative_url = f"{self.url}{engagement_id}/{related_object_path}/" - response = self.client.post(relative_url, payload) - self.assertEqual(403, response.status_code, response.content[:1000]) - # Now switch to a user with edit permissions (but not create) - self.setUp_global_writer() - # Retry adding the related object - if related_object_path == "files": - # Convert bytes to a mock uploaded file - payload["file"] = SimpleUploadedFile( - name="test_file.txt", - content=payload["file"], # the b"empty" - content_type="text/plain", - ) - response = self.client.post(relative_url, payload) - self.assertEqual(201, response.status_code, response.content[:1000]) - @versioned_fixtures class RiskAcceptanceTest(BaseClass.BaseClassTest): @@ -1726,7 +1747,7 @@ def test_file_with_quoted_name(self): @versioned_fixtures -class FindingsTest(BaseClass.BaseClassTest): +class FindingsTest(BaseClass.RelatedObjectsTest, BaseClass.BaseClassTest): fixtures = ["dojo_testdata.json"] def __init__(self, *args, **kwargs): @@ -1767,7 +1788,8 @@ def __init__(self, *args, **kwargs): "files": [], "tags": ["tag1", "tag_2"], } - self.update_fields = {"duplicate": False, "active": True, "push_to_jira": "True", "tags": ["finding_tag_new"]} + # Do not push to jira here as it will make the request fail due to jira not being configured + self.update_fields = {"duplicate": False, "active": True, "tags": ["finding_tag_new"]} self.test_type = TestType.OBJECT_PERMISSIONS self.permission_check_class = Finding self.permission_create = Permissions.Finding_Add @@ -2415,7 +2437,7 @@ def test_severity_validation(self): @versioned_fixtures -class TestsTest(BaseClass.BaseClassTest): +class TestsTest(BaseClass.RelatedObjectsTest, BaseClass.BaseClassTest): fixtures = ["dojo_testdata.json"] def __init__(self, *args, **kwargs):