diff --git a/.github/workflows/test-helm-chart.yml b/.github/workflows/test-helm-chart.yml index 8c2837706ca..5fcf845076a 100644 --- a/.github/workflows/test-helm-chart.yml +++ b/.github/workflows/test-helm-chart.yml @@ -20,13 +20,11 @@ jobs: fetch-depth: 0 - name: Set up Helm - uses: azure/setup-helm@v4 - with: - version: v3.4.0 + uses: azure/setup-helm@v4.1.0 - uses: actions/setup-python@v5 with: - python-version: 3.7 + python-version: 3.9 - name: Configure Helm repos run: |- @@ -36,14 +34,17 @@ jobs: - name: Set up chart-testing uses: helm/chart-testing-action@v2.6.1 + with: + yamale_version: 4.0.4 + yamllint_version: 1.35.1 - name: Determine target branch id: ct-branch-target run: | if [ ! -z ${GITHUB_BASE_REF} ]; then - echo "ct-branch=${GITHUB_BASE_REF}" >> $GITHUB_ENV + echo "ct-branch=${GITHUB_BASE_REF}" >> $GITHUB_ENV else - echo "ct-branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + echo "ct-branch=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV fi - name: Run chart-testing (list-changed) @@ -58,7 +59,7 @@ jobs: # x.y.z gets bumped automatically when doing a release - name: Run chart-testing (lint) run: ct lint --config ct.yaml --target-branch ${{ env.ct-branch }} --check-version-increment=true - if: ${{ env.changed == 'true' && env.ct-branch != 'dev' && env.ct-branch != 'bugfix' }} + if: ${{ env.changed == 'true' && env.ct-branch != 'dev' && env.ct-branch != 'bugfix' }} # run all checks but version increment always when something changed - name: Run chart-testing (lint) diff --git a/components/package.json b/components/package.json index 5e46baad2d7..5528766c57a 100644 --- a/components/package.json +++ b/components/package.json @@ -1,6 +1,6 @@ { "name": "defectdojo", - "version": "2.32.0", + "version": "2.33.0-dev", "license" : "BSD-3-Clause", "private": true, "dependencies": { diff --git a/components/yarn.lock b/components/yarn.lock index ffe72a3aaf0..d3d65c363f5 100644 --- a/components/yarn.lock +++ b/components/yarn.lock @@ -538,10 +538,6 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== -flot-axis@markrcote/flot-axislabels#*: - version "0.0.0" - resolved "https://codeload.github.com/markrcote/flot-axislabels/tar.gz/a181e09d04d120d05e5bc2baaa8738b5b3670428" - flot@flot/flot#~0.8.3: version "0.8.3" resolved "https://codeload.github.com/flot/flot/tar.gz/453b017cc5acfd75e252b93e8635f57f4196d45d" diff --git a/docs/content/en/getting_started/upgrading/2.33.md b/docs/content/en/getting_started/upgrading/2.33.md new file mode 100644 index 00000000000..19e9449cba4 --- /dev/null +++ b/docs/content/en/getting_started/upgrading/2.33.md @@ -0,0 +1,7 @@ +--- +title: 'Upgrading to DefectDojo Version 2.33.x' +toc_hide: true +weight: -20240304 +description: No special instructions. +--- +There are no special instructions for upgrading to 2.33.x. Check the [Release Notes](https://github.com/DefectDojo/django-DefectDojo/releases/tag/2.33.0) for the contents of the release. diff --git a/dojo/__init__.py b/dojo/__init__.py index 1ee28916860..61db2f0d7a1 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.32.0' +__version__ = '2.33.0-dev' __url__ = 'https://github.com/DefectDojo/django-DefectDojo' __docs__ = 'https://documentation.defectdojo.com' diff --git a/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py b/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py new file mode 100644 index 00000000000..88b5f922a03 --- /dev/null +++ b/dojo/db_migrations/0204_jira_project_epic_issue_type_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.1.13 on 2024-03-01 20:39 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('dojo', '0203_alter_finding_options_finding_epss_percentile_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='jira_project', + name='epic_issue_type_name', + field=models.CharField(default='Epic', blank=True, help_text='The name of the of structure that represents an Epic', max_length=64), + ), + ] diff --git a/dojo/forms.py b/dojo/forms.py index 10ea312ea91..4047ac7aeb3 100755 --- a/dojo/forms.py +++ b/dojo/forms.py @@ -2709,7 +2709,7 @@ class JIRAProjectForm(forms.ModelForm): class Meta: model = JIRA_Project exclude = ['product', 'engagement'] - fields = ['inherit_from_product', 'jira_instance', 'project_key', 'issue_template_dir', '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', '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 @@ -2742,6 +2742,7 @@ def __init__(self, *args, **kwargs): self.fields['jira_instance'].disabled = False self.fields['project_key'].disabled = False self.fields['issue_template_dir'].disabled = False + self.fields['epic_issue_type_name'].disabled = False self.fields['component'].disabled = False self.fields['custom_fields'].disabled = False self.fields['default_assignee'].disabled = False @@ -2765,6 +2766,7 @@ def __init__(self, *args, **kwargs): self.initial['jira_instance'] = jira_project_product.jira_instance.id if jira_project_product.jira_instance else None self.initial['project_key'] = jira_project_product.project_key self.initial['issue_template_dir'] = jira_project_product.issue_template_dir + self.initial['epic_issue_type_name'] = jira_project_product.epic_issue_type_name self.initial['component'] = jira_project_product.component self.initial['custom_fields'] = jira_project_product.custom_fields self.initial['default_assignee'] = jira_project_product.default_assignee @@ -2779,6 +2781,7 @@ def __init__(self, *args, **kwargs): self.fields['jira_instance'].disabled = True self.fields['project_key'].disabled = True self.fields['issue_template_dir'].disabled = True + self.fields['epic_issue_type_name'].disabled = True self.fields['component'].disabled = True self.fields['custom_fields'].disabled = True self.fields['default_assignee'].disabled = True @@ -2798,6 +2801,7 @@ def __init__(self, *args, **kwargs): if self.instance.id: self.fields['jira_instance'].required = True self.fields['project_key'].required = True + self.fields['epic_issue_type_name'].required = True def clean(self): logger.debug('validating jira project form') @@ -2807,17 +2811,18 @@ def clean(self): if not self.cleaned_data.get('inherit_from_product', False): jira_instance = self.cleaned_data.get('jira_instance') project_key = self.cleaned_data.get('project_key') + epic_issue_type_name = self.cleaned_data.get('epic_issue_type_name') - if project_key and jira_instance: + if project_key and jira_instance and epic_issue_type_name: return cleaned_data - if not project_key and not jira_instance: + if not project_key and not jira_instance and not epic_issue_type_name: return cleaned_data if self.target == 'engagement': - raise ValidationError('JIRA Project needs a JIRA Instance and JIRA Project Key, or choose to inherit settings from product') + raise ValidationError('JIRA Project needs a JIRA Instance, JIRA Project Key, and Epic issue type name, or choose to inherit settings from product') else: - raise ValidationError('JIRA Project needs a JIRA Instance and JIRA Project Key, leave empty to have no JIRA integration setup') + raise ValidationError('JIRA Project needs a JIRA Instance, JIRA Project Key, and Epic issue type name, leave empty to have no JIRA integration setup') class GITHUBFindingForm(forms.Form): diff --git a/dojo/jira_link/helper.py b/dojo/jira_link/helper.py index 5318aa0e3ed..4f7360fc465 100644 --- a/dojo/jira_link/helper.py +++ b/dojo/jira_link/helper.py @@ -1223,7 +1223,7 @@ def add_epic(engagement, **kwargs): 'summary': epic_name, 'description': epic_name, 'issuetype': { - 'name': 'Epic' + 'name': getattr(jira_project, "epic_issue_type_name", "Epic"), }, get_epic_name_field_name(jira_instance): epic_name, } diff --git a/dojo/models.py b/dojo/models.py index 98922853f47..36918c88dec 100755 --- a/dojo/models.py +++ b/dojo/models.py @@ -3890,6 +3890,7 @@ class JIRA_Project(models.Model): help_text=_("Automatically maintain parity with JIRA. Always create and update JIRA tickets for findings in this Product.")) enable_engagement_epic_mapping = models.BooleanField(default=False, blank=True) + epic_issue_type_name = models.CharField(max_length=64, blank=True, default="Epic", help_text=_("The name of the of structure that represents an Epic")) 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?")) diff --git a/dojo/reports/urls.py b/dojo/reports/urls.py index e4489708fc1..31503b6c512 100644 --- a/dojo/reports/urls.py +++ b/dojo/reports/urls.py @@ -31,9 +31,9 @@ re_path(r'^reports/custom$', views.custom_report, name='custom_report'), re_path(r'^reports/quick$', - views.quick_report, name='quick_report'), + views.QuickReportView.as_view(), name='quick_report'), re_path(r'^reports/csv_export$', - views.csv_export, name='csv_export'), + views.CSVExportView.as_view(), name='csv_export'), re_path(r'^reports/excel_export$', - views.excel_export, name='excel_export'), + views.ExcelExportView.as_view(), name='excel_export'), ] diff --git a/dojo/reports/views.py b/dojo/reports/views.py index b40efb85ffc..3380d70e954 100644 --- a/dojo/reports/views.py +++ b/dojo/reports/views.py @@ -14,6 +14,7 @@ from django.shortcuts import render, get_object_or_404 from django.utils import timezone from django.core.exceptions import PermissionDenied +from django.views import View from dojo.filters import ReportFindingFilter, EndpointReportFilter, \ EndpointFilter @@ -669,30 +670,6 @@ def prefetch_related_endpoints_for_report(endpoints): ) -def generate_quick_report(request, findings, obj=None): - product = engagement = test = None - - if obj: - if type(obj).__name__ == "Product": - product = obj - elif type(obj).__name__ == "Engagement": - engagement = obj - elif type(obj).__name__ == "Test": - test = obj - - return render(request, 'dojo/finding_pdf_report.html', { - 'report_name': 'Finding Report', - 'product': product, - 'engagement': engagement, - 'test': test, - 'findings': findings, - 'user': request.user, - 'team_name': settings.TEAM_NAME, - 'title': 'Finding Report', - 'user_id': request.user.id, - }) - - def get_list_index(list, index): try: element = list[index] @@ -787,9 +764,41 @@ def get_findings(request): return findings, obj -def quick_report(request): - findings, obj = get_findings(request) - return generate_quick_report(request, findings, obj) +class QuickReportView(View): + def add_findings_data(self): + return self.findings + + def get_template(self): + return 'dojo/finding_pdf_report.html' + + def get(self, request): + findings, obj = get_findings(request) + self.findings = findings + findings = self.add_findings_data() + return self.generate_quick_report(request, findings, obj) + + def generate_quick_report(self, request, findings, obj=None): + product = engagement = test = None + + if obj: + if type(obj).__name__ == "Product": + product = obj + elif type(obj).__name__ == "Engagement": + engagement = obj + elif type(obj).__name__ == "Test": + test = obj + + return render(request, self.get_template(), { + 'report_name': 'Finding Report', + 'product': product, + 'engagement': engagement, + 'test': test, + 'findings': findings, + 'user': request.user, + 'team_name': settings.TEAM_NAME, + 'title': 'Finding Report', + 'user_id': request.user.id, + }) def get_excludes(): @@ -809,219 +818,286 @@ def get_attributes(): return ["sla_age", "sla_deadline", "sla_days_remaining"] -def csv_export(request): - findings, obj = get_findings(request) - response = HttpResponse(content_type='text/csv') - response['Content-Disposition'] = 'attachment; filename=findings.csv' - writer = csv.writer(response) - allowed_attributes = get_attributes() - excludes_list = get_excludes() - allowed_foreign_keys = get_attributes() - first_row = True - - for finding in findings: - if first_row: - fields = [] - for key in dir(finding): - try: - if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): - if callable(getattr(finding, key)) and key not in allowed_attributes: - continue +class CSVExportView(View): + def add_findings_data(self): + return self.findings + + def add_extra_headers(self): + pass + + def add_extra_values(self): + pass + + def get(self, request): + findings, obj = get_findings(request) + self.findings = findings + findings = self.add_findings_data() + response = HttpResponse(content_type='text/csv') + response['Content-Disposition'] = 'attachment; filename=findings.csv' + writer = csv.writer(response) + allowed_attributes = get_attributes() + excludes_list = get_excludes() + allowed_foreign_keys = get_attributes() + first_row = True + + for finding in findings: + self.finding = finding + if first_row: + fields = [] + self.fields = fields + for key in dir(finding): + try: + if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): + if callable(getattr(finding, key)) and key not in allowed_attributes: + continue + fields.append(key) + except Exception as exc: + logger.error('Error in attribute: ' + str(exc)) fields.append(key) - except Exception as exc: - logger.error('Error in attribute: ' + str(exc)) - fields.append(key) - continue - fields.append('test') - fields.append('found_by') - fields.append('engagement_id') - fields.append('engagement') - fields.append('product_id') - fields.append('product') - fields.append('endpoints') - fields.append('vulnerability_ids') - - writer.writerow(fields) - - first_row = False - if not first_row: - fields = [] - for key in dir(finding): - try: - if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): - if not callable(getattr(finding, key)): - value = finding.__dict__.get(key) - if (key in allowed_foreign_keys or key in allowed_attributes) and getattr(finding, key): - if callable(getattr(finding, key)): - func = getattr(finding, key) - result = func() - value = result - else: - value = str(getattr(finding, key)) - if value and isinstance(value, str): - value = value.replace('\n', ' NEWLINE ').replace('\r', '') - fields.append(value) - except Exception as exc: - logger.error('Error in attribute: ' + str(exc)) - fields.append("Value not supported") - continue - fields.append(finding.test.title) - fields.append(finding.test.test_type.name) - fields.append(finding.test.engagement.id) - fields.append(finding.test.engagement.name) - fields.append(finding.test.engagement.product.id) - fields.append(finding.test.engagement.product.name) - - endpoint_value = '' - num_endpoints = 0 - for endpoint in finding.endpoints.all(): - num_endpoints += 1 - if num_endpoints > 5: - endpoint_value += '...' - break - endpoint_value += f'{str(endpoint)}; ' - if endpoint_value.endswith('; '): - endpoint_value = endpoint_value[:-2] - fields.append(endpoint_value) - - vulnerability_ids_value = '' - num_vulnerability_ids = 0 - for vulnerability_id in finding.vulnerability_ids: - num_vulnerability_ids += 1 - if num_vulnerability_ids > 5: - vulnerability_ids_value += '...' - break - vulnerability_ids_value += f'{str(vulnerability_id)}; ' - if finding.cve and vulnerability_ids_value.find(finding.cve) < 0: - vulnerability_ids_value += finding.cve - if vulnerability_ids_value.endswith('; '): - vulnerability_ids_value = vulnerability_ids_value[:-2] - fields.append(vulnerability_ids_value) - - writer.writerow(fields) - - return response - - -def excel_export(request): - findings, obj = get_findings(request) - workbook = Workbook() - workbook.iso_dates = True - worksheet = workbook.active - worksheet.title = 'Findings' - font_bold = Font(bold=True) - allowed_attributes = get_attributes() - excludes_list = get_excludes() - allowed_foreign_keys = get_attributes() - - row_num = 1 - for finding in findings: - if row_num == 1: - col_num = 1 - for key in dir(finding): - try: - if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): - if callable(getattr(finding, key)) and key not in allowed_attributes: - continue + continue + fields.append('test') + fields.append('found_by') + fields.append('engagement_id') + fields.append('engagement') + fields.append('product_id') + fields.append('product') + fields.append('endpoints') + fields.append('vulnerability_ids') + fields.append('tags') + self.fields = fields + self.add_extra_headers() + + writer.writerow(fields) + + first_row = False + if not first_row: + fields = [] + for key in dir(finding): + try: + if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): + if not callable(getattr(finding, key)): + value = finding.__dict__.get(key) + if (key in allowed_foreign_keys or key in allowed_attributes) and getattr(finding, key): + if callable(getattr(finding, key)): + func = getattr(finding, key) + result = func() + value = result + else: + value = str(getattr(finding, key)) + if value and isinstance(value, str): + value = value.replace('\n', ' NEWLINE ').replace('\r', '') + fields.append(value) + except Exception as exc: + logger.error('Error in attribute: ' + str(exc)) + fields.append("Value not supported") + continue + fields.append(finding.test.title) + fields.append(finding.test.test_type.name) + fields.append(finding.test.engagement.id) + fields.append(finding.test.engagement.name) + fields.append(finding.test.engagement.product.id) + fields.append(finding.test.engagement.product.name) + + endpoint_value = '' + num_endpoints = 0 + for endpoint in finding.endpoints.all(): + num_endpoints += 1 + if num_endpoints > 5: + endpoint_value += '...' + break + endpoint_value += f'{str(endpoint)}; ' + if endpoint_value.endswith('; '): + endpoint_value = endpoint_value[:-2] + fields.append(endpoint_value) + + vulnerability_ids_value = '' + num_vulnerability_ids = 0 + for vulnerability_id in finding.vulnerability_ids: + num_vulnerability_ids += 1 + if num_vulnerability_ids > 5: + vulnerability_ids_value += '...' + break + vulnerability_ids_value += f'{str(vulnerability_id)}; ' + if finding.cve and vulnerability_ids_value.find(finding.cve) < 0: + vulnerability_ids_value += finding.cve + if vulnerability_ids_value.endswith('; '): + vulnerability_ids_value = vulnerability_ids_value[:-2] + fields.append(vulnerability_ids_value) + # Tags + tags_value = '' + num_tags = 0 + for tag in finding.tags.all(): + num_tags += 1 + if num_tags > 5: + tags_value += '...' + break + tags_value += f'{str(tag)}; ' + if tags_value.endswith('; '): + tags_value = tags_value[:-2] + fields.append(tags_value) + + self.fields = fields + self.finding = finding + self.add_extra_values() + + writer.writerow(fields) + + return response + + +class ExcelExportView(View): + + def add_findings_data(self): + return self.findings + + def add_extra_headers(self): + pass + + def add_extra_values(self): + pass + + def get(self, request): + findings, obj = get_findings(request) + self.findings = findings + findings = self.add_findings_data() + workbook = Workbook() + workbook.iso_dates = True + worksheet = workbook.active + worksheet.title = 'Findings' + self.worksheet = worksheet + font_bold = Font(bold=True) + self.font_bold = font_bold + allowed_attributes = get_attributes() + excludes_list = get_excludes() + allowed_foreign_keys = get_attributes() + + row_num = 1 + for finding in findings: + if row_num == 1: + col_num = 1 + for key in dir(finding): + try: + if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): + if callable(getattr(finding, key)) and key not in allowed_attributes: + continue + cell = worksheet.cell(row=row_num, column=col_num, value=key) + cell.font = font_bold + col_num += 1 + except Exception as exc: + logger.error('Error in attribute: ' + str(exc)) cell = worksheet.cell(row=row_num, column=col_num, value=key) - cell.font = font_bold - col_num += 1 - except Exception as exc: - logger.error('Error in attribute: ' + str(exc)) - cell = worksheet.cell(row=row_num, column=col_num, value=key) - continue - cell = worksheet.cell(row=row_num, column=col_num, value='found_by') - cell.font = font_bold - col_num += 1 - worksheet.cell(row=row_num, column=col_num, value='engagement_id') - cell = cell.font = font_bold - col_num += 1 - cell = worksheet.cell(row=row_num, column=col_num, value='engagement') - cell.font = font_bold - col_num += 1 - cell = worksheet.cell(row=row_num, column=col_num, value='product_id') - cell.font = font_bold - col_num += 1 - cell = worksheet.cell(row=row_num, column=col_num, value='product') - cell.font = font_bold - col_num += 1 - cell = worksheet.cell(row=row_num, column=col_num, value='endpoints') - cell.font = font_bold - col_num += 1 - cell = worksheet.cell(row=row_num, column=col_num, value='vulnerability_ids') - cell.font = font_bold - - row_num = 2 - if row_num > 1: - col_num = 1 - for key in dir(finding): - try: - if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): - if not callable(getattr(finding, key)): - value = finding.__dict__.get(key) - if (key in allowed_foreign_keys or key in allowed_attributes) and getattr(finding, key): - if callable(getattr(finding, key)): - func = getattr(finding, key) - result = func() - value = result - else: - value = str(getattr(finding, key)) - if value and isinstance(value, datetime): - value = value.replace(tzinfo=None) - worksheet.cell(row=row_num, column=col_num, value=value) - col_num += 1 - except Exception as exc: - logger.error('Error in attribute: ' + str(exc)) - worksheet.cell(row=row_num, column=col_num, value="Value not supported") - continue - worksheet.cell(row=row_num, column=col_num, value=finding.test.test_type.name) - col_num += 1 - worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.id) - col_num += 1 - worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.name) - col_num += 1 - worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.product.id) - col_num += 1 - worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.product.name) - col_num += 1 - - endpoint_value = '' - num_endpoints = 0 - for endpoint in finding.endpoints.all(): - num_endpoints += 1 - if num_endpoints > 5: - endpoint_value += '...' - break - endpoint_value += f'{str(endpoint)}; \n' - if endpoint_value.endswith('; \n'): - endpoint_value = endpoint_value[:-3] - worksheet.cell(row=row_num, column=col_num, value=endpoint_value) - col_num += 1 - - vulnerability_ids_value = '' - num_vulnerability_ids = 0 - for vulnerability_id in finding.vulnerability_ids: - num_vulnerability_ids += 1 - if num_vulnerability_ids > 5: - vulnerability_ids_value += '...' - break - vulnerability_ids_value += f'{str(vulnerability_id)}; \n' - if finding.cve and vulnerability_ids_value.find(finding.cve) < 0: - vulnerability_ids_value += finding.cve - if vulnerability_ids_value.endswith('; \n'): - vulnerability_ids_value = vulnerability_ids_value[:-3] - worksheet.cell(row=row_num, column=col_num, value=vulnerability_ids_value) - - row_num += 1 - - with NamedTemporaryFile() as tmp: - workbook.save(tmp.name) - tmp.seek(0) - stream = tmp.read() - - response = HttpResponse( - content=stream, - content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' - ) - response['Content-Disposition'] = 'attachment; filename=findings.xlsx' - return response + continue + cell = worksheet.cell(row=row_num, column=col_num, value='found_by') + cell.font = font_bold + col_num += 1 + worksheet.cell(row=row_num, column=col_num, value='engagement_id') + cell = cell.font = font_bold + col_num += 1 + cell = worksheet.cell(row=row_num, column=col_num, value='engagement') + cell.font = font_bold + col_num += 1 + cell = worksheet.cell(row=row_num, column=col_num, value='product_id') + cell.font = font_bold + col_num += 1 + cell = worksheet.cell(row=row_num, column=col_num, value='product') + cell.font = font_bold + col_num += 1 + cell = worksheet.cell(row=row_num, column=col_num, value='endpoints') + cell.font = font_bold + col_num += 1 + cell = worksheet.cell(row=row_num, column=col_num, value='vulnerability_ids') + cell.font = font_bold + col_num += 1 + cell = worksheet.cell(row=row_num, column=col_num, value='tags') + cell.font = font_bold + col_num += 1 + self.row_num = row_num + self.col_num = col_num + self.add_extra_headers() + + row_num = 2 + if row_num > 1: + col_num = 1 + for key in dir(finding): + try: + if key not in excludes_list and (not callable(getattr(finding, key)) or key in allowed_attributes) and not key.startswith('_'): + if not callable(getattr(finding, key)): + value = finding.__dict__.get(key) + if (key in allowed_foreign_keys or key in allowed_attributes) and getattr(finding, key): + if callable(getattr(finding, key)): + func = getattr(finding, key) + result = func() + value = result + else: + value = str(getattr(finding, key)) + if value and isinstance(value, datetime): + value = value.replace(tzinfo=None) + worksheet.cell(row=row_num, column=col_num, value=value) + col_num += 1 + except Exception as exc: + logger.error('Error in attribute: ' + str(exc)) + worksheet.cell(row=row_num, column=col_num, value="Value not supported") + continue + worksheet.cell(row=row_num, column=col_num, value=finding.test.test_type.name) + col_num += 1 + worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.id) + col_num += 1 + worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.name) + col_num += 1 + worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.product.id) + col_num += 1 + worksheet.cell(row=row_num, column=col_num, value=finding.test.engagement.product.name) + col_num += 1 + + endpoint_value = '' + num_endpoints = 0 + for endpoint in finding.endpoints.all(): + num_endpoints += 1 + if num_endpoints > 5: + endpoint_value += '...' + break + endpoint_value += f'{str(endpoint)}; \n' + if endpoint_value.endswith('; \n'): + endpoint_value = endpoint_value[:-3] + worksheet.cell(row=row_num, column=col_num, value=endpoint_value) + col_num += 1 + + vulnerability_ids_value = '' + num_vulnerability_ids = 0 + for vulnerability_id in finding.vulnerability_ids: + num_vulnerability_ids += 1 + if num_vulnerability_ids > 5: + vulnerability_ids_value += '...' + break + vulnerability_ids_value += f'{str(vulnerability_id)}; \n' + if finding.cve and vulnerability_ids_value.find(finding.cve) < 0: + vulnerability_ids_value += finding.cve + if vulnerability_ids_value.endswith('; \n'): + vulnerability_ids_value = vulnerability_ids_value[:-3] + worksheet.cell(row=row_num, column=col_num, value=vulnerability_ids_value) + col_num += 1 + # tags + tags_value = '' + for tag in finding.tags.all(): + tags_value += f'{str(tag)}; \n' + if tags_value.endswith('; \n'): + tags_value = tags_value[:-3] + worksheet.cell(row=row_num, column=col_num, value=tags_value) + col_num += 1 + self.col_num = col_num + self.row_num = row_num + self.finding = finding + self.add_extra_values() + row_num += 1 + + with NamedTemporaryFile() as tmp: + workbook.save(tmp.name) + tmp.seek(0) + stream = tmp.read() + + response = HttpResponse( + content=stream, + content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ) + response['Content-Disposition'] = 'attachment; filename=findings.xlsx' + return response diff --git a/dojo/templates/dojo/finding_pdf_report.html b/dojo/templates/dojo/finding_pdf_report.html index 8a229b072ac..2b809deb71f 100644 --- a/dojo/templates/dojo/finding_pdf_report.html +++ b/dojo/templates/dojo/finding_pdf_report.html @@ -87,6 +87,7 @@
| Severity | EPSS Score / Percentile | Status | @@ -103,8 +104,10 @@CWE | {% endif %} + {% endblock finding_header %}
|---|---|---|---|
|
{% if finding.severity %}
@@ -144,7 +147,7 @@ |
{% endif %}
-
+ {% endblock finding_data %}