Skip to content

Commit

Permalink
Merge pull request #11076 from DefectDojo/master-into-dev/2.39.1-2.40…
Browse files Browse the repository at this point in the history
….0-dev

Release: Merge back 2.39.1 into dev from: master-into-dev/2.39.1-2.40.0-dev
  • Loading branch information
rossops authored Oct 15, 2024
2 parents a0ac0f6 + 643461e commit aae3e7a
Show file tree
Hide file tree
Showing 32 changed files with 567 additions and 79 deletions.
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,15 +132,14 @@ Core Moderators can help you with pull requests or feedback on dev ideas:
* Cody Maffucci ([@Maffooch](https://github.com/maffooch) | [LinkedIn](https://www.linkedin.com/in/cody-maffucci))

Moderators can help you with pull requests or feedback on dev ideas:
* Damien Carol ([@damiencarol](https://github.com/damiencarol) | [LinkedIn](https://www.linkedin.com/in/damien-carol/))
* Jannik Jürgens ([@alles-klar](https://github.com/alles-klar))
* Dubravko Sever ([@dsever](https://github.com/dsever))
* Charles Neill ([@cneill](https://github.com/cneill) | [@ccneill](https://twitter.com/ccneill))
* Jay Paz ([@jjpaz](https://twitter.com/jjpaz))
* Blake Owens ([@blakeaowens](https://github.com/blakeaowens))

## Hall of Fame

* Jannik Jürgens ([@alles-klar](https://github.com/alles-klar)) - Jannik was a long time contributor and moderator for
DefectDojo and made significant contributions to many areas of the platform. Jannik was instrumental in pioneering
and optimizing deployment methods.
* Valentijn Scholten ([@valentijnscholten](https://github.com/valentijnscholten) |
[Sponsor](https://github.com/sponsors/valentijnscholten) |
[LinkedIn](https://www.linkedin.com/in/valentijn-scholten/)) - Valentijn served as a core moderator for 3 years.
Expand Down
1 change: 1 addition & 0 deletions dojo/api_v2/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -1517,6 +1517,7 @@ class JiraProjectViewSet(
"jira_instance",
"product",
"engagement",
"enabled",
"component",
"project_key",
"push_all_issues",
Expand Down
18 changes: 18 additions & 0 deletions dojo/db_migrations/0217_jira_project_enabled.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.0.8 on 2024-10-10 17:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('dojo', '0216_alter_jira_project_push_all_issues'),
]

operations = [
migrations.AddField(
model_name='jira_project',
name='enabled',
field=models.BooleanField(blank=True, default=True, help_text='When disabled, Findings will no longer be pushed to Jira, even if they have already been pushed previously.', verbose_name='Enable Connection With Jira Project'),
),
]
3 changes: 3 additions & 0 deletions dojo/fixtures/defect_dojo_sample_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -35210,6 +35210,7 @@
"engagement": null,
"component": "",
"push_all_issues": false,
"enabled": true,
"enable_engagement_epic_mapping": true,
"push_notes": false,
"product_jira_sla_notification": false,
Expand All @@ -35227,6 +35228,7 @@
"engagement": null,
"component": "",
"push_all_issues": true,
"enabled": true,
"enable_engagement_epic_mapping": true,
"push_notes": true,
"product_jira_sla_notification": false,
Expand All @@ -35244,6 +35246,7 @@
"engagement": null,
"component": "",
"push_all_issues": false,
"enabled": true,
"enable_engagement_epic_mapping": false,
"push_notes": false,
"product_jira_sla_notification": false,
Expand Down
12 changes: 9 additions & 3 deletions dojo/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -2422,7 +2422,7 @@ def clean(self):
return self.cleaned_data


class JIRAForm(BaseJiraForm):
class AdvancedJIRAForm(BaseJiraForm):
issue_template_dir = forms.ChoiceField(required=False,
choices=JIRA_TEMPLATE_CHOICES,
help_text="Choose the folder containing the Django templates used to render the JIRA issue description. These are stored in dojo/templates/issue-trackers. Leave empty to use the default jira_full templates.")
Expand All @@ -2442,8 +2442,11 @@ class Meta:
exclude = [""]


class ExpressJIRAForm(BaseJiraForm):
class JIRAForm(BaseJiraForm):
issue_key = forms.CharField(required=True, help_text="A valid issue ID is required to gather the necessary information.")
issue_template_dir = forms.ChoiceField(required=False,
choices=JIRA_TEMPLATE_CHOICES,
help_text="Choose the folder containing the Django templates used to render the JIRA issue description. These are stored in dojo/templates/issue-trackers. Leave empty to use the default jira_full templates.")

class Meta:
model = JIRA_Instance
Expand Down Expand Up @@ -2853,7 +2856,7 @@ class JIRAProjectForm(forms.ModelForm):
class Meta:
model = JIRA_Project
exclude = ["product", "engagement"]
fields = ["inherit_from_product", "jira_instance", "project_key", "issue_template_dir", "epic_issue_type_name", "component", "custom_fields", "jira_labels", "default_assignee", "add_vulnerability_id_to_jira_label", "push_all_issues", "enable_engagement_epic_mapping", "push_notes", "product_jira_sla_notification", "risk_acceptance_expiration_notification"]
fields = ["inherit_from_product", "jira_instance", "project_key", "issue_template_dir", "epic_issue_type_name", "component", "custom_fields", "jira_labels", "default_assignee", "enabled", "add_vulnerability_id_to_jira_label", "push_all_issues", "enable_engagement_epic_mapping", "push_notes", "product_jira_sla_notification", "risk_acceptance_expiration_notification"]

def __init__(self, *args, **kwargs):
from dojo.jira_link import helper as jira_helper
Expand Down Expand Up @@ -2891,6 +2894,7 @@ def __init__(self, *args, **kwargs):
self.fields["custom_fields"].disabled = False
self.fields["default_assignee"].disabled = False
self.fields["jira_labels"].disabled = False
self.fields["enabled"].disabled = False
self.fields["add_vulnerability_id_to_jira_label"].disabled = False
self.fields["push_all_issues"].disabled = False
self.fields["enable_engagement_epic_mapping"].disabled = False
Expand All @@ -2915,6 +2919,7 @@ def __init__(self, *args, **kwargs):
self.initial["custom_fields"] = jira_project_product.custom_fields
self.initial["default_assignee"] = jira_project_product.default_assignee
self.initial["jira_labels"] = jira_project_product.jira_labels
self.initial["enabled"] = jira_project_product.enabled
self.initial["add_vulnerability_id_to_jira_label"] = jira_project_product.add_vulnerability_id_to_jira_label
self.initial["push_all_issues"] = jira_project_product.push_all_issues
self.initial["enable_engagement_epic_mapping"] = jira_project_product.enable_engagement_epic_mapping
Expand All @@ -2930,6 +2935,7 @@ def __init__(self, *args, **kwargs):
self.fields["custom_fields"].disabled = True
self.fields["default_assignee"].disabled = True
self.fields["jira_labels"].disabled = True
self.fields["enabled"].disabled = True
self.fields["add_vulnerability_id_to_jira_label"].disabled = True
self.fields["push_all_issues"].disabled = True
self.fields["enable_engagement_epic_mapping"].disabled = True
Expand Down
2 changes: 1 addition & 1 deletion dojo/home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def dashboard(request: HttpRequest) -> HttpResponse:

date_range = [today - timedelta(days=6), today] # 7 days (6 days plus today)
finding_count = findings\
.filter(created__date__range=date_range)\
.filter(date__range=date_range)\
.count()
mitigated_count = findings\
.filter(mitigated__date__range=date_range)\
Expand Down
44 changes: 35 additions & 9 deletions dojo/jira_link/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,12 @@ def is_jira_configured_and_enabled(obj):
if not is_jira_enabled():
return False

if get_jira_project(obj) is None:
jira_project = get_jira_project(obj)
if jira_project is None:
logger.debug('JIRA project not found for: "%s" not doing anything', obj)
return False

return True
return jira_project.enabled


def is_push_to_jira(instance, push_to_jira_parameter=None):
Expand All @@ -88,6 +89,10 @@ def is_push_to_jira(instance, push_to_jira_parameter=None):
if push_to_jira_parameter is not None:
return push_to_jira_parameter

# Check to see if jira project is disabled to prevent pushing findings
if not jira_project.enabled:
return False

# push_to_jira was not specified, so look at push_all_issues in JIRA_Project
return jira_project.push_all_issues

Expand All @@ -96,8 +101,10 @@ def is_push_all_issues(instance):
if not is_jira_configured_and_enabled(instance):
return False

jira_project = get_jira_project(instance)
if jira_project:
if jira_project := get_jira_project(instance):
# Check to see if jira project is disabled to prevent pushing findings
if not jira_project.enabled:
return None
return jira_project.push_all_issues
return None

Expand All @@ -108,9 +115,13 @@ def is_push_all_issues(instance):
# returns True/False, error_message, error_code
def can_be_pushed_to_jira(obj, form=None):
# logger.debug('can be pushed to JIRA: %s', finding_or_form)
if not get_jira_project(obj):
jira_project = get_jira_project(obj)
if not jira_project:
return False, f"{to_str_typed(obj)} cannot be pushed to jira as there is no jira project configuration for this product.", "error_no_jira_project"

if not jira_project.enabled:
return False, f"{to_str_typed(obj)} cannot be pushed to jira as the jira project is not enabled.", "error_no_jira_project"

if not hasattr(obj, "has_jira_issue"):
return False, f"{to_str_typed(obj)} cannot be pushed to jira as there is no jira_issue attribute.", "error_no_jira_issue_attribute"

Expand Down Expand Up @@ -1389,6 +1400,13 @@ def add_comment(obj, note, force_push=False, **kwargs):

def add_simple_jira_comment(jira_instance, jira_issue, comment):
try:
jira_project = get_jira_project(jira_issue)

# Check to see if jira project is disabled to prevent pushing findings
if not jira_project.enabled:
log_jira_generic_alert("JIRA Project is disabled", "Push to JIRA for Epic skipped because JIRA Project is disabled")
return False

jira = get_jira_connection(jira_instance)

jira.add_comment(
Expand All @@ -1403,9 +1421,13 @@ def add_simple_jira_comment(jira_instance, jira_issue, comment):
def finding_link_jira(request, finding, new_jira_issue_key):
logger.debug("linking existing jira issue %s for finding %i", new_jira_issue_key, finding.id)

existing_jira_issue = jira_get_issue(get_jira_project(finding), new_jira_issue_key)

jira_project = get_jira_project(finding)
existing_jira_issue = jira_get_issue(jira_project, new_jira_issue_key)

# Check to see if jira project is disabled to prevent pushing findings
if not jira_project.enabled:
add_error_message_to_response("Push to JIRA for finding skipped because JIRA Project is disabled")
return False

if not existing_jira_issue:
raise ValueError("JIRA issue not found or cannot be retrieved: " + new_jira_issue_key)
Expand Down Expand Up @@ -1433,9 +1455,13 @@ def finding_link_jira(request, finding, new_jira_issue_key):
def finding_group_link_jira(request, finding_group, new_jira_issue_key):
logger.debug("linking existing jira issue %s for finding group %i", new_jira_issue_key, finding_group.id)

existing_jira_issue = jira_get_issue(get_jira_project(finding_group), new_jira_issue_key)

jira_project = get_jira_project(finding_group)
existing_jira_issue = jira_get_issue(jira_project, new_jira_issue_key)

# Check to see if jira project is disabled to prevent pushing findings
if not jira_project.enabled:
add_error_message_to_response("Push to JIRA for group skipped because JIRA Project is disabled")
return False

if not existing_jira_issue:
raise ValueError("JIRA issue not found or cannot be retrieved: " + new_jira_issue_key)
Expand Down
3 changes: 2 additions & 1 deletion dojo/jira_link/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
re_path(r"^jira/webhook/(?P<secret>[\w-]+)$", views.webhook, name="jira_web_hook_secret"),
re_path(r"^jira/webhook/", views.webhook, name="jira_web_hook"),
re_path(r"^jira/add", views.NewJiraView.as_view(), name="add_jira"),
re_path(r"^jira/advanced", views.AdvancedJiraView.as_view(), name="add_jira_advanced"),
re_path(r"^jira/(?P<jid>\d+)/edit$", views.EditJiraView.as_view(), name="edit_jira"),
re_path(r"^jira/(?P<tid>\d+)/delete$", views.DeleteJiraView.as_view(), name="delete_jira"),
re_path(r"^jira$", views.ListJiraView.as_view(), name="jira"),
re_path(r"^jira/express", views.ExpressJiraView.as_view(), name="express_jira")]
]
24 changes: 12 additions & 12 deletions dojo/jira_link/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from dojo.authorization.authorization import user_has_configuration_permission

# Local application/library imports
from dojo.forms import DeleteJIRAInstanceForm, ExpressJIRAForm, JIRAForm
from dojo.forms import AdvancedJIRAForm, DeleteJIRAInstanceForm, JIRAForm
from dojo.models import JIRA_Instance, JIRA_Issue, Notes, System_Settings, User
from dojo.notifications.helper import create_notification
from dojo.utils import add_breadcrumb, add_error_message_to_response, get_setting
Expand Down Expand Up @@ -285,24 +285,24 @@ def get_custom_field(jira, label):
return field


class ExpressJiraView(View):
class NewJiraView(View):
def get_template(self):
return "dojo/express_new_jira.html"
return "dojo/new_jira.html"

def get_fallback_template(self):
return "dojo/new_jira.html"
return "dojo/new_jira_advanced.html"

def get_form_class(self):
return ExpressJIRAForm
return JIRAForm

def get_fallback_form_class(self):
return JIRAForm
return AdvancedJIRAForm

def get(self, request):
if not user_has_configuration_permission(request.user, "dojo.add_jira_instance"):
raise PermissionDenied
jform = self.get_form_class()()
add_breadcrumb(title="New Jira Configuration (Express)", top_level=False, request=request)
add_breadcrumb(title="New Jira Configuration", top_level=False, request=request)
return render(request, self.get_template(), {"jform": jform})

def post(self, request):
Expand Down Expand Up @@ -391,18 +391,18 @@ def post(self, request):
return render(request, self.get_template(), {"jform": jform})


class NewJiraView(View):
class AdvancedJiraView(View):
def get_template(self):
return "dojo/new_jira.html"
return "dojo/new_jira_advanced.html"

def get_form_class(self):
return JIRAForm
return AdvancedJIRAForm

def get(self, request):
if not user_has_configuration_permission(request.user, "dojo.add_jira_instance"):
raise PermissionDenied
jform = self.get_form_class()()
add_breadcrumb(title="New Jira Configuration", top_level=False, request=request)
add_breadcrumb(title="New Jira Configuration (Advanced)", top_level=False, request=request)
return render(request, self.get_template(), {"jform": jform})

def post(self, request):
Expand Down Expand Up @@ -442,7 +442,7 @@ def get_template(self):
return "dojo/edit_jira.html"

def get_form_class(self):
return JIRAForm
return AdvancedJIRAForm

def get(self, request, jid=None):
if not user_has_configuration_permission(request.user, "dojo.change_jira_instance"):
Expand Down
10 changes: 9 additions & 1 deletion dojo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3918,9 +3918,17 @@ class JIRA_Project(models.Model):
push_notes = models.BooleanField(default=False, blank=True)
product_jira_sla_notification = models.BooleanField(default=False, blank=True, verbose_name=_("Send SLA notifications as comment?"))
risk_acceptance_expiration_notification = models.BooleanField(default=False, blank=True, verbose_name=_("Send Risk Acceptance expiration notifications as comment?"))
enabled = models.BooleanField(
verbose_name=_("Enable Connection With Jira Project"),
help_text=_("When disabled, Findings will no longer be pushed to Jira, even if they have already been pushed previously."),
default=True,
blank=True)

def __str__(self):
return ("%s: " + self.project_key + "(%s)") % (str(self.id), str(self.jira_instance.url) if self.jira_instance else "None")
value = f"{self.id}: {self.project_key} ({self.jira_instance.url if self.jira_instance else 'None'})"
if not self.enabled:
value += " - Not Connected"
return value

def clean(self):
if not self.jira_instance:
Expand Down
2 changes: 1 addition & 1 deletion dojo/settings/.settings.dist.py.sha256sum
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7ad5e28c5c96c6a3d40826bf32cea96c131825bd4eca857276b0458e26de36a3
4d3e91f176b73278750dc2f46d27cd4fe2b47d24682ad06d6267880bbdec599c
2 changes: 2 additions & 0 deletions dojo/settings/settings.dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1733,6 +1733,8 @@ def saml2_attrib_map_format(dict):
"RHEA": "https://access.redhat.com/errata/",
"FEDORA": "https://bodhi.fedoraproject.org/updates/",
"ALSA": "https://osv.dev/vulnerability/", # e.g. https://osv.dev/vulnerability/ALSA-2024:0827
"USN": "https://ubuntu.com/security/notices/", # e.g. https://ubuntu.com/security/notices/USN-6642-1
"DLA": "https://security-tracker.debian.org/tracker/", # e.g. https://security-tracker.debian.org/tracker/DLA-3917-1
}
# List of acceptable file types that can be uploaded to a given object via arbitrary file upload
FILE_UPLOAD_TYPES = env("DD_FILE_UPLOAD_TYPES")
Expand Down
4 changes: 2 additions & 2 deletions dojo/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -589,8 +589,8 @@
{% block support-tab %}
<li>
<a href="{% url 'support' %}" aria-disabled="true" aria-expanded="false" aria-label="Support">
<i class="fa-solid fa-life-ring fa-fw"></i>
<span>{% trans "Get Support" %}</span>
<i class="fa-solid fa-level-up fa-fw"></i>
<span>{% trans "Upgrade" %}</span>
</a>
</li>
{% endblock %}
Expand Down
16 changes: 0 additions & 16 deletions dojo/templates/dojo/express_new_jira.html

This file was deleted.

8 changes: 4 additions & 4 deletions dojo/templates/dojo/jira.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ <h3 class="has-filters">
<ul class="dropdown-menu dropdown-menu-right" role="menu"
aria-labelledby="dropdownMenu1">
<li role="presentation">
<a href="{% url 'express_jira' %}">
<i class="fa-solid fa-plus"></i> Add Jira Instance (Express)
<a href="{% url 'add_jira' %}">
<i class="fa-solid fa-plus"></i> Add Jira Instance
</a>
</li>
<li role="presentation">
<a href="{% url 'add_jira' %}">
<i class="fa-solid fa-plus"></i> Add Jira Instance
<a href="{% url 'add_jira_advanced' %}">
<i class="fa-solid fa-plus"></i> Add Jira Instance (Advanced)
</a>
</li>
</ul>
Expand Down
Loading

0 comments on commit aae3e7a

Please sign in to comment.