Skip to content

Commit 5c47b14

Browse files
committed
add unit test
1 parent 1d79125 commit 5c47b14

1 file changed

Lines changed: 223 additions & 0 deletions

File tree

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
from django.template import engines
2+
from django.utils.timezone import now
3+
4+
from dojo.models import (
5+
Engagement,
6+
Finding,
7+
Product,
8+
Product_Type,
9+
Test,
10+
Test_Type,
11+
User,
12+
)
13+
from unittests.dojo_test_case import DojoTestCase
14+
15+
16+
class TestPdfReportTextWrapping(DojoTestCase):
17+
18+
"""
19+
Tests that PDF report templates render long and pre-wrapped content
20+
within margins instead of overflowing.
21+
"""
22+
23+
fixtures = ["dojo_testdata.json"]
24+
25+
LONG_URL = "https://app.example.com/assets/vendor-" + "a1b2c3d4" * 8 + ".js.map"
26+
27+
# Content with an embedded <pre> tag, simulating imports (e.g. BugCrowd CSV)
28+
# that store HTML-wrapped text in finding fields.
29+
DESCRIPTION_WITH_PRE = (
30+
"<pre>\n"
31+
"An internal debug configuration file (debug-config-e7f3a901.json) is publicly "
32+
"accessible at the URL: " + LONG_URL + ". "
33+
"Debug configuration files can reveal internal service addresses, feature flags, "
34+
"and environment variables. Exposing such files can leak sensitive information "
35+
"about the application infrastructure, aiding attackers in lateral movement and "
36+
"facilitating exploitation of internal services.\n"
37+
"</pre>"
38+
)
39+
40+
MITIGATION_WITH_PRE = (
41+
'<pre data-language="plain">\n'
42+
"Remove Debug Files From Public Directories: Ensure .json debug configuration "
43+
"files are not deployed to publicly accessible paths on the web server.\n"
44+
"Restrict Access: If debug configurations are required in staging environments, "
45+
"restrict access to authenticated admin users only via IP whitelisting.\n"
46+
"Environment-Specific Builds: Use separate build profiles for development and "
47+
"production to ensure debug artifacts are excluded from release bundles.\n"
48+
"Audit Build Artifacts: Regularly scan deployment artifacts for unintended "
49+
"inclusion of debug or configuration files.\n"
50+
"</pre>"
51+
)
52+
53+
IMPACT_WITH_PRE = (
54+
'<pre data-language="plain">\n'
55+
"Information Disclosure: Attackers can discover internal microservice endpoints, "
56+
"feature flag states, and environment-specific configuration values.\n"
57+
"Lateral Movement: Revealed internal addresses may allow attackers to probe "
58+
"backend services that are not intended to be publicly reachable.\n"
59+
"Credential Exposure: If the debug configuration includes API keys or tokens "
60+
"left by developers, this could lead to unauthorized access.\n"
61+
"</pre>"
62+
)
63+
64+
STEPS_WITH_PRE = (
65+
'<pre data-language="plain">\n'
66+
"Open a web browser.\n"
67+
"Navigate to the URL: " + LONG_URL + ".\n"
68+
"Observe that the configuration file is accessible and can be downloaded.\n"
69+
"Review the file contents for internal service addresses and environment variables.\n"
70+
"</pre>"
71+
)
72+
73+
# Plain markdown content (no embedded <pre> tags) with a very long unbroken token
74+
DESCRIPTION_LONG_TOKEN = (
75+
"A session token was observed in the query string: "
76+
"token=" + "x" * 300 + " "
77+
"which exceeds normal length and may cause rendering issues in reports."
78+
)
79+
80+
def setUp(self):
81+
super().setUp()
82+
self.user = User.objects.get(username="admin")
83+
self.product_type = Product_Type.objects.create(name="Report Test PT")
84+
self.product = Product.objects.create(
85+
name="Report Test Product",
86+
description="Product for report tests",
87+
prod_type=self.product_type,
88+
)
89+
self.engagement = Engagement.objects.create(
90+
name="Report Test Engagement",
91+
product=self.product,
92+
target_start=now(),
93+
target_end=now(),
94+
)
95+
self.test_type = Test_Type.objects.create(name="Report Test Scan")
96+
self.test_obj = Test.objects.create(
97+
engagement=self.engagement,
98+
test_type=self.test_type,
99+
title="Report Rendering Test",
100+
target_start=now(),
101+
target_end=now(),
102+
)
103+
self.django_engine = engines["django"]
104+
105+
def _create_finding(self, **kwargs):
106+
defaults = {
107+
"title": "Debug Configuration File Exposed",
108+
"test": self.test_obj,
109+
"severity": "Medium",
110+
"description": self.DESCRIPTION_WITH_PRE,
111+
"mitigation": self.MITIGATION_WITH_PRE,
112+
"impact": self.IMPACT_WITH_PRE,
113+
"steps_to_reproduce": self.STEPS_WITH_PRE,
114+
"active": True,
115+
"verified": True,
116+
"reporter": self.user,
117+
"numerical_severity": "S2",
118+
"date": now().date(),
119+
}
120+
defaults.update(kwargs)
121+
return Finding.objects.create(**defaults)
122+
123+
def _render_finding_report(self, findings):
124+
"""Render finding_pdf_report.html with the given findings and return HTML."""
125+
template = self.django_engine.get_template("dojo/finding_pdf_report.html")
126+
context = {
127+
"report_name": "Finding Report",
128+
"findings": findings,
129+
"include_finding_notes": 0,
130+
"include_finding_images": 0,
131+
"include_executive_summary": 0,
132+
"include_table_of_contents": 0,
133+
"include_disclaimer": 0,
134+
"disclaimer": "",
135+
"user": self.user,
136+
"team_name": "Test Team",
137+
"title": "Finding Report",
138+
"host": "http://localhost:8080",
139+
"user_id": self.user.id,
140+
}
141+
return template.render(context)
142+
143+
def test_no_nested_pre_tags_in_report(self):
144+
"""
145+
Markdown-rendered fields should not produce nested <pre><pre> elements.
146+
147+
When imported data already contains <pre> tags (common with BugCrowd CSV
148+
imports), the template wrapper must not add an additional <pre> layer.
149+
The outer wrapper should be a <div class="report-field">.
150+
"""
151+
finding = self._create_finding()
152+
html = self._render_finding_report(Finding.objects.filter(pk=finding.pk))
153+
154+
# The template should wrap markdown-rendered fields in div.report-field,
155+
# not in <pre> tags. We should not see <pre><pre> nesting.
156+
self.assertNotIn("<pre><pre>", html)
157+
self.assertNotIn("<pre><pre ", html)
158+
159+
# The report-field wrapper should be present
160+
self.assertIn('class="report-field"', html)
161+
162+
def test_report_field_contains_rendered_content(self):
163+
"""Verify that finding content is actually rendered inside report-field divs."""
164+
finding = self._create_finding()
165+
html = self._render_finding_report(Finding.objects.filter(pk=finding.pk))
166+
167+
# The description text should appear in the rendered output
168+
self.assertIn("debug-config-e7f3a901.json", html)
169+
self.assertIn("Debug configuration files can reveal", html)
170+
171+
# Mitigation content should appear
172+
self.assertIn("Remove Debug Files From Public Directories", html)
173+
174+
def test_long_unbroken_string_in_report_field(self):
175+
"""
176+
Fields with very long unbroken strings should render inside report-field
177+
divs that have overflow-wrap: break-word styling.
178+
"""
179+
finding = self._create_finding(description=self.DESCRIPTION_LONG_TOKEN)
180+
html = self._render_finding_report(Finding.objects.filter(pk=finding.pk))
181+
182+
# The long token should be present in the output
183+
self.assertIn("x" * 300, html)
184+
185+
# It should be inside a report-field div, not a bare <pre>
186+
# Find the section containing our long token
187+
idx = html.index("x" * 300)
188+
# Walk backwards to find the nearest opening tag
189+
preceding = html[max(0, idx - 500):idx]
190+
self.assertIn("report-field", preceding)
191+
192+
def test_report_base_css_has_overflow_wrap(self):
193+
"""The report base template must include overflow-wrap for text wrapping."""
194+
template = self.django_engine.get_template("report_base.html")
195+
# Render with minimal context to get the CSS
196+
html = template.render({"report_name": "Test"})
197+
198+
self.assertIn("overflow-wrap: break-word", html)
199+
200+
def test_report_base_css_styles_nested_pre(self):
201+
"""
202+
The report base CSS must style .report-field pre to prevent
203+
nested <pre> elements from breaking out of margins.
204+
"""
205+
template = self.django_engine.get_template("report_base.html")
206+
html = template.render({"report_name": "Test"})
207+
208+
self.assertIn(".report-field pre", html)
209+
self.assertIn("overflow-wrap: break-word", html)
210+
211+
def test_raw_request_pre_tags_preserved(self):
212+
"""
213+
Raw request/response <pre> tags should remain unchanged.
214+
215+
Only markdown-rendered fields should use div.report-field wrappers.
216+
The raw_request class pre tags are for literal request/response data
217+
and should stay as <pre>.
218+
"""
219+
template = self.django_engine.get_template("dojo/finding_pdf_report.html")
220+
source = template.template.source
221+
self.assertIn('class="raw_request"', source)
222+
# raw_request should still be inside <pre> tags
223+
self.assertIn('<pre class="raw_request">', source)

0 commit comments

Comments
 (0)