Skip to content

Commit adef7bc

Browse files
fix(sonarqube): bypass 10K result cap by fetching issues per-rule
Closes #33 The issues/search API enforces p*ps <= 10000. With PAGE_SIZE=500, page 21 returns HTTP 400. Fix: iterate per-rule instead of passing all ~600 rules in one query. Each single-rule query stays well under 10K. Also fixes off-by-one in page count (ceiling division) and adds HTTP status checking before reading response body.
1 parent b7b159c commit adef7bc

File tree

3 files changed

+126
-8
lines changed

3 files changed

+126
-8
lines changed

pom.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,19 @@
868868
<artifactId>jackson-databind</artifactId>
869869
<version>2.21.2</version>
870870
</dependency>
871+
872+
<dependency>
873+
<groupId>org.junit.jupiter</groupId>
874+
<artifactId>junit-jupiter-api</artifactId>
875+
<version>5.12.2</version>
876+
<scope>test</scope>
877+
</dependency>
878+
<dependency>
879+
<groupId>org.junit.jupiter</groupId>
880+
<artifactId>junit-jupiter-params</artifactId>
881+
<version>5.12.2</version>
882+
<scope>test</scope>
883+
</dependency>
871884
</dependencies>
872885

873886
<build>

src/main/java/org/owasp/benchmark/report/sonarqube/SonarReport.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,19 @@ public class SonarReport {
3535
private static final ObjectMapper objectMapper = new ObjectMapper();
3636

3737
public static void main(String[] args) throws Exception {
38-
String allJavaRules = String.join(",", allJavaRules());
38+
Set<String> allJavaRules = allJavaRules();
3939
List<String> issues = new ArrayList<>();
4040
List<String> hotspots = new ArrayList<>();
4141

42-
forAllPagesAt(
43-
"issues/search?componentKeys="
44-
+ SONAR_PROJECT
45-
+ "&types=VULNERABILITY&&rules="
46-
+ allJavaRules,
47-
(result -> issues.addAll(result.issues)));
42+
for (String rule : allJavaRules) {
43+
forAllPagesAt(
44+
"issues/search?componentKeys="
45+
+ SONAR_PROJECT
46+
+ "&types=VULNERABILITY&rules="
47+
+ rule,
48+
(result -> issues.addAll(result.issues)));
49+
}
50+
4851
forAllPagesAt(
4952
"hotspots/search?projectKey=" + SONAR_PROJECT,
5053
(result -> hotspots.addAll(result.hotspots)));
@@ -91,7 +94,9 @@ private static void forAllPagesAt(String apiPath, Consumer<SonarQubeResult> page
9194
objectMapper.readValue(
9295
apiCall(apiPath + pagingSuffix(page, apiPath)), SonarQubeResult.class);
9396

94-
pages = (result.paging.resultCount / PAGE_SIZE) + 1;
97+
pages =
98+
(result.paging.resultCount / PAGE_SIZE)
99+
+ (result.paging.resultCount % PAGE_SIZE == 0 ? 0 : 1);
95100

96101
pageHandlerCallback.accept(result);
97102

@@ -110,6 +115,11 @@ private static String apiCall(String apiPath) throws IOException {
110115
connection.setDoOutput(true);
111116
connection.setRequestProperty("Authorization", "Basic " + sonarAuth);
112117

118+
int status = connection.getResponseCode();
119+
if (status != 200) {
120+
throw new IOException("SonarQube API returned HTTP " + status + " for " + apiPath);
121+
}
122+
113123
return join("\n", readLines(connection.getInputStream(), defaultCharset()));
114124
}
115125

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* OWASP Benchmark Project
3+
*
4+
* <p>This file is part of the Open Web Application Security Project (OWASP) Benchmark Project For
5+
* details, please see <a
6+
* href="https://owasp.org/www-project-benchmark/">https://owasp.org/www-project-benchmark/</a>.
7+
*
8+
* <p>The OWASP Benchmark is free software: you can redistribute it and/or modify it under the terms
9+
* of the GNU General Public License as published by the Free Software Foundation, version 2.
10+
*
11+
* <p>The OWASP Benchmark is distributed in the hope that it will be useful, but WITHOUT ANY
12+
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
13+
* PURPOSE. See the GNU General Public License for more details.
14+
*/
15+
package org.owasp.benchmark.report.sonarqube;
16+
17+
import static org.junit.jupiter.api.Assertions.assertEquals;
18+
import static org.junit.jupiter.api.Assertions.assertFalse;
19+
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.params.ParameterizedTest;
22+
import org.junit.jupiter.params.provider.CsvSource;
23+
24+
public class SonarReportTest {
25+
26+
private static final int PAGE_SIZE = 500;
27+
28+
/**
29+
* Replicates the fixed paging formula from SonarReport.forAllPagesAt(). The old formula was
30+
* {@code (resultCount / PAGE_SIZE) + 1}, which over-counts by 1 when resultCount is an exact
31+
* multiple of PAGE_SIZE.
32+
*/
33+
private static int calculatePages(int resultCount) {
34+
return (resultCount / PAGE_SIZE) + (resultCount % PAGE_SIZE == 0 ? 0 : 1);
35+
}
36+
37+
@ParameterizedTest(name = "resultCount={0} => pages={1}")
38+
@CsvSource({
39+
"0, 0",
40+
"1, 1",
41+
"499, 1",
42+
"500, 1",
43+
"501, 2",
44+
"999, 2",
45+
"1000, 2",
46+
"1001, 3",
47+
"10000, 20",
48+
"10001, 21"
49+
})
50+
void pageCalculationProducesCorrectCeiling(int resultCount, int expectedPages) {
51+
assertEquals(
52+
expectedPages,
53+
calculatePages(resultCount),
54+
"Paging for " + resultCount + " results at page size " + PAGE_SIZE);
55+
}
56+
57+
@Test
58+
void oldFormulaOvercountsOnExactMultiple() {
59+
int resultCount = 1000;
60+
int oldFormula = (resultCount / PAGE_SIZE) + 1;
61+
int fixedFormula = calculatePages(resultCount);
62+
63+
assertEquals(3, oldFormula, "Old formula produces 3 pages for 1000 results (wrong)");
64+
assertEquals(2, fixedFormula, "Fixed formula produces 2 pages for 1000 results (correct)");
65+
}
66+
67+
@Test
68+
void issueSearchUrlHasNoDoubleAmpersand() {
69+
String sonarProject = "benchmark";
70+
String rule = "java:S1234";
71+
72+
String fixedUrl =
73+
"issues/search?componentKeys="
74+
+ sonarProject
75+
+ "&types=VULNERABILITY&rules="
76+
+ rule;
77+
78+
assertFalse(fixedUrl.contains("&&"), "URL must not contain double ampersand");
79+
}
80+
81+
@Test
82+
void oldIssueSearchUrlHadDoubleAmpersand() {
83+
String sonarProject = "benchmark";
84+
String allJavaRules = "java:S1234,java:S5678";
85+
86+
String oldUrl =
87+
"issues/search?componentKeys="
88+
+ sonarProject
89+
+ "&types=VULNERABILITY&&rules="
90+
+ allJavaRules;
91+
92+
assertEquals(
93+
true, oldUrl.contains("&&"), "Old URL construction had double ampersand (the bug)");
94+
}
95+
}

0 commit comments

Comments
 (0)