Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ public abstract class ProviderResponseHandler {

protected abstract String getProviderName(Exchange exchange);

public abstract ProviderResponse responseToIssues(byte[] response, DependencyTree tree)
throws IOException;
public abstract ProviderResponse responseToIssues(Exchange exchange) throws IOException;

public ProviderResponse aggregateSplit(ProviderResponse oldExchange, ProviderResponse newExchange)
throws IOException {
Expand Down Expand Up @@ -111,7 +110,7 @@ public ProviderResponse aggregateSplit(ProviderResponse oldExchange, ProviderRes
return exchange;
}

protected ProviderStatus defaultOkStatus(String provider) {
public ProviderStatus defaultOkStatus(String provider) {
return new ProviderStatus()
.name(provider)
.ok(Boolean.TRUE)
Expand Down Expand Up @@ -204,7 +203,7 @@ public void processTokenFallBack(Exchange exchange) {
private static String prettifyHttpError(HttpOperationFailedException httpException) {
String text = httpException.getStatusText();
String defaultReason =
httpException.getResponseBody() != null
httpException.getResponseBody() != null && !httpException.getResponseBody().isBlank()
? httpException.getResponseBody()
: httpException.getMessage();
return text
Expand All @@ -216,9 +215,8 @@ private static String prettifyHttpError(HttpOperationFailedException httpExcepti
};
}

public ProviderResponse emptyResponse(
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree) {
return new ProviderResponse(Collections.emptyMap(), null);
public ProviderResponse emptyResponse(Exchange exchange) {
return new ProviderResponse(Collections.emptyMap(), defaultOkStatus(getProviderName(exchange)));
}

/**
Expand Down Expand Up @@ -295,7 +293,7 @@ public ProviderReport buildReport(
@Body ProviderResponse response,
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
throws IOException {
if (response.status() != null) {
if (response.status() != null && response.pkgItems() == null) {
return new ProviderReport().status(response.status()).sources(Collections.emptyMap());
}
var providerName = getProviderName(exchange);
Expand All @@ -306,7 +304,7 @@ public ProviderReport buildReport(
.entrySet()
.forEach(
entry -> reports.put(entry.getKey(), buildReportForSource(entry.getValue(), tree)));
return new ProviderReport().status(defaultOkStatus(providerName)).sources(reports);
return new ProviderReport().status(response.status()).sources(reports);
}

private Source buildReportForSource(Map<String, PackageItem> pkgItemsData, DependencyTree tree) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.camel.Body;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangeProperty;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -62,12 +60,11 @@ protected String getProviderName(Exchange exchange) {
}

@Override
public ProviderResponse responseToIssues(
@Body byte[] response,
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
throws IOException {
public ProviderResponse responseToIssues(Exchange exchange) throws IOException {
var response = exchange.getIn().getBody(byte[].class);
var tree = exchange.getProperty(Constants.DEPENDENCY_TREE_PROPERTY, DependencyTree.class);
var json = (ObjectNode) mapper.readTree(response);
return new ProviderResponse(getIssues(json, tree), null);
return new ProviderResponse(getIssues(json, tree), defaultOkStatus(getProviderName(exchange)));
}

private Map<String, PackageItem> getIssues(ObjectNode response, DependencyTree tree) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,11 @@

import org.apache.camel.AggregationStrategy;
import org.apache.camel.Exchange;
import org.jboss.logging.Logger;

import io.github.guacsec.trustifyda.api.PackageRef;
import io.github.guacsec.trustifyda.api.v5.Issue;
import io.github.guacsec.trustifyda.api.v5.ProviderStatus;
import io.github.guacsec.trustifyda.api.v5.Remediation;
import io.github.guacsec.trustifyda.api.v5.RemediationTrustedContent;
import io.github.guacsec.trustifyda.model.PackageItem;
Expand All @@ -36,25 +38,60 @@

public class RecommendationAggregation implements AggregationStrategy {

private static final Logger LOGGER = Logger.getLogger(RecommendationAggregation.class);

@Override
public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
if (oldExchange == null) {
return newExchange;
}
var worstStatus = getWorstStatus(oldExchange, newExchange);

var providerResponse = getProviderResponse(oldExchange, newExchange);
var recommendations = getRecommendations(oldExchange, newExchange);
if (providerResponse == null) {
providerResponse = new ProviderResponse(new HashMap<>(), worstStatus);
}
Map<PackageRef, IndexedRecommendation> recommendations = null;
try {
recommendations = getRecommendations(oldExchange, newExchange);
} catch (Exception e) {
LOGGER.warn("Error getting recommendations", e);
}

if (providerResponse.pkgItems() == null) {
providerResponse = new ProviderResponse(new HashMap<>(), providerResponse.status());
providerResponse = new ProviderResponse(new HashMap<>(), worstStatus);
}
if (recommendations != null && !recommendations.isEmpty()) {
setTrustedContent(recommendations, providerResponse);
}

oldExchange.getIn().setBody(providerResponse);
oldExchange.getIn().setBody(new ProviderResponse(providerResponse.pkgItems(), worstStatus));
return oldExchange;
}

private ProviderStatus getWorstStatus(Exchange oldExchange, Exchange newExchange) {
ProviderStatus oldStatus = getStatus(oldExchange);
ProviderStatus newStatus = getStatus(newExchange);

if (oldStatus == null && newStatus == null) {
return null;
}
if (oldStatus == null) {
return newStatus;
}
if (newStatus == null) {
return oldStatus;
}
return Boolean.FALSE.equals(oldStatus.getOk()) ? oldStatus : newStatus;
}

private ProviderStatus getStatus(Exchange exchange) {
if (exchange.getIn().getBody() instanceof ProviderResponse response) {
return response.status();
}
return null;
}

private ProviderResponse getProviderResponse(Exchange oldExchange, Exchange newExchange) {
var body = oldExchange.getIn().getBody();
if (body != null && body instanceof ProviderResponse) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,42 +106,47 @@ public void configure() throws Exception {
.parallelProcessing()
.transform()
.method(requestBuilder, "buildRequest")
.circuitBreaker()
.faultToleranceConfiguration()
.timeoutEnabled(true)
.timeoutDuration(TIMEOUT_DURATION)
.end()

.to(direct("trustifyRequest"))
.onFallback()
.process(responseHandler::processResponseError);
.to(direct("trustifyRequest"));

from(direct("recommendations"))
.routeId("recommendations")
.routePolicy(new ProviderRoutePolicy(registry))
.choice()
.when(exchangeProperty(Constants.RECOMMEND_PARAM).isEqualTo(Boolean.TRUE))
.circuitBreaker()
.faultToleranceConfiguration()
.timeoutEnabled(true)
.timeoutDuration(TIMEOUT_DURATION)
.end()
.process(this::processRecommendationsRequest)
.toD("${exchangeProperty.trustifyUrl}")
.process(this::processRecommendations)
.onFallback()
.process(responseHandler::processResponseError)
.endChoice()

.end();

from(direct("vulnerabilities"))
.routeId("vulnerabilities")
.routePolicy(new ProviderRoutePolicy(registry))
.process(this::processVulnerabilitiesRequest)
.circuitBreaker()
.faultToleranceConfiguration()
.timeoutEnabled(true)
.timeoutDuration(TIMEOUT_DURATION)
.end()
.process(this::processVulnerabilitiesRequest)
.toD("${exchangeProperty.trustifyUrl}")
.transform(method(responseHandler, "responseToIssues"))
.onFallback()
.process(responseHandler::processResponseError)
.end();

from(direct("trustifyRequest"))
.routeId("trustifyRequest")
.process(this::setProviderConfig)
.process(this::addAuthentication)
.multicast(new RecommendationAggregation())
.stopOnException()
.parallelProcessing()
.to(direct("vulnerabilities"))
.to(direct("recommendations"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.camel.Body;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangeProperty;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
Expand Down Expand Up @@ -70,12 +68,11 @@ protected String getProviderName(Exchange exchange) {
}

@Override
public ProviderResponse responseToIssues(
@Body byte[] response,
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
throws IOException {
public ProviderResponse responseToIssues(Exchange exchange) throws IOException {
var response = exchange.getIn().getBody(byte[].class);
var tree = exchange.getProperty(Constants.DEPENDENCY_TREE_PROPERTY, DependencyTree.class);
var json = (ObjectNode) mapper.readTree(response);
return new ProviderResponse(getIssues(json, tree), null);
return new ProviderResponse(getIssues(json, tree), defaultOkStatus(getProviderName(exchange)));
}

private Map<String, PackageItem> getIssues(ObjectNode response, DependencyTree tree) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,15 +188,49 @@ protected void verifyProviders(Collection<String> providers, Map<String, String>
}

protected void stubRecommendRequests() {
// Missing token
server.stubFor(post(Constants.TRUSTIFY_RECOMMEND_PATH).willReturn(aResponse().withStatus(401)));

// Invalid token
server.stubFor(
post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + INVALID_TOKEN))
.willReturn(
aResponse()
.withStatus(401)
.withBody(
"{\"error\": \"Unauthorized\", \"message\": \"Verify the provided"
+ " credentials are valid.\"}}")));
// Internal Error
server.stubFor(
post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + ERROR_TOKEN))
.willReturn(aResponse().withStatus(500).withBody("Unexpected error")));
// Forbidden
server.stubFor(
post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withHeader(Constants.AUTHORIZATION_HEADER, equalTo("Bearer " + UNAUTH_TOKEN))
.willReturn(
aResponse()
.withStatus(403)
.withBody(
"{\"error\": \"Forbidden\", \"message\": \"The provided credentials don't"
+ " have the required permissions.\"}}")));
server.stubFor(
post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withHeader(
Constants.AUTHORIZATION_HEADER,
equalTo("Bearer " + TRUSTIFY_TOKEN).or(equalTo("Bearer " + OK_TOKEN)))
.willReturn(
aResponse()
.withStatus(200)
.withHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBodyFile("trustedcontent/empty_report.json")));
server.stubFor(
post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withHeader(
Constants.AUTHORIZATION_HEADER,
equalTo("Bearer " + TRUSTIFY_TOKEN).or(equalTo("Bearer " + OK_TOKEN)))
.withRequestBody(
equalToJson(
loadFileAsString("__files/trustedcontent/maven_request.json"), true, false))
Expand All @@ -207,6 +241,9 @@ protected void stubRecommendRequests() {
.withBodyFile("trustedcontent/maven_report.json")));
server.stubFor(
post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withHeader(
Constants.AUTHORIZATION_HEADER,
equalTo("Bearer " + TRUSTIFY_TOKEN).or(equalTo("Bearer " + OK_TOKEN)))
.withRequestBody(
equalToJson(
loadFileAsString("__files/trustedcontent/batch_request.json"), true, false))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.IOException;
import java.util.Collections;
Expand All @@ -31,6 +33,7 @@
import java.util.stream.Stream;

import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
Expand All @@ -46,6 +49,7 @@
import io.github.guacsec.trustifyda.api.v5.SeverityUtils;
import io.github.guacsec.trustifyda.api.v5.Source;
import io.github.guacsec.trustifyda.api.v5.SourceSummary;
import io.github.guacsec.trustifyda.integration.Constants;
import io.github.guacsec.trustifyda.model.DependencyTree;
import io.github.guacsec.trustifyda.model.DirectDependency;
import io.github.guacsec.trustifyda.model.PackageItem;
Expand All @@ -70,7 +74,6 @@ public void testSummary(
ProviderResponseHandler handler = new TestResponseHandler();
ProviderReport response =
handler.buildReport(Mockito.mock(Exchange.class), new ProviderResponse(data, null), tree);
assertOkStatus(response);
SourceSummary summary = getValidSource(response, sourceName).getSummary();

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

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

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

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

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

@Override
public ProviderResponse responseToIssues(byte[] response, DependencyTree tree)
throws IOException {
public ProviderResponse responseToIssues(Exchange exchange) throws IOException {
throw new IllegalArgumentException("not implemented");
}
}

public static Exchange buildExchange(byte[] response, DependencyTree tree) {
var exchange = mock(Exchange.class);
when(exchange.getIn()).thenReturn(mock(Message.class));
when(exchange.getIn().getBody(byte[].class)).thenReturn(response);
when(exchange.getProperty(Constants.DEPENDENCY_TREE_PROPERTY, DependencyTree.class))
.thenReturn(tree);
return exchange;
}
}
Loading