Skip to content

Commit e177a6d

Browse files
authored
Add (failing) test case for Sonar SQL finding (#608)
Add (failing) test case for Sonar SQL parameterization finding
1 parent adcca27 commit e177a6d

File tree

3 files changed

+71
-1
lines changed

3 files changed

+71
-1
lines changed

src/core_codemods/sonar/results.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,5 @@ def from_json(cls, json_file: str | Path) -> Self:
9191

9292
return result_set
9393
except Exception:
94-
logger.debug("Could not parse sonar json %s", json_file)
94+
logger.exception("Could not parse sonar json %s", json_file)
9595
return cls()

tests/codemods/sonar/test_sonar_sql_parameterization.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
import json
2+
from pathlib import Path
3+
4+
import pytest
25

36
from codemodder.codemods.test import BaseSASTCodemodTest
47
from core_codemods.sonar.sonar_sql_parameterization import SonarSQLParameterization
58

9+
SAMPLE_FILE_PATH = (
10+
Path(__file__).parents[2] / "samples" / "sonar" / "sql_parameterization.json"
11+
)
12+
613

714
class TestSonarSQLParameterization(BaseSASTCodemodTest):
815
codemod = SonarSQLParameterization
@@ -58,3 +65,65 @@ def f():
5865
]
5966
}
6067
self.run_and_assert(tmpdir, input_code, expected, results=json.dumps(issues))
68+
69+
@pytest.mark.xfail(reason="TODO: See issue #607")
70+
def test_more_complicated_example(self, tmpdir):
71+
input_code = """
72+
from django.shortcuts import redirect
73+
from django.http import HttpResponse
74+
75+
import sqlite3
76+
import json
77+
78+
79+
def do_useful_things(request):
80+
if request.method == "POST":
81+
user = request.POST.get("user")
82+
83+
sql = "SELECT user FROM users WHERE user = '%s'"
84+
conn = sqlite3.connect("example")
85+
result = conn.cursor().execute(sql % user)
86+
87+
json_response = json.dumps({"user": result.fetchone()[0]})
88+
return HttpResponse(json_response.encode("utf-8"))
89+
else:
90+
return redirect("/")
91+
""".lstrip(
92+
"\n"
93+
)
94+
expected = """
95+
from django.shortcuts import redirect
96+
from django.http import HttpResponse
97+
98+
import sqlite3
99+
import json
100+
101+
102+
def do_useful_things(request):
103+
if request.method == "POST":
104+
user = request.POST.get("user")
105+
106+
sql = "SELECT user FROM users WHERE user = ?"
107+
conn = sqlite3.connect("example")
108+
result = conn.cursor().execute(sql, (user, ))
109+
110+
json_response = json.dumps({"user": result.fetchone()[0]})
111+
return HttpResponse(json_response.encode("utf-8"))
112+
else:
113+
return redirect("/")
114+
""".lstrip(
115+
"\n"
116+
)
117+
118+
issues = json.loads(SAMPLE_FILE_PATH.read_text())
119+
120+
filename = Path(tmpdir) / "introduction" / "new_view.py"
121+
filename.parent.mkdir(parents=True)
122+
123+
self.run_and_assert(
124+
tmpdir,
125+
input_code,
126+
expected,
127+
files=[filename],
128+
results=json.dumps(issues),
129+
)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"total":2,"p":1,"ps":100,"paging":{"pageIndex":1,"pageSize":100,"total":2},"effortTotal":35,"debtTotal":35,"issues":[{"key":"AY_KW3q9kpLwWuMztk6B","rule":"python:S6552","severity":"MAJOR","component":"drdavella_pygoat-sonar2:introduction/new_view.py","project":"drdavella_pygoat-sonar2","line":13,"hash":"91d1a8baa6977afdf844ab3f2870df56","textRange":{"startLine":13,"endLine":13,"startOffset":0,"endOffset":27},"flows":[],"status":"OPEN","message":"Move this \u0027@receiver\u0027 decorator to the top of the other decorators.","effort":"5min","debt":"5min","tags":[],"creationDate":"2024-05-30T18:34:05+0200","updateDate":"2024-05-30T18:35:24+0200","type":"BUG","organization":"drdavella","pullRequest":"5","cleanCodeAttribute":"LOGICAL","cleanCodeAttributeCategory":"INTENTIONAL","impacts":[{"softwareQuality":"RELIABILITY","severity":"MEDIUM"}]},{"key":"AY_KW3q9kpLwWuMztk6D","rule":"pythonsecurity:S3649","severity":"BLOCKER","component":"drdavella_pygoat-sonar2:introduction/new_view.py","project":"drdavella_pygoat-sonar2","line":20,"hash":"cc503242d422d9ba9c4f02e0c7e243cb","textRange":{"startLine":20,"endLine":20,"startOffset":17,"endOffset":51},"flows":[{"locations":[{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":20,"endLine":20,"startOffset":17,"endOffset":51},"msg":"Sink: this invocation is not safe; a malicious value can be used as argument"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":19,"endLine":19,"startOffset":8,"endOffset":92},"msg":"A malicious value can be assigned to variable ‘query’"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":19,"endLine":19,"startOffset":16,"endOffset":92},"msg":"This concatenation can propagate malicious content to the newly created string"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":19,"endLine":19,"startOffset":54,"endOffset":58},"msg":"The malicious content is concatenated into the string"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":16,"endLine":16,"startOffset":8,"endOffset":39},"msg":"A malicious value can be assigned to variable ‘name’"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":16,"endLine":16,"startOffset":15,"endOffset":39},"msg":"Source: a user can craft an HTTP request with malicious content"}]},{"locations":[{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":20,"endLine":20,"startOffset":17,"endOffset":51},"msg":"Sink: this invocation is not safe; a malicious value can be used as argument"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":19,"endLine":19,"startOffset":8,"endOffset":92},"msg":"A malicious value can be assigned to variable ‘query’"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":19,"endLine":19,"startOffset":16,"endOffset":92},"msg":"This concatenation can propagate malicious content to the newly created string"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":19,"endLine":19,"startOffset":81,"endOffset":86},"msg":"The malicious content is concatenated into the string"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":17,"endLine":17,"startOffset":8,"endOffset":41},"msg":"A malicious value can be assigned to variable ‘phone’"},{"component":"drdavella_pygoat-sonar2:introduction/new_view.py","textRange":{"startLine":17,"endLine":17,"startOffset":16,"endOffset":41},"msg":"Source: a user can craft an HTTP request with malicious content"}]}],"status":"OPEN","message":"Change this code to not construct SQL queries directly from user-controlled data.","effort":"30min","debt":"30min","tags":["cwe","sql"],"creationDate":"2024-05-30T18:34:05+0200","updateDate":"2024-05-30T18:35:24+0200","type":"VULNERABILITY","organization":"drdavella","pullRequest":"5","cleanCodeAttribute":"COMPLETE","cleanCodeAttributeCategory":"INTENTIONAL","impacts":[{"softwareQuality":"SECURITY","severity":"HIGH"}]}],"components":[{"organization":"drdavella","key":"drdavella_pygoat-sonar2:introduction/new_view.py","uuid":"AY_KW3gxkpLwWuMztk5a","enabled":true,"qualifier":"FIL","name":"new_view.py","longName":"introduction/new_view.py","path":"introduction/new_view.py","pullRequest":"5"},{"organization":"drdavella","key":"drdavella_pygoat-sonar2","uuid":"AY_KWyTh-nOrCSyUsFgG","enabled":true,"qualifier":"TRK","name":"pygoat-sonar","longName":"pygoat-sonar","pullRequest":"5"}],"organizations":[{"key":"drdavella","name":"Dan D\u0027Avella"}],"facets":[]}

0 commit comments

Comments
 (0)