Skip to content

Commit 6d2ba3e

Browse files
committed
feat: report recommendations error but show vulnerabilities
Signed-off-by: Ruben Romero Montes <[email protected]>
1 parent a5885d4 commit 6d2ba3e

File tree

10 files changed

+259
-63
lines changed

10 files changed

+259
-63
lines changed

src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandler.java

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ public abstract class ProviderResponseHandler {
7171

7272
protected abstract String getProviderName(Exchange exchange);
7373

74-
public abstract ProviderResponse responseToIssues(byte[] response, DependencyTree tree)
75-
throws IOException;
74+
public abstract ProviderResponse responseToIssues(Exchange exchange) throws IOException;
7675

7776
public ProviderResponse aggregateSplit(ProviderResponse oldExchange, ProviderResponse newExchange)
7877
throws IOException {
@@ -111,7 +110,7 @@ public ProviderResponse aggregateSplit(ProviderResponse oldExchange, ProviderRes
111110
return exchange;
112111
}
113112

114-
protected ProviderStatus defaultOkStatus(String provider) {
113+
public ProviderStatus defaultOkStatus(String provider) {
115114
return new ProviderStatus()
116115
.name(provider)
117116
.ok(Boolean.TRUE)
@@ -204,7 +203,7 @@ public void processTokenFallBack(Exchange exchange) {
204203
private static String prettifyHttpError(HttpOperationFailedException httpException) {
205204
String text = httpException.getStatusText();
206205
String defaultReason =
207-
httpException.getResponseBody() != null
206+
httpException.getResponseBody() != null && !httpException.getResponseBody().isBlank()
208207
? httpException.getResponseBody()
209208
: httpException.getMessage();
210209
return text
@@ -216,9 +215,8 @@ private static String prettifyHttpError(HttpOperationFailedException httpExcepti
216215
};
217216
}
218217

219-
public ProviderResponse emptyResponse(
220-
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree) {
221-
return new ProviderResponse(Collections.emptyMap(), null);
218+
public ProviderResponse emptyResponse(Exchange exchange) {
219+
return new ProviderResponse(Collections.emptyMap(), defaultOkStatus(getProviderName(exchange)));
222220
}
223221

224222
/**
@@ -295,7 +293,7 @@ public ProviderReport buildReport(
295293
@Body ProviderResponse response,
296294
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
297295
throws IOException {
298-
if (response.status() != null) {
296+
if (response.status() != null && response.pkgItems() == null) {
299297
return new ProviderReport().status(response.status()).sources(Collections.emptyMap());
300298
}
301299
var providerName = getProviderName(exchange);
@@ -306,7 +304,7 @@ public ProviderReport buildReport(
306304
.entrySet()
307305
.forEach(
308306
entry -> reports.put(entry.getKey(), buildReportForSource(entry.getValue(), tree)));
309-
return new ProviderReport().status(defaultOkStatus(providerName)).sources(reports);
307+
return new ProviderReport().status(response.status()).sources(reports);
310308
}
311309

312310
private Source buildReportForSource(Map<String, PackageItem> pkgItemsData, DependencyTree tree) {

src/main/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandler.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@
2525
import java.util.Map;
2626
import java.util.stream.Collectors;
2727

28-
import org.apache.camel.Body;
2928
import org.apache.camel.Exchange;
30-
import org.apache.camel.ExchangeProperty;
3129

3230
import com.fasterxml.jackson.databind.JsonNode;
3331
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -62,12 +60,11 @@ protected String getProviderName(Exchange exchange) {
6260
}
6361

6462
@Override
65-
public ProviderResponse responseToIssues(
66-
@Body byte[] response,
67-
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
68-
throws IOException {
63+
public ProviderResponse responseToIssues(Exchange exchange) throws IOException {
64+
var response = exchange.getIn().getBody(byte[].class);
65+
var tree = exchange.getProperty(Constants.DEPENDENCY_TREE_PROPERTY, DependencyTree.class);
6966
var json = (ObjectNode) mapper.readTree(response);
70-
return new ProviderResponse(getIssues(json, tree), null);
67+
return new ProviderResponse(getIssues(json, tree), defaultOkStatus(getProviderName(exchange)));
7168
}
7269

7370
private Map<String, PackageItem> getIssues(ObjectNode response, DependencyTree tree) {

src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregation.java

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424

2525
import org.apache.camel.AggregationStrategy;
2626
import org.apache.camel.Exchange;
27+
import org.jboss.logging.Logger;
2728

2829
import io.github.guacsec.trustifyda.api.PackageRef;
2930
import io.github.guacsec.trustifyda.api.v5.Issue;
31+
import io.github.guacsec.trustifyda.api.v5.ProviderStatus;
3032
import io.github.guacsec.trustifyda.api.v5.Remediation;
3133
import io.github.guacsec.trustifyda.api.v5.RemediationTrustedContent;
3234
import io.github.guacsec.trustifyda.model.PackageItem;
@@ -36,25 +38,60 @@
3638

3739
public class RecommendationAggregation implements AggregationStrategy {
3840

41+
private static final Logger LOGGER = Logger.getLogger(RecommendationAggregation.class);
42+
3943
@Override
4044
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
4145
if (oldExchange == null) {
4246
return newExchange;
4347
}
48+
var worstStatus = getWorstStatus(oldExchange, newExchange);
49+
4450
var providerResponse = getProviderResponse(oldExchange, newExchange);
45-
var recommendations = getRecommendations(oldExchange, newExchange);
51+
if (providerResponse == null) {
52+
providerResponse = new ProviderResponse(new HashMap<>(), worstStatus);
53+
}
54+
Map<PackageRef, IndexedRecommendation> recommendations = null;
55+
try {
56+
recommendations = getRecommendations(oldExchange, newExchange);
57+
} catch (Exception e) {
58+
LOGGER.warn("Error getting recommendations", e);
59+
}
4660

4761
if (providerResponse.pkgItems() == null) {
48-
providerResponse = new ProviderResponse(new HashMap<>(), providerResponse.status());
62+
providerResponse = new ProviderResponse(new HashMap<>(), worstStatus);
4963
}
5064
if (recommendations != null && !recommendations.isEmpty()) {
5165
setTrustedContent(recommendations, providerResponse);
5266
}
5367

54-
oldExchange.getIn().setBody(providerResponse);
68+
oldExchange.getIn().setBody(new ProviderResponse(providerResponse.pkgItems(), worstStatus));
5569
return oldExchange;
5670
}
5771

72+
private ProviderStatus getWorstStatus(Exchange oldExchange, Exchange newExchange) {
73+
ProviderStatus oldStatus = getStatus(oldExchange);
74+
ProviderStatus newStatus = getStatus(newExchange);
75+
76+
if (oldStatus == null && newStatus == null) {
77+
return null;
78+
}
79+
if (oldStatus == null) {
80+
return newStatus;
81+
}
82+
if (newStatus == null) {
83+
return oldStatus;
84+
}
85+
return Boolean.FALSE.equals(oldStatus.getOk()) ? oldStatus : newStatus;
86+
}
87+
88+
private ProviderStatus getStatus(Exchange exchange) {
89+
if (exchange.getIn().getBody() instanceof ProviderResponse response) {
90+
return response.status();
91+
}
92+
return null;
93+
}
94+
5895
private ProviderResponse getProviderResponse(Exchange oldExchange, Exchange newExchange) {
5996
var body = oldExchange.getIn().getBody();
6097
if (body != null && body instanceof ProviderResponse) {

src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyIntegration.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -106,42 +106,47 @@ public void configure() throws Exception {
106106
.parallelProcessing()
107107
.transform()
108108
.method(requestBuilder, "buildRequest")
109-
.circuitBreaker()
110-
.faultToleranceConfiguration()
111-
.timeoutEnabled(true)
112-
.timeoutDuration(TIMEOUT_DURATION)
113-
.end()
114-
115-
.to(direct("trustifyRequest"))
116-
.onFallback()
117-
.process(responseHandler::processResponseError);
109+
.to(direct("trustifyRequest"));
118110

119111
from(direct("recommendations"))
120112
.routeId("recommendations")
121113
.routePolicy(new ProviderRoutePolicy(registry))
122114
.choice()
123115
.when(exchangeProperty(Constants.RECOMMEND_PARAM).isEqualTo(Boolean.TRUE))
116+
.circuitBreaker()
117+
.faultToleranceConfiguration()
118+
.timeoutEnabled(true)
119+
.timeoutDuration(TIMEOUT_DURATION)
120+
.end()
124121
.process(this::processRecommendationsRequest)
125122
.toD("${exchangeProperty.trustifyUrl}")
126123
.process(this::processRecommendations)
124+
.onFallback()
125+
.process(responseHandler::processResponseError)
127126
.endChoice()
128127

129128
.end();
130129

131130
from(direct("vulnerabilities"))
132131
.routeId("vulnerabilities")
133132
.routePolicy(new ProviderRoutePolicy(registry))
134-
.process(this::processVulnerabilitiesRequest)
133+
.circuitBreaker()
134+
.faultToleranceConfiguration()
135+
.timeoutEnabled(true)
136+
.timeoutDuration(TIMEOUT_DURATION)
137+
.end()
138+
.process(this::processVulnerabilitiesRequest)
135139
.toD("${exchangeProperty.trustifyUrl}")
136140
.transform(method(responseHandler, "responseToIssues"))
141+
.onFallback()
142+
.process(responseHandler::processResponseError)
137143
.end();
138144

139145
from(direct("trustifyRequest"))
140146
.routeId("trustifyRequest")
141147
.process(this::setProviderConfig)
142148
.process(this::addAuthentication)
143149
.multicast(new RecommendationAggregation())
144-
.stopOnException()
145150
.parallelProcessing()
146151
.to(direct("vulnerabilities"))
147152
.to(direct("recommendations"))

src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@
2626
import java.util.Map;
2727
import java.util.stream.Collectors;
2828

29-
import org.apache.camel.Body;
3029
import org.apache.camel.Exchange;
31-
import org.apache.camel.ExchangeProperty;
3230

3331
import com.fasterxml.jackson.databind.JsonNode;
3432
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -70,12 +68,11 @@ protected String getProviderName(Exchange exchange) {
7068
}
7169

7270
@Override
73-
public ProviderResponse responseToIssues(
74-
@Body byte[] response,
75-
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
76-
throws IOException {
71+
public ProviderResponse responseToIssues(Exchange exchange) throws IOException {
72+
var response = exchange.getIn().getBody(byte[].class);
73+
var tree = exchange.getProperty(Constants.DEPENDENCY_TREE_PROPERTY, DependencyTree.class);
7774
var json = (ObjectNode) mapper.readTree(response);
78-
return new ProviderResponse(getIssues(json, tree), null);
75+
return new ProviderResponse(getIssues(json, tree), defaultOkStatus(getProviderName(exchange)));
7976
}
8077

8178
private Map<String, PackageItem> getIssues(ObjectNode response, DependencyTree tree) {

src/test/java/io/github/guacsec/trustifyda/integration/AbstractAnalysisTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,8 +188,34 @@ protected void verifyProviders(Collection<String> providers, Map<String, String>
188188
}
189189

190190
protected void stubRecommendRequests() {
191+
// Invalid token
192+
server.stubFor(
193+
post(Constants.TRUSTIFY_RECOMMEND_PATH)
194+
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + INVALID_TOKEN))
195+
.willReturn(
196+
aResponse()
197+
.withStatus(401)
198+
.withBody(
199+
"{\"error\": \"Unauthorized\", \"message\": \"Verify the provided"
200+
+ " credentials are valid.\"}}")));
201+
// Internal Error
202+
server.stubFor(
203+
post(Constants.TRUSTIFY_RECOMMEND_PATH)
204+
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + ERROR_TOKEN))
205+
.willReturn(aResponse().withStatus(500).withBody("Unexpected error")));
206+
// Forbidden
207+
server.stubFor(
208+
post(Constants.TRUSTIFY_RECOMMEND_PATH)
209+
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + UNAUTH_TOKEN))
210+
.willReturn(
211+
aResponse()
212+
.withStatus(403)
213+
.withBody(
214+
"{\"error\": \"Forbidden\", \"message\": \"The provided credentials don't"
215+
+ " have the required permissions.\"}}")));
191216
server.stubFor(
192217
post(Constants.TRUSTIFY_RECOMMEND_PATH)
218+
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + TRUSTIFY_TOKEN))
193219
.willReturn(
194220
aResponse()
195221
.withStatus(200)
@@ -200,6 +226,7 @@ protected void stubRecommendRequests() {
200226
.withRequestBody(
201227
equalToJson(
202228
loadFileAsString("__files/trustedcontent/maven_request.json"), true, false))
229+
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + TRUSTIFY_TOKEN))
203230
.willReturn(
204231
aResponse()
205232
.withStatus(200)
@@ -210,6 +237,7 @@ protected void stubRecommendRequests() {
210237
.withRequestBody(
211238
equalToJson(
212239
loadFileAsString("__files/trustedcontent/batch_request.json"), true, false))
240+
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + TRUSTIFY_TOKEN))
213241
.willReturn(
214242
aResponse()
215243
.withStatus(200)

src/test/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandlerTest.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import static org.junit.jupiter.api.Assertions.assertEquals;
2121
import static org.junit.jupiter.api.Assertions.assertNotNull;
2222
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
import static org.mockito.Mockito.mock;
24+
import static org.mockito.Mockito.when;
2325

2426
import java.io.IOException;
2527
import java.util.Collections;
@@ -31,6 +33,7 @@
3133
import java.util.stream.Stream;
3234

3335
import org.apache.camel.Exchange;
36+
import org.apache.camel.Message;
3437
import org.junit.jupiter.api.Test;
3538
import org.junit.jupiter.params.ParameterizedTest;
3639
import org.junit.jupiter.params.provider.Arguments;
@@ -46,6 +49,7 @@
4649
import io.github.guacsec.trustifyda.api.v5.SeverityUtils;
4750
import io.github.guacsec.trustifyda.api.v5.Source;
4851
import io.github.guacsec.trustifyda.api.v5.SourceSummary;
52+
import io.github.guacsec.trustifyda.integration.Constants;
4953
import io.github.guacsec.trustifyda.model.DependencyTree;
5054
import io.github.guacsec.trustifyda.model.DirectDependency;
5155
import io.github.guacsec.trustifyda.model.PackageItem;
@@ -70,7 +74,6 @@ public void testSummary(
7074
ProviderResponseHandler handler = new TestResponseHandler();
7175
ProviderReport response =
7276
handler.buildReport(Mockito.mock(Exchange.class), new ProviderResponse(data, null), tree);
73-
assertOkStatus(response);
7477
SourceSummary summary = getValidSource(response, sourceName).getSummary();
7578

7679
assertEquals(expected.getDirect(), summary.getDirect());
@@ -192,7 +195,6 @@ public void testSorted() throws IOException {
192195
handler.buildReport(
193196
Mockito.mock(Exchange.class), new ProviderResponse(data, null), buildTree());
194197

195-
assertOkStatus(response);
196198
DependencyReport reportHighest = getValidSource(response, TEST_SOURCE).getDependencies().get(0);
197199
assertEquals("ab", reportHighest.getRef().name());
198200

@@ -224,7 +226,6 @@ public void testHighestVulnerabilityInDirectDependency() throws IOException {
224226
handler.buildReport(
225227
Mockito.mock(Exchange.class), new ProviderResponse(data, null), buildTree());
226228

227-
assertOkStatus(response);
228229
DependencyReport highest = getValidSource(response, TEST_SOURCE).getDependencies().get(0);
229230
assertEquals("ISSUE-002", highest.getHighestVulnerability().getId());
230231
assertEquals(9f, highest.getHighestVulnerability().getCvssScore());
@@ -246,7 +247,6 @@ public void testHighestVulnerabilityInTransitiveDependency() throws IOException
246247
handler.buildReport(
247248
Mockito.mock(Exchange.class), new ProviderResponse(data, null), buildTree());
248249

249-
assertOkStatus(response);
250250
DependencyReport highest = getValidSource(response, TEST_SOURCE).getDependencies().get(0);
251251
assertEquals("ISSUE-002", highest.getHighestVulnerability().getId());
252252
assertEquals(9f, highest.getHighestVulnerability().getCvssScore());
@@ -426,9 +426,17 @@ protected String getProviderName(Exchange exchange) {
426426
}
427427

428428
@Override
429-
public ProviderResponse responseToIssues(byte[] response, DependencyTree tree)
430-
throws IOException {
429+
public ProviderResponse responseToIssues(Exchange exchange) throws IOException {
431430
throw new IllegalArgumentException("not implemented");
432431
}
433432
}
433+
434+
public static Exchange buildExchange(byte[] response, DependencyTree tree) {
435+
var exchange = mock(Exchange.class);
436+
when(exchange.getIn()).thenReturn(mock(Message.class));
437+
when(exchange.getIn().getBody(byte[].class)).thenReturn(response);
438+
when(exchange.getProperty(Constants.DEPENDENCY_TREE_PROPERTY, DependencyTree.class))
439+
.thenReturn(tree);
440+
return exchange;
441+
}
434442
}

src/test/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandlerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
package io.github.guacsec.trustifyda.integration.providers.osv;
1919

20+
import static io.github.guacsec.trustifyda.integration.providers.ProviderResponseHandlerTest.buildExchange;
2021
import static org.junit.jupiter.api.Assertions.assertEquals;
2122
import static org.junit.jupiter.api.Assertions.assertFalse;
2223
import static org.junit.jupiter.api.Assertions.assertNotNull;
@@ -57,7 +58,7 @@ void testVectors() throws IOException, URISyntaxException {
5758
deps.put(jacksonRef, new DirectDependency(jacksonRef, null));
5859
var dependencyTree = new DependencyTree(deps);
5960

60-
var report = handler.responseToIssues(providerResponse, dependencyTree);
61+
var report = handler.responseToIssues(buildExchange(providerResponse, dependencyTree));
6162

6263
assertFalse(report.pkgItems().isEmpty());
6364
assertEquals(2, report.pkgItems().size());

0 commit comments

Comments
 (0)