Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/pr-labeler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ jobs:
- uses: actions/labeler@v5
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
sync-labels: true
2 changes: 1 addition & 1 deletion components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "defectdojo",
"version": "2.31.1",
"version": "2.31.2",
"license" : "BSD-3-Clause",
"private": true,
"dependencies": {
Expand Down
3 changes: 2 additions & 1 deletion docs/content/en/integrations/google-sheets-sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ draft: false
weight: 7
---

**Please note - the Google Sheets feature has been deprecated as of DefectDojo version 2.21.0 - these documents are for reference only.**

With the Google Sheets sync feature, DefectDojo allow the users to
export all the finding details of each test into a separate Google
Expand Down Expand Up @@ -112,4 +113,4 @@ If a Google Spreadsheet is already created for the Test:
After creating a Google Spreadsheet, users can review and edit Finding
details using the Google Sheet. If any change is done in the Google
Sheet users can click the **Sync Google Sheet** button to get those
changes into DefectDojo.
changes into DefectDojo.
2 changes: 1 addition & 1 deletion dojo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@
# Django starts so that shared_task will use this app.
from .celery import app as celery_app # noqa

__version__ = '2.31.1'
__version__ = '2.31.2'
__url__ = 'https://github.com/DefectDojo/django-DefectDojo'
__docs__ = 'https://documentation.defectdojo.com'
2 changes: 1 addition & 1 deletion dojo/endpoint/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def get_endpoint_ids(endpoints):
hosts = []
ids = []
for e in endpoints:
key = e.host + '-' + str(e.product.id)
key = f"{e.host}-{e.product.id}"
if key in hosts:
continue
else:
Expand Down
2 changes: 1 addition & 1 deletion dojo/engagement/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ def post(self, request, eid=None, pid=None):
return HttpResponseRedirect(
reverse('view_test', args=(test.id, )))

return HttpResponseRedirect(reverse('view_test', args=(test.id, )))
return HttpResponseRedirect(reverse('import_scan_results', args=(engagement.id, )))


@user_is_authorized(Engagement, Permissions.Engagement_Edit, 'eid')
Expand Down
8 changes: 5 additions & 3 deletions dojo/jira_link/helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,17 +863,19 @@ def update_jira_issue(obj, *args, **kwargs):
summary=jira_summary(obj),
description=jira_description(obj),
component_name=jira_project.component if not issue.fields.components else None,
labels=labels,
labels=labels + issue.fields.labels,
environment=jira_environment(obj),
priority_name=jira_priority(obj),
# Do not update the priority in jira after creation as this could have changed in jira, but should not change in dojo
# priority_name=jira_priority(obj),
issuetype_fields=issuetype_fields)

logger.debug('sending fields to JIRA: %s', fields)

issue.update(
summary=fields['summary'],
description=fields['description'],
priority=fields['priority'],
# Do not update the priority in jira after creation as this could have changed in jira, but should not change in dojo
# priority=fields['priority'],
fields=fields)

push_status_to_jira(obj, jira_instance, jira, issue)
Expand Down
10 changes: 9 additions & 1 deletion dojo/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from django.utils.html import escape
from pytz import all_timezones
from polymorphic.models import PolymorphicModel
from polymorphic.managers import PolymorphicManager
from multiselectfield import MultiSelectField
from django import forms
from django.utils.translation import gettext as _
Expand Down Expand Up @@ -4355,6 +4356,8 @@ class Meta:
help_text=_("If selected, user doesn't have to answer this question"))

text = models.TextField(blank=False, help_text=_('The question text'), default='')
objects = models.Manager()
polymorphic = PolymorphicManager()

def __str__(self):
return self.text
Expand All @@ -4364,6 +4367,7 @@ class TextQuestion(Question):
'''
Question with a text answer
'''
objects = PolymorphicManager()

def get_form(self):
'''
Expand Down Expand Up @@ -4397,8 +4401,8 @@ class ChoiceQuestion(Question):

multichoice = models.BooleanField(default=False,
help_text=_("Select one or more"))

choices = models.ManyToManyField(Choice)
objects = PolymorphicManager()

def get_form(self):
'''
Expand Down Expand Up @@ -4476,13 +4480,16 @@ class Answer(PolymorphicModel, TimeStampedModel):
null=False,
blank=False,
on_delete=models.CASCADE)
objects = models.Manager()
polymorphic = PolymorphicManager()


class TextAnswer(Answer):
answer = models.TextField(
blank=False,
help_text=_('The answer text'),
default='')
objects = PolymorphicManager()

def __str__(self):
return self.answer
Expand All @@ -4492,6 +4499,7 @@ class ChoiceAnswer(Answer):
answer = models.ManyToManyField(
Choice,
help_text=_('The selected choices as the answer'))
objects = PolymorphicManager()

def __str__(self):
if len(self.answer.all()):
Expand Down
21 changes: 12 additions & 9 deletions dojo/survey/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ def delete_engagement_survey(request, eid, sid):
if request.method == 'POST':
form = Delete_Questionnaire_Form(request.POST, instance=survey)
if form.is_valid():
answers = Answer.objects.filter(
answers = Answer.polymorphic.filter(
question__in=[
question.id for question in survey.survey.questions.all()],
question.id for question in Question.polymorphic.filter(engagement_survey=survey.survey)],
answered_survey=survey)
for answer in answers:
answer.delete()
Expand Down Expand Up @@ -95,7 +95,7 @@ def answer_questionnaire(request, eid, sid):
prefix=str(q.id),
answered_survey=survey,
question=q, form_tag=False)
for q in survey.survey.questions.all()]
for q in Question.polymorphic.filter(engagement_survey=survey.survey)]

questions_are_valid = []

Expand Down Expand Up @@ -184,7 +184,7 @@ def get_answered_questions(survey=None, read_only=False):
answered_survey=survey,
question=q,
form_tag=False)
for q in survey.survey.questions.all()]
for q in Question.polymorphic.filter(engagement_survey=survey.survey)]

if read_only:
for question in questions:
Expand Down Expand Up @@ -416,7 +416,7 @@ def questionnaire(request):

@user_is_configuration_authorized('dojo.view_question')
def questions(request):
questions = Question.objects.all()
questions = Question.polymorphic.all()
questions = QuestionFilter(request.GET, queryset=questions)
paged_questions = get_page_items(request, questions.qs, 25)
add_breadcrumb(title="Questions", top_level=False, request=request)
Expand Down Expand Up @@ -500,7 +500,10 @@ def create_question(request):

@user_is_configuration_authorized('dojo.change_question')
def edit_question(request, qid):
question = get_object_or_404(Question, id=qid)
try:
question = Question.polymorphic.get(id=qid)
except Question.DoesNotExist:
return Http404()
survey = Engagement_Survey.objects.filter(questions__in=[question])
reverted = False
answered = []
Expand Down Expand Up @@ -652,7 +655,7 @@ def delete_empty_questionnaire(request, esid):
form = Delete_Questionnaire_Form(request.POST, instance=survey)
if form.is_valid():
answers = Answer.objects.filter(
question__in=[question.id for question in survey.survey.questions.all()],
question__in=[question.id for question in Question.polymorphic.filter(engagement_survey=survey.survey)],
answered_survey=survey)
for answer in answers:
answer.delete()
Expand Down Expand Up @@ -740,7 +743,7 @@ def answer_empty_survey(request, esid):
engagement_survey=engagement_survey,
question=q,
form_tag=False)
for q in engagement_survey.questions.all()
for q in Question.polymorphic.filter(engagement_survey=engagement_survey)
]

if request.method == 'POST':
Expand All @@ -753,7 +756,7 @@ def answer_empty_survey(request, esid):
answered_survey=survey,
question=q,
form_tag=False)
for q in survey.survey.questions.all()
for q in Question.polymorphic.filter(engagement_survey=survey.survey)
]

questions_are_valid = []
Expand Down
2 changes: 1 addition & 1 deletion dojo/templates/dojo/engagement.html
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ <h3 class="has-filters">
</div>
</td>
<td> {{ e.target_start }} - {{ e.target_end }}
{% if e.is_overdue %}
{% if e.is_overdue and e.status != 'Completed' %}
<span class="tag-label warning-color">
{{ e.target_end|overdue }} overdue
</span>
Expand Down
2 changes: 1 addition & 1 deletion dojo/templates/dojo/engagements_all.html
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ <h3 class="has-filters">
{% endif %}
<td> {{ e.status }} </td>
<td> {{ e.target_start }} - {{ e.target_end }}
{% if e.is_overdue and e.active %}
{% if e.is_overdue and e.active and e.status != 'Completed' %}
<sup><div class="tag-label warning-color">{{ e.target_end|overdue }} overdue</div></sup>
{% endif %}
</td>
Expand Down
2 changes: 1 addition & 1 deletion dojo/templates/dojo/snippets/engagement_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ <h4> {% if status == "open" %}Active{% elif status == "paused" %}Paused {% else
</td>
<td class="text-left" class="nowrap">{{ eng.target_start|datediff_time:eng.target_end }}
{% if status == "open" %}
{% if eng.is_overdue %}
{% if eng.is_overdue and eng.status != 'Completed' %}
<sup>
<div class="tag-label warning-color">
{{ eng.target_end|overdue }} overdue
Expand Down
26 changes: 18 additions & 8 deletions dojo/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,14 +483,19 @@ def deduplicate_uid_or_hash_code(new_finding):


def set_duplicate(new_finding, existing_finding):
deduplicationLogger.debug(f"new_finding.status(): {new_finding.id} {new_finding.status()}")
deduplicationLogger.debug(f"existing_finding.status(): {existing_finding.id} {existing_finding.status()}")
if existing_finding.duplicate:
logger.debug('existing finding: %s:%s:duplicate=%s;duplicate_finding=%s', existing_finding.id, existing_finding.title, existing_finding.duplicate, existing_finding.duplicate_finding.id if existing_finding.duplicate_finding else 'None')
deduplicationLogger.debug('existing finding: %s:%s:duplicate=%s;duplicate_finding=%s', existing_finding.id, existing_finding.title, existing_finding.duplicate, existing_finding.duplicate_finding.id if existing_finding.duplicate_finding else 'None')
raise Exception("Existing finding is a duplicate")
if existing_finding.id == new_finding.id:
raise Exception("Can not add duplicate to itself")
deduplicationLogger.debug('Setting new finding ' + str(new_finding.id) + ' as a duplicate of existing finding ' + str(existing_finding.id))
if is_duplicate_reopen(new_finding, existing_finding):
set_duplicate_reopen(new_finding, existing_finding)
raise Exception("Found a regression. Ignore this so that a new duplicate chain can be made")
if new_finding.duplicate and finding_mitigated(existing_finding):
raise Exception("Skip this finding as we do not want to attach a new duplicate to a mitigated finding")

deduplicationLogger.debug('Setting new finding ' + str(new_finding.id) + ' as a duplicate of existing finding ' + str(existing_finding.id))
new_finding.duplicate = True
new_finding.active = False
new_finding.verified = False
Expand All @@ -509,11 +514,16 @@ def set_duplicate(new_finding, existing_finding):
super(Finding, existing_finding).save()


def is_duplicate_reopen(new_finding, existing_finding):
if (existing_finding.is_mitigated or existing_finding.mitigated) and not existing_finding.out_of_scope and not existing_finding.false_p and new_finding.active and not new_finding.is_mitigated:
return True
else:
return False
def is_duplicate_reopen(new_finding, existing_finding) -> bool:
return finding_mitigated(existing_finding) and finding_not_human_set_status(existing_finding) and not finding_mitigated(new_finding)


def finding_mitigated(finding: Finding) -> bool:
return finding.active is False and (finding.is_mitigated is True or finding.mitigated is not None)


def finding_not_human_set_status(finding: Finding) -> bool:
return finding.out_of_scope is False and finding.false_p is False


def set_duplicate_reopen(new_finding, existing_finding):
Expand Down
4 changes: 2 additions & 2 deletions helm/defectdojo/Chart.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
apiVersion: v2
appVersion: "2.31.1"
appVersion: "2.31.2"
description: A Helm chart for Kubernetes to install DefectDojo
name: defectdojo
version: 1.6.109
version: 1.6.110
icon: https://www.defectdojo.org/img/favicon.ico
maintainers:
- name: madchap
Expand Down