From f45173997b8331e6cba3deac78bc2e1043b9281d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Mart=C3=ADn?= Date: Fri, 16 Oct 2026 10:33:07 +0200 Subject: [PATCH] fix(api): wrap long account IDs in PDF report cover table (#13063) --- .../pdf-report-account-id-overflow.fixed.md | 1 + api/src/backend/tasks/jobs/reports/cis.py | 12 +++------ api/src/backend/tasks/jobs/reports/ens.py | 15 ++++------- api/src/backend/tasks/jobs/reports/nis2.py | 14 ++++------ .../backend/tasks/tests/test_reports_cis.py | 22 ++++++++++++++++ .../backend/tasks/tests/test_reports_ens.py | 26 +++++++++++++++++++ .../backend/tasks/tests/test_reports_nis2.py | 26 +++++++++++++++++++ 7 files changed, 89 insertions(+), 27 deletions(-) create mode 100644 api/changelog.d/pdf-report-account-id-overflow.fixed.md diff --git a/api/changelog.d/pdf-report-account-id-overflow.fixed.md b/api/changelog.d/pdf-report-account-id-overflow.fixed.md new file mode 100644 index 0000000000..d97d9bc41d --- /dev/null +++ b/api/changelog.d/pdf-report-account-id-overflow.fixed.md @@ -0,0 +1 @@ +Compliance report PDF cover table wraps every value, so long account IDs such as OCI tenancy OCIDs no longer overflow their cell diff --git a/api/src/backend/tasks/jobs/reports/cis.py b/api/src/backend/tasks/jobs/reports/cis.py index 8a1cfb6eba..420c46252e 100644 --- a/api/src/backend/tasks/jobs/reports/cis.py +++ b/api/src/backend/tasks/jobs/reports/cis.py @@ -172,14 +172,10 @@ class CISReportGenerator(BaseComplianceReportGenerator): # Metadata table via base class helper info_rows = self._build_info_rows(data, language=self.config.language) - metadata_data = [] - for label, value in info_rows: - if label in ("Name:", "Description:") and value: - metadata_data.append( - [label, Paragraph(str(value), self.styles["normal_center"])] - ) - else: - metadata_data.append([label, value]) + metadata_data = [ + [label, Paragraph(escape_html(str(value)), self.styles["normal_center"])] + for label, value in info_rows + ] metadata_table = Table(metadata_data, colWidths=[2 * inch, 4 * inch]) metadata_table.setStyle( diff --git a/api/src/backend/tasks/jobs/reports/ens.py b/api/src/backend/tasks/jobs/reports/ens.py index 44c874bfc1..a940e95416 100644 --- a/api/src/backend/tasks/jobs/reports/ens.py +++ b/api/src/backend/tasks/jobs/reports/ens.py @@ -13,7 +13,7 @@ from .base import ( get_requirement_metadata, ) from .charts import create_horizontal_bar_chart, create_radar_chart -from .components import get_color_for_compliance +from .components import escape_html, get_color_for_compliance from .config import ( COLOR_BG_BLUE, COLOR_BLUE, @@ -99,15 +99,10 @@ class ENSReportGenerator(BaseComplianceReportGenerator): # Compliance info table - use base class helper for consistency info_rows = self._build_info_rows(data, language="es") - # Convert tuples to lists and wrap long text in Paragraphs - info_data = [] - for label, value in info_rows: - if label in ("Nombre:", "Descripción:") and value: - info_data.append( - [label, Paragraph(value, self.styles["normal_center"])] - ) - else: - info_data.append([label, value]) + info_data = [ + [label, Paragraph(escape_html(str(value)), self.styles["normal_center"])] + for label, value in info_rows + ] info_table = Table(info_data, colWidths=[2 * inch, 4 * inch]) info_table.setStyle( diff --git a/api/src/backend/tasks/jobs/reports/nis2.py b/api/src/backend/tasks/jobs/reports/nis2.py index ed936f9571..1775871f42 100644 --- a/api/src/backend/tasks/jobs/reports/nis2.py +++ b/api/src/backend/tasks/jobs/reports/nis2.py @@ -11,6 +11,7 @@ from .base import ( get_requirement_metadata, ) from .charts import create_horizontal_bar_chart, get_chart_color_for_percentage +from .components import escape_html from .config import ( COLOR_BORDER_GRAY, COLOR_DARK_GRAY, @@ -111,15 +112,10 @@ class NIS2ReportGenerator(BaseComplianceReportGenerator): # Compliance metadata table - use base class helper for consistency info_rows = self._build_info_rows(data, language="en") - # Convert tuples to lists and wrap long text in Paragraphs - metadata_data = [] - for label, value in info_rows: - if label in ("Name:", "Description:") and value: - metadata_data.append( - [label, Paragraph(value, self.styles["normal_center"])] - ) - else: - metadata_data.append([label, value]) + metadata_data = [ + [label, Paragraph(escape_html(str(value)), self.styles["normal_center"])] + for label, value in info_rows + ] metadata_table = Table(metadata_data, colWidths=[2 * inch, 4 * inch]) metadata_table.setStyle( diff --git a/api/src/backend/tasks/tests/test_reports_cis.py b/api/src/backend/tasks/tests/test_reports_cis.py index b57780b8a4..530e58133d 100644 --- a/api/src/backend/tasks/tests/test_reports_cis.py +++ b/api/src/backend/tasks/tests/test_reports_cis.py @@ -2,6 +2,7 @@ from unittest.mock import Mock, patch import pytest from api.models import StatusChoices +from reportlab.lib.units import inch from reportlab.platypus import Image, LongTable, Paragraph, Table from tasks.jobs.reports import FRAMEWORK_REGISTRY, ComplianceData, RequirementData from tasks.jobs.reports import cis as cis_report_module @@ -582,3 +583,24 @@ class TestCISCoverPage: paragraphs = [e for e in elements if isinstance(e, Paragraph)] content = " ".join(str(p.text) for p in paragraphs) assert "AWS" in content + + def test_cover_page_wraps_long_account_id( + self, cis_generator, basic_cis_compliance_data + ): + provider = Mock() + provider.provider = "oraclecloud" + provider.uid = "ocid1.tenancy.oc1.." + "a" * 70 + provider.alias = None + basic_cis_compliance_data.provider_obj = provider + elements = cis_generator.create_cover_page(basic_cis_compliance_data) + table = next( + e + for e in elements + if isinstance(e, Table) + and any(row[0] == "Account ID:" for row in e._cellvalues) + ) + values = [row[1] for row in table._cellvalues] + assert all(isinstance(v, Paragraph) for v in values) + account_id = next(v for v in values if v.text == provider.uid) + _, height = account_id.wrap(4 * inch, 1000) + assert height > account_id.style.leading diff --git a/api/src/backend/tasks/tests/test_reports_ens.py b/api/src/backend/tasks/tests/test_reports_ens.py index 220c3e1427..ecb938bd38 100644 --- a/api/src/backend/tasks/tests/test_reports_ens.py +++ b/api/src/backend/tasks/tests/test_reports_ens.py @@ -2,6 +2,7 @@ import io from unittest.mock import Mock, patch import pytest +from reportlab.lib.units import inch from reportlab.platypus import Image, PageBreak, Paragraph, Table from tasks.jobs.reports import FRAMEWORK_REGISTRY, ComplianceData, RequirementData from tasks.jobs.reports import ens as ens_report_module @@ -189,6 +190,31 @@ class TestENSCoverPage: tables = [e for e in elements if isinstance(e, Table)] assert len(tables) >= 1 # At least info table + def test_cover_page_wraps_long_account_id( + self, ens_generator, basic_ens_compliance_data + ): + provider = Mock() + provider.provider = "oraclecloud" + provider.uid = "ocid1.tenancy.oc1.." + "a" * 70 + provider.alias = None + basic_ens_compliance_data.provider_obj = provider + basic_ens_compliance_data.requirements = [] + basic_ens_compliance_data.attributes_by_requirement_id = {} + + elements = ens_generator.create_cover_page(basic_ens_compliance_data) + + table = next( + e + for e in elements + if isinstance(e, Table) + and any(row[0] == "Account ID:" for row in e._cellvalues) + ) + values = [row[1] for row in table._cellvalues] + assert all(isinstance(v, Paragraph) for v in values) + account_id = next(v for v in values if v.text == provider.uid) + _, height = account_id.wrap(4 * inch, 1000) + assert height > account_id.style.leading + def test_cover_page_has_warning_about_manual( self, ens_generator, basic_ens_compliance_data ): diff --git a/api/src/backend/tasks/tests/test_reports_nis2.py b/api/src/backend/tasks/tests/test_reports_nis2.py index ea6e06477d..ce630648f8 100644 --- a/api/src/backend/tasks/tests/test_reports_nis2.py +++ b/api/src/backend/tasks/tests/test_reports_nis2.py @@ -2,6 +2,7 @@ import io from unittest.mock import Mock, patch import pytest +from reportlab.lib.units import inch from reportlab.platypus import Image, PageBreak, Paragraph, Table from tasks.jobs.reports import FRAMEWORK_REGISTRY, ComplianceData, RequirementData from tasks.jobs.reports import nis2 as nis2_report_module @@ -214,6 +215,31 @@ class TestNIS2CoverPage: tables = [e for e in elements if isinstance(e, Table)] assert len(tables) >= 1 + def test_cover_page_wraps_long_account_id( + self, nis2_generator, basic_nis2_compliance_data + ): + provider = Mock() + provider.provider = "oraclecloud" + provider.uid = "ocid1.tenancy.oc1.." + "a" * 70 + provider.alias = None + basic_nis2_compliance_data.provider_obj = provider + basic_nis2_compliance_data.requirements = [] + basic_nis2_compliance_data.attributes_by_requirement_id = {} + + elements = nis2_generator.create_cover_page(basic_nis2_compliance_data) + + table = next( + e + for e in elements + if isinstance(e, Table) + and any(row[0] == "Account ID:" for row in e._cellvalues) + ) + values = [row[1] for row in table._cellvalues] + assert all(isinstance(v, Paragraph) for v in values) + account_id = next(v for v in values if v.text == provider.uid) + _, height = account_id.wrap(4 * inch, 1000) + assert height > account_id.style.leading + # ============================================================================= # Executive Summary Tests