diff --git a/README.md b/README.md
index 04c91d76..0bb507e0 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,7 @@
## Dependencies
-- Redis cache: Allows caching Red Hat recommendations and remediations. Can be configured with the `quarkus.redis.host` parameter
-- TrustedContent: Provides Red Hat recommendations and remediations.
-- External Vulnerability providers enabled.
+- Trustify: Provides vulnerability data and recommendations [Trustify](https://github.com/guacsec/trustify)
- Postgres Database: Stores data needed for the Model Cards functionality. See [Model Cards](#model-cards)
## Vulnerability providers
diff --git a/deploy/trust-da.yaml b/deploy/trust-da.yaml
index fa39af90..583d7d4d 100644
--- a/deploy/trust-da.yaml
+++ b/deploy/trust-da.yaml
@@ -32,16 +32,6 @@ spec:
env:
- name: MONITORING_ENABLED
value: "false"
- - name: DB_REDIS_HOST
- valueFrom:
- secretKeyRef:
- name: trust-da-secret
- key: db.host
- - name: DB_REDIS_PORT
- valueFrom:
- secretKeyRef:
- name: trust-da-secret
- key: db.port
- name: TRUSTIFY_HOST
value: http://trustify:8080/api/v2/
- name: TRUSTIFY_CLIENT_ID
diff --git a/pom.xml b/pom.xml
index 75e4f47b..d3158f9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -89,10 +89,6 @@
io.quarkus
quarkus-rest-jackson
-
- io.quarkus
- quarkus-redis-client
-
org.apache.camel.quarkus
camel-quarkus-jackson
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/Constants.java b/src/main/java/io/github/guacsec/trustifyda/integration/Constants.java
index 475ef4ce..b7077f77 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/Constants.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/Constants.java
@@ -57,7 +57,6 @@ private Constants() {}
public static final String SPDX_MEDIATYPE_JSON = "application/vnd.spdx+json";
public static final String CYCLONEDX_MEDIATYPE_JSON = "application/vnd.cyclonedx+json";
- public static final String TRUSTED_CONTENT_PROVIDER = "trusted-content";
public static final String OSV_PROVIDER = "osv";
public static final String HTTP_UNAUTHENTICATED = "Unauthenticated";
@@ -70,15 +69,13 @@ private Constants() {}
public static final String API_VERSION_PROPERTY = "apiVersion";
public static final String GZIP_RESPONSE_PROPERTY = "gzipResponse";
public static final String SBOM_ID_PROPERTY = "sbomId";
- public static final String UNSCANNED_REFS_PROPERTY = "unscannedRefs";
- public static final String CACHED_RECOMMENDATIONS_PROPERTY = "missedRecommendations";
public static final String PROVIDER_CONFIG_PROPERTY = "providerConfig";
public static final String PROVIDERS_PROPERTY = "providers";
public static final String OSV_NVD_PURLS_PATH = "/purls";
public static final String OSV_NVD_HEALTH_PATH = "/q/health";
- public static final String TRUSTED_CONTENT_PATH = "/recommend";
+ public static final String TRUSTIFY_RECOMMEND_PATH = "/api/v2/purl/recommend";
public static final String TRUSTIFY_ANALYZE_PATH = "/api/v2/vulnerability/analyze";
public static final String TRUSTIFY_HEALTH_PATH = "/.well-known/trustify";
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/backend/ExhortIntegration.java b/src/main/java/io/github/guacsec/trustifyda/integration/backend/ExhortIntegration.java
index db69cf0b..18a11644 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/backend/ExhortIntegration.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/backend/ExhortIntegration.java
@@ -56,7 +56,6 @@
import io.github.guacsec.trustifyda.integration.providers.VulnerabilityProvider;
import io.github.guacsec.trustifyda.integration.sbom.SbomParser;
import io.github.guacsec.trustifyda.integration.sbom.SbomParserFactory;
-import io.github.guacsec.trustifyda.integration.trustedcontent.TcResponseAggregation;
import io.github.guacsec.trustifyda.model.DependencyTree;
import io.github.guacsec.trustifyda.model.DirectDependency;
import io.github.guacsec.trustifyda.monitoring.MonitoringProcessor;
@@ -82,8 +81,6 @@ public class ExhortIntegration extends EndpointRouteBuilder {
@Inject MonitoringProcessor monitoringProcessor;
- @Inject TcResponseAggregation tcResponseAggregation;
-
@Inject ObjectMapper mapper;
ExhortIntegration(MeterRegistry registry) {
@@ -226,7 +223,6 @@ public void configure() {
from(direct("analyzeSbom"))
.routeId("analyzeSbom")
- .enrich(direct("getTrustedContent"), tcResponseAggregation)
.to(direct("findVulnerabilities"))
.transform().method(ProviderAggregationStrategy.class, "toReport");
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/cache/CacheService.java b/src/main/java/io/github/guacsec/trustifyda/integration/cache/CacheService.java
deleted file mode 100644
index cfbe6ba0..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/integration/cache/CacheService.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.integration.cache;
-
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.camel.Body;
-import org.apache.camel.ExchangeProperty;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-import io.github.guacsec.trustifyda.integration.Constants;
-import io.github.guacsec.trustifyda.model.trustedcontent.CachedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
-
-public interface CacheService {
-
- public void cacheRecommendations(
- @Body TrustedContentResponse response,
- @ExchangeProperty(Constants.CACHED_RECOMMENDATIONS_PROPERTY) Set misses);
-
- public Map getRecommendations(Set purls);
-}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheService.java b/src/main/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheService.java
deleted file mode 100644
index 7b42c613..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheService.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.integration.cache;
-
-import java.time.Duration;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-import io.github.guacsec.trustifyda.model.trustedcontent.CachedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
-import io.quarkus.redis.datasource.RedisDataSource;
-import io.quarkus.redis.datasource.value.ValueCommands;
-
-import jakarta.enterprise.context.ApplicationScoped;
-
-@ApplicationScoped
-public class RedisCacheService implements CacheService {
-
- @ConfigProperty(name = "recommendations.cache.ttl", defaultValue = "1d")
- Duration recommendationTtl;
-
- private final ValueCommands recommendationsCommands;
-
- public RedisCacheService(RedisDataSource ds) {
- this.recommendationsCommands = ds.value(CachedRecommendation.class);
- }
-
- @Override
- public void cacheRecommendations(TrustedContentResponse response, Set misses) {
- if (response == null || response.status() == null || !response.status().getOk()) {
- return;
- }
- misses.stream()
- .forEach(
- v ->
- recommendationsCommands.psetex(
- "recommendations:" + v.ref(),
- recommendationTtl.toMillis(),
- new CachedRecommendation(v, response.recommendations().get(v))));
- }
-
- @Override
- public Map getRecommendations(Set purls) {
- if (purls == null || purls.isEmpty()) {
- return Collections.emptyMap();
- }
- var result =
- recommendationsCommands.mget(
- purls.stream().map(p -> "recommendations:" + p.ref()).toArray(String[]::new));
- return result.values().stream()
- .filter(Objects::nonNull)
- .collect(Collectors.toMap(v -> v.ref(), v -> v));
- }
-}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderAggregationStrategy.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderAggregationStrategy.java
index ddd26aee..c81536ac 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderAggregationStrategy.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderAggregationStrategy.java
@@ -28,7 +28,6 @@
import io.github.guacsec.trustifyda.api.v5.Scanned;
import io.github.guacsec.trustifyda.integration.Constants;
import io.github.guacsec.trustifyda.model.DependencyTree;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
@@ -45,11 +44,8 @@ public Map aggregate(
public AnalysisReport toReport(
@Body Map reports,
- @ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree,
- @ExchangeProperty(Constants.TRUSTED_CONTENT_PROVIDER) TrustedContentResponse tcResponse) {
+ @ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree) {
- reports.put(
- Constants.TRUSTED_CONTENT_PROVIDER, new ProviderReport().status(tcResponse.status()));
var scanned = new Scanned().direct(tree.directCount()).transitive(tree.transitiveCount());
scanned.total(scanned.getDirect() + scanned.getTransitive());
return new AnalysisReport().providers(reports).scanned(scanned);
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandler.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandler.java
index 09d0a5cc..660c4518 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandler.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandler.java
@@ -22,9 +22,11 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -40,22 +42,17 @@
import io.github.guacsec.trustifyda.api.v5.Issue;
import io.github.guacsec.trustifyda.api.v5.ProviderReport;
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.api.v5.Source;
import io.github.guacsec.trustifyda.api.v5.SourceSummary;
import io.github.guacsec.trustifyda.api.v5.TransitiveDependencyReport;
-import io.github.guacsec.trustifyda.api.v5.UnscannedDependency;
import io.github.guacsec.trustifyda.config.exception.PackageValidationException;
import io.github.guacsec.trustifyda.config.exception.UnexpectedProviderException;
import io.github.guacsec.trustifyda.integration.Constants;
import io.github.guacsec.trustifyda.model.CvssScoreComparable.DependencyScoreComparator;
import io.github.guacsec.trustifyda.model.CvssScoreComparable.TransitiveScoreComparator;
import io.github.guacsec.trustifyda.model.DependencyTree;
+import io.github.guacsec.trustifyda.model.PackageItem;
import io.github.guacsec.trustifyda.model.ProviderResponse;
-import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
-import io.github.guacsec.trustifyda.model.trustedcontent.Vulnerability;
import io.github.guacsec.trustifyda.monitoring.MonitoringProcessor;
import io.quarkus.runtime.annotations.RegisterForReflection;
@@ -68,10 +65,6 @@
@ApplicationScoped
public abstract class ProviderResponseHandler {
- public static final String NOT_AFFECTED_STATUS = "NotAffected";
- public static final String FIXED_STATUS = "Fixed";
- public static final List FIXED_STATUSES = List.of(NOT_AFFECTED_STATUS, FIXED_STATUS);
-
private static final Logger LOGGER = Logger.getLogger(ProviderResponseHandler.class);
@Inject MonitoringProcessor monitoringProcessor;
@@ -87,46 +80,33 @@ public ProviderResponse aggregateSplit(ProviderResponse oldExchange, ProviderRes
return newExchange;
}
if (oldExchange.status() != null && !Boolean.TRUE.equals(oldExchange.status().getOk())) {
- if (newExchange.unscanned() != null) {
- if (oldExchange.unscanned() == null) {
- return new ProviderResponse(
- oldExchange.issues(), oldExchange.status(), newExchange.unscanned());
- } else {
- oldExchange.unscanned().addAll(newExchange.unscanned());
- }
- }
return oldExchange;
}
- var exchange = new ProviderResponse(new HashMap<>(), oldExchange.status(), new ArrayList<>());
- if (oldExchange.unscanned() != null) {
- exchange.unscanned().addAll(oldExchange.unscanned());
- }
- if (newExchange.unscanned() != null) {
- exchange.unscanned().addAll(newExchange.unscanned());
- }
- if (oldExchange.issues() != null) {
- exchange.issues().putAll(oldExchange.issues());
+ var exchange = new ProviderResponse(new HashMap<>(), oldExchange.status());
+
+ if (oldExchange.pkgItems() != null) {
+ exchange.pkgItems().putAll(oldExchange.pkgItems());
}
- if (newExchange.issues() != null) {
+ if (newExchange.pkgItems() != null) {
exchange
- .issues()
+ .pkgItems()
.entrySet()
.forEach(
e -> {
- var issues = newExchange.issues().get(e.getKey());
- if (issues != null) {
- e.getValue().addAll(issues);
+ var item = newExchange.pkgItems().get(e.getKey());
+ if (item != null) {
+ e.getValue().issues().addAll(item.issues());
}
});
- newExchange.issues().keySet().stream()
- .filter(k -> !exchange.issues().keySet().contains(k))
+ newExchange.pkgItems().keySet().stream()
+ .filter(k -> !exchange.pkgItems().keySet().contains(k))
.forEach(
k -> {
- exchange.issues().put(k, newExchange.issues().get(k));
+ exchange.pkgItems().put(k, newExchange.pkgItems().get(k));
});
} else if (Boolean.FALSE.equals(newExchange.status().getOk())) {
- return new ProviderResponse(exchange.issues(), newExchange.status(), exchange.unscanned());
+ return new ProviderResponse(exchange.pkgItems(), newExchange.status());
}
return exchange;
}
@@ -163,11 +143,10 @@ public void processResponseError(Exchange exchange) {
var providerName = getProviderName(exchange);
ProviderStatus status = new ProviderStatus().ok(false).name(providerName);
Exception exception = (Exception) exchange.getProperty(Exchange.EXCEPTION_CAUGHT);
- if (exception == null) {}
- Throwable cause = exception.getCause();
+ Throwable cause = exception != null ? exception.getCause() : null;
- while (cause instanceof RuntimeCamelException && cause != null) {
+ while (cause instanceof RuntimeCamelException) {
cause = cause.getCause();
}
if (cause == null) {
@@ -188,7 +167,7 @@ public void processResponseError(Exchange exchange) {
.code(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
LOGGER.warn("Unable to process request to: {}", providerName, cause);
}
- ProviderResponse response = new ProviderResponse(null, status, null);
+ ProviderResponse response = new ProviderResponse(null, status);
monitoringProcessor.processProviderError(exchange, exception, providerName);
exchange.getMessage().setBody(response);
}
@@ -239,114 +218,118 @@ private static String prettifyHttpError(HttpOperationFailedException httpExcepti
public ProviderResponse emptyResponse(
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree) {
- return new ProviderResponse(Collections.emptyMap(), null, null);
+ return new ProviderResponse(Collections.emptyMap(), null);
}
- public ProviderResponse unscannedResponse(
- @ExchangeProperty(Constants.UNSCANNED_REFS_PROPERTY) List unscanned) {
- return new ProviderResponse(Collections.emptyMap(), null, unscanned);
- }
-
- private Map>> splitIssuesBySource(
- Map> issuesData) {
- Map>> sourcesIssues = new HashMap<>();
- if (issuesData == null) {
- return sourcesIssues;
+ /**
+ * Groups PackageItems by source from their issues. Each PackageItem can have issues from
+ * different sources, so we group them accordingly while preserving recommendations.
+ */
+ private Map> groupPackageItemsBySource(
+ Map pkgItems, String defaultSource) {
+ Map> sourcesData = new HashMap<>();
+ if (pkgItems == null) {
+ return sourcesData;
}
- issuesData
- .entrySet()
- .forEach(
- e -> {
- e.getValue()
- .forEach(
- i -> {
- var issues = sourcesIssues.get(i.getSource());
- if (issues == null) {
- issues = new HashMap<>();
- sourcesIssues.put(i.getSource(), issues);
- }
- var depIssues = issues.get(e.getKey());
- if (depIssues == null) {
- depIssues = new ArrayList<>();
- issues.put(e.getKey(), depIssues);
+
+ var recommendations =
+ pkgItems.values().stream()
+ .filter(item -> item.recommendation() != null)
+ .collect(Collectors.toMap(PackageItem::packageRef, PackageItem::recommendation));
+
+ pkgItems.forEach(
+ (packageRef, packageItem) -> {
+ // If there are issues, group by their source
+ if (packageItem.issues() != null && !packageItem.issues().isEmpty()) {
+ packageItem
+ .issues()
+ .forEach(
+ issue -> {
+ String source = issue.getSource();
+ if (source == null) {
+ return;
+ }
+ var sourceItems = sourcesData.computeIfAbsent(source, k -> new HashMap<>());
+ // Get or create PackageItem for this source and package
+ var existingItem = sourceItems.get(packageRef);
+ var recommendation = recommendations.get(packageRef);
+ if (existingItem == null) {
+ // Create new PackageItem with this issue and the recommendation
+ sourceItems.put(
+ packageRef,
+ new PackageItem(
+ packageRef, recommendation, new ArrayList<>(List.of(issue))));
+ } else {
+ // Add issue to existing PackageItem if not already present
+ if (!existingItem.issues().contains(issue)) {
+ var updatedIssues = new ArrayList<>(existingItem.issues());
+ updatedIssues.add(issue);
+
+ sourceItems.put(
+ packageRef,
+ new PackageItem(packageRef, recommendation, updatedIssues));
}
- depIssues.add(i);
- });
- });
- return sourcesIssues;
+ }
+ });
+ }
+ });
+ if (sourcesData.isEmpty() && !recommendations.isEmpty()) {
+ sourcesData.put(defaultSource, new HashMap<>());
+ }
+ sourcesData.forEach(
+ (source, items) -> {
+ recommendations.forEach(
+ (packageRef, recommendation) -> {
+ if (!items.containsKey(packageRef)) {
+ items.put(
+ packageRef,
+ new PackageItem(packageRef, recommendation, Collections.emptyList()));
+ }
+ });
+ });
+ return sourcesData;
}
public ProviderReport buildReport(
Exchange exchange,
@Body ProviderResponse response,
- @ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree,
- @ExchangeProperty(Constants.TRUSTED_CONTENT_PROVIDER) TrustedContentResponse tcResponse)
+ @ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
throws IOException {
if (response.status() != null) {
return new ProviderReport().status(response.status()).sources(Collections.emptyMap());
}
var providerName = getProviderName(exchange);
- var sourcesIssues = splitIssuesBySource(response.issues());
- if (sourcesIssues.isEmpty()
- && (!tcResponse.recommendations().isEmpty()
- || (response.unscanned() != null && !response.unscanned().isEmpty()))) {
- sourcesIssues.put(providerName, Collections.emptyMap());
- }
+ var sourcesData = groupPackageItemsBySource(response.pkgItems(), providerName);
+
Map reports = new HashMap<>();
- sourcesIssues
+ sourcesData
.entrySet()
.forEach(
- k ->
- reports.put(
- k.getKey(),
- buildReportForSource(k.getValue(), tree, tcResponse, response.unscanned())));
+ entry -> reports.put(entry.getKey(), buildReportForSource(entry.getValue(), tree)));
return new ProviderReport().status(defaultOkStatus(providerName)).sources(reports);
}
- private Source buildReportForSource(
- Map> issuesData,
- DependencyTree tree,
- TrustedContentResponse tcResponse,
- List unscanned) {
+ private Source buildReportForSource(Map pkgItemsData, DependencyTree tree) {
List sourceReport = new ArrayList<>();
+ Set processedRefs = new HashSet<>();
+
+ // Process packages from the dependency tree
tree.dependencies().entrySet().stream()
.forEach(
depEntry -> {
- var recommendation = tcResponse.recommendations().get(depEntry.getKey());
- var issues = getIssues(depEntry.getKey(), issuesData, recommendation);
- var directReport = new DependencyReport().ref(depEntry.getKey());
- directReport.issues(
- issues.stream()
- .sorted(Comparator.comparing(Issue::getCvssScore).reversed())
- .collect(Collectors.toList()));
- if (recommendation != null
- && !depEntry.getKey().isCoordinatesEquals(recommendation.packageName())) {
- directReport.recommendation(recommendation.packageName());
- }
- directReport.setHighestVulnerability(
- directReport.getIssues().stream().findFirst().orElse(null));
+ var packageRef = depEntry.getKey();
+ processedRefs.add(packageRef.ref());
+ var packageItem = getPackageItem(packageRef, pkgItemsData);
+ var directReport = new DependencyReport().ref(packageRef);
+
+ setIssues(packageItem, directReport);
+ setRecommendations(packageItem, directReport);
+
List transitiveReports =
depEntry.getValue().transitive().stream()
.map(
t -> {
- var tRecommendation = tcResponse.recommendations().get(t);
- var transitiveIssues = getIssues(t, issuesData, tRecommendation);
- transitiveIssues =
- transitiveIssues.stream()
- .sorted(Comparator.comparing(Issue::getCvssScore).reversed())
- .collect(Collectors.toList());
- var highestTransitive = transitiveIssues.stream().findFirst();
- if (highestTransitive.isPresent()) {
- if (directReport.getHighestVulnerability() == null
- || directReport.getHighestVulnerability().getCvssScore()
- < highestTransitive.get().getCvssScore()) {
- directReport.setHighestVulnerability(highestTransitive.get());
- }
- }
- return new TransitiveDependencyReport()
- .ref(t)
- .issues(transitiveIssues)
- .highestVulnerability(highestTransitive.orElse(null));
+ return getTransitiveReport(pkgItemsData, directReport, t);
})
.filter(transitiveReport -> !transitiveReport.getIssues().isEmpty())
.collect(Collectors.toList());
@@ -358,77 +341,101 @@ private Source buildReportForSource(
}
});
+ if (pkgItemsData != null) {
+ addRecommendationsWithoutIssues(pkgItemsData, sourceReport, processedRefs);
+ }
+
sourceReport.sort(Collections.reverseOrder(new DependencyScoreComparator()));
- var summary = buildSummary(issuesData, tree, sourceReport, unscanned);
- return new Source().summary(summary).dependencies(sourceReport).unscanned(unscanned);
+ var summary = buildSummary(pkgItemsData, tree, sourceReport);
+ return new Source().summary(summary).dependencies(sourceReport);
}
- private List getIssues(
- PackageRef ref, Map> issuesData, IndexedRecommendation recommendation) {
- var providerIssues = issuesData.get(ref.ref());
- if (providerIssues == null) {
- return Collections.emptyList();
- }
- if (recommendation == null) {
- return providerIssues;
- }
- return providerIssues.stream()
- .map(i -> setRemediation(i, recommendation))
- .filter(i -> !isAffectedTrustedContent(ref, i, recommendation))
- .toList();
+ private void addRecommendationsWithoutIssues(
+ Map pkgItemsData,
+ List sourceReport,
+ Set processedRefs) {
+ pkgItemsData.entrySet().stream()
+ .filter(
+ entry -> {
+ var packageItem = entry.getValue();
+ // Include if it has a recommendation but no issues and wasn't already processed
+ return !processedRefs.contains(entry.getKey())
+ && (packageItem.issues() == null || packageItem.issues().isEmpty())
+ && packageItem.recommendation() != null
+ && packageItem.recommendation().packageName() != null;
+ })
+ .forEach(
+ entry -> {
+ try {
+ var packageRef = new PackageRef(entry.getKey());
+ var packageItem = entry.getValue();
+ var directReport = new DependencyReport().ref(packageRef);
+ directReport.recommendation(packageItem.recommendation().packageName());
+ sourceReport.add(directReport);
+ } catch (Exception e) {
+ // Skip if packageRef cannot be created from the string
+ // This shouldn't happen but handle gracefully
+ }
+ });
}
- private boolean isAffectedTrustedContent(
- PackageRef ref, Issue issue, IndexedRecommendation recommendation) {
- if (recommendation == null) {
- return false;
+ private TransitiveDependencyReport getTransitiveReport(
+ Map pkgItemsData, DependencyReport directReport, PackageRef t) {
+ var transitiveItem = getPackageItem(t, pkgItemsData);
+ List transitiveIssues = Collections.emptyList();
+ if (transitiveItem != null
+ && transitiveItem.issues() != null
+ && !transitiveItem.issues().isEmpty()) {
+ transitiveIssues =
+ transitiveItem.issues().stream()
+ .sorted(Comparator.comparing(Issue::getCvssScore).reversed())
+ .collect(Collectors.toList());
}
- if (!ref.isCoordinatesEquals(recommendation.packageName())) {
- return false;
- }
- Vulnerability vuln;
- if (issue.getCves() == null || issue.getCves().isEmpty()) {
- vuln = recommendation.vulnerabilities().get(issue.getId());
- } else {
- vuln = recommendation.vulnerabilities().get(issue.getCves().get(0));
+ var highestTransitive = transitiveIssues.stream().findFirst();
+ if (highestTransitive.isPresent()) {
+ if (directReport.getHighestVulnerability() == null
+ || directReport.getHighestVulnerability().getCvssScore()
+ < highestTransitive.get().getCvssScore()) {
+ directReport.setHighestVulnerability(highestTransitive.get());
+ }
}
- return !isAffected(vuln);
+ var transitiveReport =
+ new TransitiveDependencyReport()
+ .ref(t)
+ .issues(transitiveIssues)
+ .highestVulnerability(highestTransitive.orElse(null));
+ // Note: TransitiveDependencyReport doesn't have a recommendation field
+ // Recommendations are only set on direct dependencies
+ return transitiveReport;
}
- private Issue setRemediation(Issue i, IndexedRecommendation recommendation) {
- if (i.getCves() == null || i.getCves().isEmpty()) {
- return i;
- }
- var cve = i.getCves().get(0);
- var vuln = recommendation.vulnerabilities().get(cve);
- if (isAffected(vuln)) {
- return i;
+ private void setRecommendations(PackageItem packageItem, DependencyReport directReport) {
+ if (packageItem != null
+ && packageItem.recommendation() != null
+ && packageItem.recommendation().packageName() != null) {
+ directReport.recommendation(packageItem.recommendation().packageName());
}
- if (i.getRemediation() == null) {
- i.remediation(new Remediation());
- }
-
- i.getRemediation()
- .setTrustedContent(
- new RemediationTrustedContent()
- .justification(vuln.getJustification())
- .status(vuln.getStatus())
- .ref(recommendation.packageName()));
- return i;
}
- private boolean isAffected(Vulnerability vuln) {
- if (vuln == null || vuln.getStatus() == null) {
- return true;
+ private void setIssues(PackageItem packageItem, DependencyReport directReport) {
+ if (packageItem != null && packageItem.issues() != null && !packageItem.issues().isEmpty()) {
+ var issues =
+ packageItem.issues().stream()
+ .sorted(Comparator.comparing(Issue::getCvssScore).reversed())
+ .collect(Collectors.toList());
+ directReport.issues(issues);
+ directReport.setHighestVulnerability(issues.stream().findFirst().orElse(null));
}
- return !FIXED_STATUSES.contains(vuln.getStatus());
+ }
+
+ private PackageItem getPackageItem(PackageRef ref, Map pkgItemsData) {
+ return pkgItemsData.get(ref.ref());
}
private SourceSummary buildSummary(
- Map> issuesData,
+ Map issuesData,
DependencyTree tree,
- List sourceReport,
- List unscanned) {
+ List sourceReport) {
var counter = new VulnerabilityCounter();
var directRefs =
tree.dependencies().keySet().stream().map(PackageRef::ref).collect(Collectors.toSet());
@@ -438,34 +445,40 @@ private SourceSummary buildSummary(
Long recommendationsCount =
sourceReport.stream().filter(s -> s.getRecommendation() != null).count();
counter.recommendations.set(recommendationsCount.intValue());
- counter.unscanned.set(Optional.ofNullable(unscanned).map(List::size).orElse(0));
return counter.getSummary();
}
- private void incrementCounter(
- List issues, VulnerabilityCounter counter, boolean isDirect) {
- if (!issues.isEmpty()) {
+ private void incrementCounter(PackageItem item, VulnerabilityCounter counter, boolean isDirect) {
+ if (item != null && !item.issues().isEmpty()) {
counter.dependencies.incrementAndGet();
}
- issues.forEach(
- i -> {
- var vulnerabilities = countVulnerabilities(i);
- switch (i.getSeverity()) {
- case CRITICAL -> counter.critical.addAndGet(vulnerabilities);
- case HIGH -> counter.high.addAndGet(vulnerabilities);
- case MEDIUM -> counter.medium.addAndGet(vulnerabilities);
- case LOW -> counter.low.addAndGet(vulnerabilities);
- }
- counter.total.addAndGet(vulnerabilities);
- if (isDirect) {
- counter.direct.addAndGet(vulnerabilities);
- }
- if (i.getRemediation() != null
- && i.getRemediation().getTrustedContent() != null
- && i.getRemediation().getTrustedContent().getRef() != null) {
- counter.remediations.incrementAndGet();
- }
- });
+ if (item.issues() == null) {
+ return;
+ }
+ item.issues().stream()
+ .filter(Objects::nonNull)
+ .forEach(
+ i -> {
+ var vulnerabilities = countVulnerabilities(i);
+ var severity = i.getSeverity();
+ if (severity != null) {
+ switch (severity) {
+ case CRITICAL -> counter.critical.addAndGet(vulnerabilities);
+ case HIGH -> counter.high.addAndGet(vulnerabilities);
+ case MEDIUM -> counter.medium.addAndGet(vulnerabilities);
+ case LOW -> counter.low.addAndGet(vulnerabilities);
+ }
+ }
+ counter.total.addAndGet(vulnerabilities);
+ if (isDirect) {
+ counter.direct.addAndGet(vulnerabilities);
+ }
+ if (i.getRemediation() != null
+ && i.getRemediation().getTrustedContent() != null
+ && i.getRemediation().getTrustedContent().getRef() != null) {
+ counter.remediations.incrementAndGet();
+ }
+ });
}
// The number of vulnerabilities is the total count of public CVEs
@@ -489,8 +502,7 @@ private static final record VulnerabilityCounter(
AtomicInteger direct,
AtomicInteger dependencies,
AtomicInteger remediations,
- AtomicInteger recommendations,
- AtomicInteger unscanned) {
+ AtomicInteger recommendations) {
VulnerabilityCounter() {
this(
@@ -502,7 +514,6 @@ private static final record VulnerabilityCounter(
new AtomicInteger(),
new AtomicInteger(),
new AtomicInteger(),
- new AtomicInteger(),
new AtomicInteger());
}
@@ -517,9 +528,7 @@ SourceSummary getSummary() {
.transitive(total.get() - direct.get())
.dependencies(dependencies.get())
.remediations(remediations.get())
- // Will be calculated later when TC recommendations will be added.
- .recommendations(recommendations.get())
- .unscanned(unscanned.get());
+ .recommendations(recommendations.get());
}
}
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandler.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandler.java
index 952ab7fa..4e3f76c6 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandler.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandler.java
@@ -42,6 +42,7 @@
import io.github.guacsec.trustifyda.integration.providers.ProviderResponseHandler;
import io.github.guacsec.trustifyda.model.CvssParser;
import io.github.guacsec.trustifyda.model.DependencyTree;
+import io.github.guacsec.trustifyda.model.PackageItem;
import io.github.guacsec.trustifyda.model.ProviderResponse;
import io.quarkus.runtime.annotations.RegisterForReflection;
@@ -66,17 +67,20 @@ public ProviderResponse responseToIssues(
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
throws IOException {
var json = (ObjectNode) mapper.readTree(response);
- return new ProviderResponse(getIssues(json, tree), null, null);
+ return new ProviderResponse(getIssues(json, tree), null);
}
- private Map> getIssues(ObjectNode response, DependencyTree tree) {
+ private Map getIssues(ObjectNode response, DependencyTree tree) {
return tree.getAll().stream()
.map(PackageRef::ref)
.filter(ref -> response.has(ref))
- .collect(Collectors.toMap(ref -> ref, ref -> toIssues(ref, (ArrayNode) response.get(ref))));
+ .collect(
+ Collectors.toMap(
+ ref -> ref,
+ ref -> new PackageItem(ref, null, toIssues((ArrayNode) response.get(ref)))));
}
- private List toIssues(String ref, ArrayNode response) {
+ private List toIssues(ArrayNode response) {
if (response.isEmpty()) {
return Collections.emptyList();
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregation.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregation.java
new file mode 100644
index 00000000..f2eff3c8
--- /dev/null
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregation.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2023-2025 Trustify Dependency Analytics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.guacsec.trustifyda.integration.providers.trustify;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.camel.AggregationStrategy;
+import org.apache.camel.Exchange;
+
+import io.github.guacsec.trustifyda.api.PackageRef;
+import io.github.guacsec.trustifyda.api.v5.Issue;
+import io.github.guacsec.trustifyda.api.v5.Remediation;
+import io.github.guacsec.trustifyda.api.v5.RemediationTrustedContent;
+import io.github.guacsec.trustifyda.model.PackageItem;
+import io.github.guacsec.trustifyda.model.ProviderResponse;
+import io.github.guacsec.trustifyda.model.trustify.IndexedRecommendation;
+import io.github.guacsec.trustifyda.model.trustify.Recommendation;
+
+public class RecommendationAggregation implements AggregationStrategy {
+
+ @Override
+ public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
+ if (oldExchange == null) {
+ return newExchange;
+ }
+ var providerResponse = getProviderResponse(oldExchange, newExchange);
+ var recommendations = getRecommendations(oldExchange, newExchange);
+
+ if (providerResponse.pkgItems() == null) {
+ providerResponse = new ProviderResponse(new HashMap<>(), providerResponse.status());
+ }
+ if (recommendations != null && !recommendations.isEmpty()) {
+ setTrustedContent(recommendations, providerResponse);
+ }
+
+ oldExchange.getIn().setBody(providerResponse);
+ return oldExchange;
+ }
+
+ private ProviderResponse getProviderResponse(Exchange oldExchange, Exchange newExchange) {
+ var body = oldExchange.getIn().getBody();
+ if (body != null && body instanceof ProviderResponse) {
+ return (ProviderResponse) body;
+ }
+ body = newExchange.getIn().getBody();
+ if (body != null && body instanceof ProviderResponse) {
+ return (ProviderResponse) body;
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map getRecommendations(
+ Exchange oldExchange, Exchange newExchange) {
+ var body = oldExchange.getIn().getBody();
+ if (body != null && body instanceof Map) {
+ return (Map) body;
+ }
+ body = newExchange.getIn().getBody();
+ if (body != null && body instanceof Map) {
+ return (Map) body;
+ }
+ return null;
+ }
+
+ private void setTrustedContent(
+ Map recommendations, ProviderResponse providerResponse) {
+ recommendations.forEach(
+ (key, value) -> {
+ var pkgItem = providerResponse.pkgItems().get(key.ref());
+ var recommendation = toRecommendation(value);
+ var issues = new ArrayList();
+ if (pkgItem != null && pkgItem.issues() != null) {
+ issues.addAll(pkgItem.issues());
+ pkgItem
+ .issues()
+ .forEach(
+ issue -> {
+ if (value.vulnerabilities() == null || value.vulnerabilities().isEmpty()) {
+ return;
+ }
+ var vuln = value.vulnerabilities().get(issue.getId());
+ if (vuln == null) {
+ return;
+ }
+ var remediation =
+ new RemediationTrustedContent()
+ .ref(value.packageName())
+ .status(vuln.getStatus())
+ .justification(vuln.getJustification());
+ issue.remediation(new Remediation().trustedContent(remediation));
+ });
+ }
+ providerResponse
+ .pkgItems()
+ .put(key.ref(), new PackageItem(key.ref(), recommendation, issues));
+ });
+ }
+
+ private Recommendation toRecommendation(IndexedRecommendation recommendation) {
+ if (recommendation.vulnerabilities() == null) {
+ return new Recommendation(recommendation.packageName(), Collections.emptyList());
+ }
+ return new Recommendation(
+ recommendation.packageName(), recommendation.vulnerabilities().values().stream().toList());
+ }
+}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyIntegration.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyIntegration.java
index 884a512d..794f1fef 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyIntegration.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyIntegration.java
@@ -17,6 +17,13 @@
package io.github.guacsec.trustifyda.integration.providers.trustify;
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
import org.apache.camel.Exchange;
import org.apache.camel.Message;
import org.apache.camel.builder.AggregationStrategies;
@@ -24,11 +31,19 @@
import org.apache.camel.http.base.HttpOperationFailedException;
import org.jboss.logging.Logger;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.github.guacsec.trustifyda.api.PackageRef;
import io.github.guacsec.trustifyda.integration.Constants;
import io.github.guacsec.trustifyda.integration.providers.VulnerabilityProvider;
+import io.github.guacsec.trustifyda.integration.providers.trustify.ubi.UBIRecommendation;
import io.github.guacsec.trustifyda.model.ProviderHealthCheckResult;
+import io.github.guacsec.trustifyda.model.trustify.IndexedRecommendation;
import io.github.guacsec.trustifyda.model.trustify.ProviderConfig;
import io.github.guacsec.trustifyda.model.trustify.ProvidersConfig;
+import io.github.guacsec.trustifyda.model.trustify.Recommendation;
+import io.github.guacsec.trustifyda.model.trustify.RecommendationsResponse;
+import io.github.guacsec.trustifyda.model.trustify.Vulnerability;
import io.github.guacsec.trustifyda.service.DynamicOidcClientService;
import io.micrometer.core.instrument.MeterRegistry;
@@ -57,8 +72,15 @@ public class TrustifyIntegration extends EndpointRouteBuilder {
@Inject VulnerabilityProvider vulnerabilityProvider;
@Inject TrustifyResponseHandler responseHandler;
@Inject TrustifyRequestBuilder requestBuilder;
+ @Inject ObjectMapper mapper;
+ @Inject UBIRecommendation ubiRecommendation;
@Inject MeterRegistry registry;
+ // Other values are Affected and UnderInvestigation
+ // see https://www.cisa.gov/sites/default/files/2023-01/VEX_Status_Justification_Jun22.pdf
+ private static final List FIXED_STATUSES = List.of("NotAffected", "Fixed");
+ private static final String OCI_PURL_TYPE = "oci";
+
@Override
public void configure() throws Exception {
// fmt:off
@@ -89,17 +111,41 @@ public void configure() throws Exception {
.timeoutEnabled(true)
.timeoutDuration(TIMEOUT_DURATION)
.end()
+
.to(direct("trustifyRequest"))
.onFallback()
.process(responseHandler::processResponseError);
+
+ from(direct("recommendations"))
+ .routeId("recommendations")
+ .routePolicy(new ProviderRoutePolicy(registry))
+ .choice()
+ .when(exchangeProperty(Constants.RECOMMEND_PARAM).isEqualTo(Boolean.TRUE))
+ .process(this::processRecommendationsRequest)
+ .toD("${exchangeProperty.trustifyUrl}")
+ .process(this::processRecommendations)
+ .endChoice()
+
+ .end();
+
+ from(direct("vulnerabilities"))
+ .routeId("vulnerabilities")
+ .routePolicy(new ProviderRoutePolicy(registry))
+ .process(this::processVulnerabilitiesRequest)
+ .toD("${exchangeProperty.trustifyUrl}")
+ .transform(method(responseHandler, "responseToIssues"))
+ .end();
from(direct("trustifyRequest"))
.routeId("trustifyRequest")
- .routePolicy(new ProviderRoutePolicy(registry))
- .process(this::processRequest)
+ .process(this::setProviderConfig)
.process(this::addAuthentication)
- .toD("${exchangeProperty.trustifyUrl}")
- .transform(method(responseHandler, "responseToIssues"));
+ .multicast(new RecommendationAggregation())
+ .stopOnException()
+ .parallelProcessing()
+ .to(direct("vulnerabilities"))
+ .to(direct("recommendations"))
+ .end();
// Generic health check route
from(direct("trustifyHealthCheck"))
@@ -132,7 +178,12 @@ public void configure() throws Exception {
// fmt:on
}
- private void processRequest(Exchange exchange) {
+ private void setProviderConfig(Exchange exchange) {
+ var config = exchange.getProperty(Constants.PROVIDER_CONFIG_PROPERTY, ProviderConfig.class);
+ exchange.setProperty(TRUSTIFY_URL_PROPERTY, config.host());
+ }
+
+ private void processVulnerabilitiesRequest(Exchange exchange) {
Message message = exchange.getMessage();
message.removeHeader(Exchange.HTTP_RAW_QUERY);
message.removeHeader(Exchange.HTTP_QUERY);
@@ -142,8 +193,18 @@ private void processRequest(Exchange exchange) {
message.setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
message.setHeader(Exchange.HTTP_METHOD, HttpMethod.POST);
message.setHeader(Exchange.HTTP_PATH, Constants.TRUSTIFY_ANALYZE_PATH);
- var config = exchange.getProperty(Constants.PROVIDER_CONFIG_PROPERTY, ProviderConfig.class);
- exchange.setProperty(TRUSTIFY_URL_PROPERTY, config.host());
+ }
+
+ private void processRecommendationsRequest(Exchange exchange) {
+ Message message = exchange.getMessage();
+ message.removeHeader(Exchange.HTTP_RAW_QUERY);
+ message.removeHeader(Exchange.HTTP_QUERY);
+ message.removeHeader(Exchange.HTTP_URI);
+ message.removeHeader(Constants.ACCEPT_ENCODING_HEADER);
+
+ message.setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
+ message.setHeader(Exchange.HTTP_METHOD, HttpMethod.POST);
+ message.setHeader(Exchange.HTTP_PATH, Constants.TRUSTIFY_RECOMMEND_PATH);
}
private void processHealthRequest(Exchange exchange) {
@@ -265,4 +326,69 @@ private String getTokenHeader(Exchange exchange) {
}
return null;
}
+
+ /**
+ * Processes recommendations response from Trusted Content API. Converts the JSON response to a
+ * map of PackageRef to IndexedRecommendation.
+ */
+ public void processRecommendations(Exchange exchange) throws IOException {
+ byte[] tcResponse = exchange.getIn().getBody(byte[].class);
+ String sbomId = exchange.getProperty(Constants.SBOM_ID_PROPERTY, String.class);
+
+ var response = mapper.readValue(tcResponse, RecommendationsResponse.class);
+ var mergedRecommendations = indexRecommendations(response);
+ mergedRecommendations.putAll(getUBIRecommendation(sbomId));
+
+ exchange.getMessage().setBody(mergedRecommendations);
+ }
+
+ private Map indexRecommendations(
+ RecommendationsResponse response) {
+ Map result = new HashMap<>();
+ if (response == null) {
+ return result;
+ }
+ response.getMatchings().entrySet().stream()
+ .filter(e -> !e.getValue().isEmpty())
+ .forEach(
+ e -> {
+ List recommendations = e.getValue();
+ PackageRef pkgRef = recommendations.get(0).packageName();
+ Map vulnerabilities =
+ recommendations.stream()
+ .map(Recommendation::vulnerabilities)
+ .flatMap(List::stream)
+ .collect(
+ Collectors.toMap(
+ v -> v.getId().toUpperCase(), v -> v, this::filterFixed));
+ result.put(
+ new PackageRef(e.getKey()), new IndexedRecommendation(pkgRef, vulnerabilities));
+ });
+ return result;
+ }
+
+ private Map getUBIRecommendation(String sbomId) {
+ if (sbomId == null) {
+ return Collections.emptyMap();
+ }
+
+ var pkgRef = new PackageRef(sbomId);
+ if (!OCI_PURL_TYPE.equals(pkgRef.purl().getType())) {
+ return Collections.emptyMap();
+ }
+
+ var recommendedUBIPurl = ubiRecommendation.mapping().get(pkgRef.name());
+ if (recommendedUBIPurl != null) {
+ var recommendation = new IndexedRecommendation(new PackageRef(recommendedUBIPurl), null);
+ return Collections.singletonMap(pkgRef, recommendation);
+ }
+ return Collections.emptyMap();
+ }
+
+ private Vulnerability filterFixed(Vulnerability a, Vulnerability b) {
+ if (a.getStatus() != null && !FIXED_STATUSES.contains(a.getStatus())) {
+ return a;
+ }
+ return b;
+ }
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java
index b6aad8ba..c2f24e3f 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandler.java
@@ -42,6 +42,7 @@
import io.github.guacsec.trustifyda.integration.Constants;
import io.github.guacsec.trustifyda.integration.providers.ProviderResponseHandler;
import io.github.guacsec.trustifyda.model.DependencyTree;
+import io.github.guacsec.trustifyda.model.PackageItem;
import io.github.guacsec.trustifyda.model.ProviderResponse;
import io.github.guacsec.trustifyda.model.trustify.AdvisoryScore;
import io.github.guacsec.trustifyda.model.trustify.ScoreType;
@@ -74,17 +75,20 @@ public ProviderResponse responseToIssues(
@ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree)
throws IOException {
var json = (ObjectNode) mapper.readTree(response);
- return new ProviderResponse(getIssues(json, tree), null, null);
+ return new ProviderResponse(getIssues(json, tree), null);
}
- private Map> getIssues(ObjectNode response, DependencyTree tree) {
+ private Map getIssues(ObjectNode response, DependencyTree tree) {
return tree.getAll().stream()
.map(PackageRef::ref)
.filter(ref -> response.has(ref))
- .collect(Collectors.toMap(ref -> ref, ref -> toIssues(ref, (ArrayNode) response.get(ref))));
+ .collect(
+ Collectors.toMap(
+ ref -> ref,
+ ref -> new PackageItem(ref, null, toIssues((ArrayNode) response.get(ref)))));
}
- private List toIssues(String ref, ArrayNode response) {
+ private List toIssues(ArrayNode response) {
if (response.isEmpty()) {
return Collections.emptyList();
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/ubi/UBIRecommendation.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/ubi/UBIRecommendation.java
similarity index 92%
rename from src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/ubi/UBIRecommendation.java
rename to src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/ubi/UBIRecommendation.java
index 0dc90d54..2580e0d7 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/ubi/UBIRecommendation.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/ubi/UBIRecommendation.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.github.guacsec.trustifyda.integration.trustedcontent.ubi;
+package io.github.guacsec.trustifyda.integration.providers.trustify.ubi;
import java.util.Map;
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/report/BrandingConfig.java b/src/main/java/io/github/guacsec/trustifyda/integration/report/BrandingConfig.java
index 6ffc07bb..51e0ab94 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/report/BrandingConfig.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/report/BrandingConfig.java
@@ -31,5 +31,5 @@ public interface BrandingConfig {
String imageRecommendation();
- String imageRemediationLink();
+ String imageRecommendationLink();
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/report/ReportTemplate.java b/src/main/java/io/github/guacsec/trustifyda/integration/report/ReportTemplate.java
index 96a6c1e5..d7911786 100644
--- a/src/main/java/io/github/guacsec/trustifyda/integration/report/ReportTemplate.java
+++ b/src/main/java/io/github/guacsec/trustifyda/integration/report/ReportTemplate.java
@@ -36,7 +36,7 @@
import com.fasterxml.jackson.databind.ObjectWriter;
import io.github.guacsec.trustifyda.integration.Constants;
-import io.github.guacsec.trustifyda.integration.trustedcontent.ubi.UBIRecommendation;
+import io.github.guacsec.trustifyda.integration.providers.trustify.ubi.UBIRecommendation;
import io.quarkus.runtime.annotations.RegisterForReflection;
import jakarta.enterprise.context.ApplicationScoped;
@@ -145,7 +145,7 @@ private Map getBrandingConfigMap(BrandingConfig config) {
branding.put("exploreTitle", config.exploreTitle());
branding.put("exploreDescription", config.exploreDescription());
branding.put("imageRecommendation", config.imageRecommendation());
- branding.put("imageRemediationLink", config.imageRemediationLink());
+ branding.put("imageRecommendationLink", config.imageRecommendationLink());
return branding;
}
@@ -156,7 +156,7 @@ private static class BrandingConfigImpl implements BrandingConfig {
private final String exploreTitle;
private final String exploreDescription;
private final String imageRecommendation;
- private final String imageRemediationLink;
+ private final String imageRecommendationLink;
public BrandingConfigImpl(
String displayName,
@@ -164,13 +164,13 @@ public BrandingConfigImpl(
String exploreTitle,
String exploreDescription,
String imageRecommendation,
- String imageRemediationLink) {
+ String imageRecommendationLink) {
this.displayName = displayName;
this.exploreUrl = exploreUrl;
this.exploreTitle = exploreTitle;
this.exploreDescription = exploreDescription;
this.imageRecommendation = imageRecommendation;
- this.imageRemediationLink = imageRemediationLink;
+ this.imageRecommendationLink = imageRecommendationLink;
}
@Override
@@ -199,8 +199,8 @@ public String imageRecommendation() {
}
@Override
- public String imageRemediationLink() {
- return imageRemediationLink;
+ public String imageRecommendationLink() {
+ return imageRecommendationLink;
}
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseAggregation.java b/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseAggregation.java
deleted file mode 100644
index fbd991fd..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseAggregation.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.integration.trustedcontent;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-
-import org.apache.camel.AggregationStrategy;
-import org.apache.camel.Exchange;
-import org.apache.camel.ExchangeProperty;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-import io.github.guacsec.trustifyda.api.v5.ProviderStatus;
-import io.github.guacsec.trustifyda.integration.Constants;
-import io.github.guacsec.trustifyda.integration.cache.CacheService;
-import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentCachedRequest;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
-import io.quarkus.runtime.annotations.RegisterForReflection;
-
-import jakarta.inject.Inject;
-import jakarta.inject.Singleton;
-import jakarta.ws.rs.core.Response.Status;
-
-@Singleton
-@RegisterForReflection
-public class TcResponseAggregation implements AggregationStrategy {
-
- @Inject CacheService cacheService;
-
- @Override
- public Exchange aggregate(Exchange oldExchange, Exchange newExchange) {
- oldExchange.setProperty(
- Constants.TRUSTED_CONTENT_PROVIDER,
- newExchange.getIn().getBody(TrustedContentResponse.class));
- return oldExchange;
- }
-
- public TrustedContentResponse aggregateCachedResponse(
- @ExchangeProperty(Constants.CACHED_RECOMMENDATIONS_PROPERTY)
- TrustedContentCachedRequest cached,
- Exchange exchange)
- throws ExecutionException {
-
- var externalResponse = exchange.getIn().getBody(TrustedContentResponse.class);
- if (externalResponse == null) {
- externalResponse =
- new TrustedContentResponse(
- Collections.emptyMap(),
- new ProviderStatus()
- .name(Constants.TRUSTED_CONTENT_PROVIDER)
- .code(Status.OK.getStatusCode())
- .message(Status.OK.getReasonPhrase())
- .ok(Boolean.TRUE));
- }
- cacheService.cacheRecommendations(externalResponse, cached.miss());
- Map recommendations =
- new HashMap<>(externalResponse.recommendations());
- cacheService.cacheRecommendations(externalResponse, cached.miss());
- recommendations.putAll(cached.cached());
- exchange.removeProperty(Constants.CACHED_RECOMMENDATIONS_PROPERTY);
- return new TrustedContentResponse(recommendations, externalResponse.status());
- }
-
- public TrustedContentResponse aggregateEmptyResponse(Exchange exchange) {
- return new TrustedContentResponse(
- new HashMap<>(),
- new ProviderStatus()
- .name(Constants.TRUSTED_CONTENT_PROVIDER)
- .code(Status.OK.getStatusCode())
- .message(Status.OK.getReasonPhrase())
- .ok(Boolean.TRUE));
- }
-}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandler.java b/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandler.java
deleted file mode 100644
index 3c0e9b74..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandler.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.integration.trustedcontent;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-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.ObjectMapper;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-import io.github.guacsec.trustifyda.api.v5.ProviderStatus;
-import io.github.guacsec.trustifyda.integration.Constants;
-import io.github.guacsec.trustifyda.integration.providers.ProviderResponseHandler;
-import io.github.guacsec.trustifyda.integration.trustedcontent.ubi.UBIRecommendation;
-import io.github.guacsec.trustifyda.model.DependencyTree;
-import io.github.guacsec.trustifyda.model.ProviderResponse;
-import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.Recommendations;
-import io.github.guacsec.trustifyda.model.trustedcontent.TcRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
-import io.github.guacsec.trustifyda.model.trustedcontent.Vulnerability;
-import io.quarkus.runtime.annotations.RegisterForReflection;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.core.Response.Status;
-
-@ApplicationScoped
-@RegisterForReflection
-public class TcResponseHandler extends ProviderResponseHandler {
-
- // Other values are Affected and UnderInvestigation
- // see https://www.cisa.gov/sites/default/files/2023-01/VEX_Status_Justification_Jun22.pdf
- private static final List FIXED_STATUSES = List.of("NotAffected", "Fixed");
-
- private static final String OCI_PURL_TYPE = "oci";
-
- @Inject ObjectMapper mapper;
-
- @Inject UBIRecommendation ubiRecommendation;
-
- public TrustedContentResponse mergeSplitRecommendations(
- TrustedContentResponse oldResponse, TrustedContentResponse newResponse) {
- if (oldResponse == null) {
- return newResponse;
- }
- if (!oldResponse.status().getOk()) {
- return oldResponse;
- }
- oldResponse.recommendations().putAll(newResponse.recommendations());
- return oldResponse;
- }
-
- public TrustedContentResponse processRecommendations(
- Exchange exchange,
- @Body byte[] tcResponse,
- @ExchangeProperty(Constants.SBOM_ID_PROPERTY) String sbomId)
- throws IOException {
- var recommendations = mapper.readValue(tcResponse, Recommendations.class);
- var mergedRecommendations = mergeRecommendations(recommendations);
- mergedRecommendations.putAll(getUBIRecommendation(sbomId));
-
- return new TrustedContentResponse(
- mergedRecommendations,
- new ProviderStatus()
- .name(getProviderName(exchange))
- .code(Status.OK.getStatusCode())
- .message(Status.OK.getReasonPhrase())
- .ok(Boolean.TRUE));
- }
-
- private Map mergeRecommendations(
- Recommendations recommendations) {
- Map result = new HashMap<>();
- if (recommendations == null) {
- return result;
- }
- recommendations.getMatchings().entrySet().stream()
- .forEach(
- e -> result.put(new PackageRef(e.getKey()), aggregateRecommendations(e.getValue())));
- return result;
- }
-
- private IndexedRecommendation aggregateRecommendations(List recommendations) {
- PackageRef pkgRef = getHighestRemediationRecommendation(recommendations);
- return new IndexedRecommendation(
- pkgRef,
- recommendations.stream()
- .map(TcRecommendation::vulnerabilities)
- .flatMap(List::stream)
- .collect(Collectors.toMap(v -> v.getId().toUpperCase(), v -> v, this::filterFixed)));
- }
-
- private PackageRef getHighestRemediationRecommendation(List tcRecommendations) {
- return tcRecommendations.stream()
- .sorted(
- (rec1, rec2) ->
- Arrays.compare(
- rec2.packageName().toString().toCharArray(),
- rec1.packageName().toString().toCharArray()))
- .map(recommendedPackage -> recommendedPackage.packageName())
- .findFirst()
- .get();
- }
-
- private Map getUBIRecommendation(String sbomId) {
- if (sbomId == null) {
- return Collections.emptyMap();
- }
-
- var pkgRef = new PackageRef(sbomId);
- if (!OCI_PURL_TYPE.equals(pkgRef.purl().getType())) {
- return Collections.emptyMap();
- }
-
- var recommendedUBIPurl = ubiRecommendation.mapping().get(pkgRef.name());
- if (recommendedUBIPurl != null) {
- var recommendation = new IndexedRecommendation(new PackageRef(recommendedUBIPurl), null);
- return Collections.singletonMap(pkgRef, recommendation);
- }
- return Collections.emptyMap();
- }
-
- @Override
- protected String getProviderName(Exchange exchange) {
- return Constants.TRUSTED_CONTENT_PROVIDER;
- }
-
- @Override
- public void processResponseError(Exchange exchange) {
- super.processResponseError(exchange);
- var response = exchange.getIn().getBody(ProviderResponse.class);
- exchange.getIn().setBody(new TrustedContentResponse(null, response.status()));
- }
-
- @Override
- public ProviderResponse responseToIssues(byte[] response, DependencyTree tree)
- throws IOException {
- throw new UnsupportedOperationException("Not yet implemented");
- }
-
- private Vulnerability filterFixed(Vulnerability a, Vulnerability b) {
- if (a.getStatus() != null && !FIXED_STATUSES.contains(a.getStatus())) {
- return a;
- }
- return b;
- }
-}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentIntegration.java b/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentIntegration.java
deleted file mode 100644
index 9542f05a..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentIntegration.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.integration.trustedcontent;
-
-import org.apache.camel.Exchange;
-import org.apache.camel.builder.AggregationStrategies;
-import org.apache.camel.builder.endpoint.EndpointRouteBuilder;
-import org.eclipse.microprofile.config.inject.ConfigProperty;
-
-import io.github.guacsec.trustifyda.integration.Constants;
-import io.quarkus.runtime.annotations.RegisterForReflection;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-import jakarta.ws.rs.HttpMethod;
-import jakarta.ws.rs.core.MediaType;
-
-@ApplicationScoped
-@RegisterForReflection
-public class TrustedContentIntegration extends EndpointRouteBuilder {
-
- @ConfigProperty(name = "api.trustedcontent.timeout", defaultValue = "60s")
- String timeout;
-
- @Inject TcResponseHandler responseHandler;
-
- @Inject TrustedContentRequestBuilder requestBuilder;
-
- @Inject TcResponseAggregation aggregation;
-
- @Override
- public void configure() {
- // fmt:off
- from(direct("getTrustedContent"))
- .routeId("getTrustedContent")
- .choice().when(exchangeProperty(Constants.RECOMMEND_PARAM).isEqualTo(Boolean.TRUE))
- .setBody(method(requestBuilder, "filterCachedRecommendations"))
- .to(direct("getRemoteTrustedContent"))
- .setBody(method(aggregation, "aggregateCachedResponse"))
- .otherwise()
- .setBody(method(aggregation, "aggregateEmptyResponse"))
- .endChoice();
-
- from(direct("getRemoteTrustedContent"))
- .routeId("getRemoteTrustedContent")
- .transform(method(requestBuilder, "split"))
- .split(body(), AggregationStrategies.bean(TcResponseHandler.class, "mergeSplitRecommendations"))
- .parallelProcessing()
- .transform().method(requestBuilder, "buildRequest")
- .process(this::handleHeaders)
- .circuitBreaker()
- .faultToleranceConfiguration()
- .timeoutEnabled(true)
- .timeoutDuration(timeout)
- .end()
- .to(vertxHttp("{{api.trustedcontent.host}}"))
- .transform(method(TcResponseHandler.class, "processRecommendations"))
- .onFallback()
- .process(responseHandler::processResponseError);
- // fmt:on
- }
-
- private void handleHeaders(Exchange exchange) {
- var message = exchange.getMessage();
- message.removeHeader(Exchange.HTTP_QUERY);
- message.removeHeader(Exchange.HTTP_URI);
-
- message.setHeader(Exchange.HTTP_PATH, Constants.TRUSTED_CONTENT_PATH);
- message.setHeader(Exchange.HTTP_METHOD, HttpMethod.POST);
- message.setHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON);
- }
-}
diff --git a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilder.java b/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilder.java
deleted file mode 100644
index 890bd140..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilder.java
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.integration.trustedcontent;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.camel.Exchange;
-import org.apache.camel.ExchangeProperty;
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-import io.github.guacsec.trustifyda.integration.Constants;
-import io.github.guacsec.trustifyda.integration.cache.CacheService;
-import io.github.guacsec.trustifyda.model.DependencyTree;
-import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentCachedRequest;
-import io.quarkus.runtime.annotations.RegisterForReflection;
-
-import jakarta.enterprise.context.ApplicationScoped;
-import jakarta.inject.Inject;
-
-@ApplicationScoped
-@RegisterForReflection
-public class TrustedContentRequestBuilder {
-
- private static final int BULK_SIZE = 128;
-
- @Inject ObjectMapper mapper;
-
- @Inject CacheService cacheService;
-
- public List> split(Set misses) {
- List> bulks = new ArrayList<>();
- List bulk = new ArrayList<>();
- for (var miss : misses) {
- if (bulk.size() == BULK_SIZE) {
- bulks.add(bulk);
- bulk = new ArrayList<>();
- }
- bulk.add(miss);
- }
- if (!bulk.isEmpty()) {
- bulks.add(bulk);
- }
- if (bulks.isEmpty()) {
- return null;
- }
- return bulks;
- }
-
- public String buildRequest(Set misses) throws JsonProcessingException {
-
- var node = mapper.createArrayNode();
- misses.stream().map(PackageRef::toString).forEach(node::add);
- var obj = mapper.createObjectNode().set("purls", node);
- return mapper.writeValueAsString(obj);
- }
-
- public Set filterCachedRecommendations(
- @ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree,
- Exchange exchange) {
- Set miss = new HashSet<>();
- var allRefs = tree.getAll();
- var cached = cacheService.getRecommendations(allRefs);
-
- Map cachedIdxRecommendations = new HashMap<>();
- allRefs.forEach(
- p -> {
- var cachedReq = cached.get(p);
- if (cachedReq == null) {
- miss.add(p);
- } else if (cachedReq.recommendation() != null) {
- cachedIdxRecommendations.put(p, cachedReq.recommendation());
- }
- });
- var req = new TrustedContentCachedRequest(cachedIdxRecommendations, miss);
- exchange.setProperty(Constants.CACHED_RECOMMENDATIONS_PROPERTY, req);
- return req.miss();
- }
-}
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentCachedRequest.java b/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java
similarity index 69%
rename from src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentCachedRequest.java
rename to src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java
index 92d66be2..60ea3002 100644
--- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentCachedRequest.java
+++ b/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java
@@ -15,12 +15,11 @@
* limitations under the License.
*/
-package io.github.guacsec.trustifyda.model.trustedcontent;
+package io.github.guacsec.trustifyda.model;
-import java.util.Map;
-import java.util.Set;
+import java.util.List;
-import io.github.guacsec.trustifyda.api.PackageRef;
+import io.github.guacsec.trustifyda.api.v5.Issue;
+import io.github.guacsec.trustifyda.model.trustify.Recommendation;
-public record TrustedContentCachedRequest(
- Map cached, Set miss) {}
+public record PackageItem(String packageRef, Recommendation recommendation, List issues) {}
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/ProviderResponse.java b/src/main/java/io/github/guacsec/trustifyda/model/ProviderResponse.java
index 661b3ca2..1197d58f 100644
--- a/src/main/java/io/github/guacsec/trustifyda/model/ProviderResponse.java
+++ b/src/main/java/io/github/guacsec/trustifyda/model/ProviderResponse.java
@@ -17,12 +17,8 @@
package io.github.guacsec.trustifyda.model;
-import java.util.List;
import java.util.Map;
-import io.github.guacsec.trustifyda.api.v5.Issue;
import io.github.guacsec.trustifyda.api.v5.ProviderStatus;
-import io.github.guacsec.trustifyda.api.v5.UnscannedDependency;
-public record ProviderResponse(
- Map> issues, ProviderStatus status, List unscanned) {}
+public record ProviderResponse(Map pkgItems, ProviderStatus status) {}
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/CachedRecommendation.java b/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/CachedRecommendation.java
deleted file mode 100644
index 85a92566..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/CachedRecommendation.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.model.trustedcontent;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-
-public record CachedRecommendation(PackageRef ref, IndexedRecommendation recommendation) {}
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentResponse.java b/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentResponse.java
deleted file mode 100644
index bf266ccb..00000000
--- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentResponse.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.model.trustedcontent;
-
-import java.util.Collections;
-import java.util.Map;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-import io.github.guacsec.trustifyda.api.v5.ProviderStatus;
-
-public record TrustedContentResponse(
- Map recommendations, ProviderStatus status) {
-
- public TrustedContentResponse {
- if (recommendations == null) {
- recommendations = Collections.emptyMap();
- }
- if (status == null) {
- status = new ProviderStatus();
- }
- }
-}
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/IndexedRecommendation.java b/src/main/java/io/github/guacsec/trustifyda/model/trustify/IndexedRecommendation.java
similarity index 96%
rename from src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/IndexedRecommendation.java
rename to src/main/java/io/github/guacsec/trustifyda/model/trustify/IndexedRecommendation.java
index 3aebbc32..94489ec8 100644
--- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/IndexedRecommendation.java
+++ b/src/main/java/io/github/guacsec/trustifyda/model/trustify/IndexedRecommendation.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.github.guacsec.trustifyda.model.trustedcontent;
+package io.github.guacsec.trustifyda.model.trustify;
import java.util.Map;
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TcRecommendation.java b/src/main/java/io/github/guacsec/trustifyda/model/trustify/Recommendation.java
similarity index 87%
rename from src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TcRecommendation.java
rename to src/main/java/io/github/guacsec/trustifyda/model/trustify/Recommendation.java
index c8d34842..551b70c8 100644
--- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TcRecommendation.java
+++ b/src/main/java/io/github/guacsec/trustifyda/model/trustify/Recommendation.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.github.guacsec.trustifyda.model.trustedcontent;
+package io.github.guacsec.trustifyda.model.trustify;
import java.util.List;
@@ -25,7 +25,7 @@
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
-public record TcRecommendation(
+public record Recommendation(
@JsonProperty("package") PackageRef packageName, List vulnerabilities) {
public static Builder builder() {
@@ -47,8 +47,8 @@ public Builder vulnerabilities(List vulnerabilities) {
return this;
}
- public TcRecommendation build() {
- return new TcRecommendation(packageName, vulnerabilities);
+ public Recommendation build() {
+ return new Recommendation(packageName, vulnerabilities);
}
}
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/Recommendations.java b/src/main/java/io/github/guacsec/trustifyda/model/trustify/RecommendationsResponse.java
similarity index 64%
rename from src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/Recommendations.java
rename to src/main/java/io/github/guacsec/trustifyda/model/trustify/RecommendationsResponse.java
index 01b17005..8399ebe3 100644
--- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/Recommendations.java
+++ b/src/main/java/io/github/guacsec/trustifyda/model/trustify/RecommendationsResponse.java
@@ -15,25 +15,27 @@
* limitations under the License.
*/
-package io.github.guacsec.trustifyda.model.trustedcontent;
+package io.github.guacsec.trustifyda.model.trustify;
-import java.util.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.quarkus.runtime.annotations.RegisterForReflection;
@RegisterForReflection
-public record Recommendations(
- @JsonProperty("recommendations") Map> matchings) {
+public record RecommendationsResponse(
+ @JsonProperty("recommendations") Map> matchings) {
- public Recommendations {
+ public RecommendationsResponse {
if (matchings == null) {
matchings = new HashMap<>();
}
}
- public Map> getMatchings() {
+ public Map> getMatchings() {
return matchings;
}
@@ -42,15 +44,15 @@ public static Builder builder() {
}
public static class Builder {
- public Map> matchings;
+ public Map> matchings;
- public Builder matchings(Map> matchings) {
+ public Builder matchings(Map> matchings) {
this.matchings = matchings;
return this;
}
- public Recommendations build() {
- return new Recommendations(this.matchings);
+ public RecommendationsResponse build() {
+ return new RecommendationsResponse(this.matchings);
}
}
}
diff --git a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/Vulnerability.java b/src/main/java/io/github/guacsec/trustifyda/model/trustify/Vulnerability.java
similarity index 96%
rename from src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/Vulnerability.java
rename to src/main/java/io/github/guacsec/trustifyda/model/trustify/Vulnerability.java
index 777df6fa..384c731a 100644
--- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/Vulnerability.java
+++ b/src/main/java/io/github/guacsec/trustifyda/model/trustify/Vulnerability.java
@@ -15,7 +15,7 @@
* limitations under the License.
*/
-package io.github.guacsec.trustifyda.model.trustedcontent;
+package io.github.guacsec.trustifyda.model.trustify;
import io.quarkus.runtime.annotations.RegisterForReflection;
diff --git a/src/main/java/io/github/guacsec/trustifyda/service/DynamicOidcClientService.java b/src/main/java/io/github/guacsec/trustifyda/service/DynamicOidcClientService.java
index d7dfaded..2c749a79 100644
--- a/src/main/java/io/github/guacsec/trustifyda/service/DynamicOidcClientService.java
+++ b/src/main/java/io/github/guacsec/trustifyda/service/DynamicOidcClientService.java
@@ -200,7 +200,7 @@ public boolean validateToken(String providerKey, String token) {
} catch (IOException | InterruptedException e) {
LOGGER.error(
- "Token introspection failed for provider " + providerKey + ": " + e.getMessage());
+ "Token introspection failed for provider " + providerKey + ": " + e.getMessage(), e);
throw new ServerErrorException(
"Token introspection failed for provider " + providerKey + ": " + e.getMessage(),
Response.Status.INTERNAL_SERVER_ERROR.getStatusCode());
diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties
index 9f753c04..754155d0 100644
--- a/src/main/resources/application.properties
+++ b/src/main/resources/application.properties
@@ -1,4 +1,4 @@
-# %dev.quarkus.log.level=DEBUG
+%dev.quarkus.log.level=DEBUG
project.shortname=${pom.artifactId}
project.id=${pom.groupId}:${pom.artifactId}
@@ -6,8 +6,6 @@ project.name=${pom.name}
project.version=${pom.version}
project.build=${timestamp}
-api.trustedcontent.host=https://exhort.trust.rhcloud.com/api/v1/
-
api.onguard.host=http://onguard:8080/
api.onguard.management.host=http://onguard:9000/
@@ -43,7 +41,6 @@ trustedcontent.recommendation.ubi.mapping.debian=${trustedcontent.recommendation
trustedcontent.recommendation.ubi.mapping.fedora=${trustedcontent.recommendation.ubi.purl.ubi9}
trustedcontent.recommendation.ubi.mapping.amazonlinux=${trustedcontent.recommendation.ubi.purl.ubi9}
-%prod.quarkus.redis.hosts=redis://${db.redis.host:localhost}:${db.redis.port:6379}/
%dev.quarkus.keycloak.devservices.enabled=false
# Configure your datasource
diff --git a/src/main/resources/freemarker/templates/generated/main.js b/src/main/resources/freemarker/templates/generated/main.js
index d95fe098..77bfdb03 100644
--- a/src/main/resources/freemarker/templates/generated/main.js
+++ b/src/main/resources/freemarker/templates/generated/main.js
@@ -1 +1 @@
-!function(){"use strict";var e={3992:function(e,n,r){var i=r(1477),t=r(5741),a=(r(9220),r(4685)),o=r(3457),s=r(3710),c=r(3029),l=r(2901),d="maven",u="https://pkg.go.dev/",h="https://www.npmjs.com/package/",g="https://pypi.org/project/",v="__ISSUE_ID__",p="pkg:",x=["oss-index"],f="https://maven.repository.redhat.com/ga/",m=/%[0-9A-Fa-f]{2}/,j=function(e){return"oss-index"===e?"https://ossindex.sonatype.org/user/register":""},y=function(e,n){var r=D.fromString(e),i=function(e){var n="";return e.namespace&&(n=e.type===d?"".concat(e.namespace,":"):"".concat(e.namespace,"/")),n+"".concat(e.name)}(r),t=r.version?decodeURIComponent(r.version):"";return n?i+"@".concat(t):i},I=function(e){var n=D.fromString(e),r=f;if(n.namespace){var i,t=null===(i=n.namespace)||void 0===i?void 0:i.replace(/\./g,"/");return"".concat(f).concat(t,"/").concat(n.name,"/").concat(n.version)}return r},A=function(e){var n=D.fromString(e);switch(n.type){case d:var r=n.version;if(null!==r&&void 0!==r&&r.includes("redhat")){var i,t=null===(i=n.namespace)||void 0===i?void 0:i.replace(/\./g,"/");return"".concat(f).concat(t,"/").concat(n.name,"/").concat(n.version)}return"".concat("https://central.sonatype.com/artifact/").concat(n.namespace,"/").concat(n.name,"/").concat(n.version);case"golang":var a=n.version;return null!==a&&void 0!==a&&a.match(/v\d\.\d.\d-\d{14}-\w{12}/)?"".concat(u).concat(n.namespace,"/").concat(n.name):"".concat(u).concat(n.namespace,"/").concat(n.name,"@").concat(n.version);case"npm":return n.namespace?"".concat(h).concat(n.namespace,"/").concat(n.name,"/v/").concat(n.version):"".concat(h).concat(n.name,"/v/").concat(n.version);case"pypi":return n.namespace?"".concat(g).concat(n.namespace,"/").concat(n.name,"/").concat(n.version):"".concat(g).concat(n.name,"/").concat(n.version);case"deb":return"".concat("https://sources.debian.org/patches/").concat(n.name,"/").concat(n.version);default:return n.toString()}},b=function(e){var n=D.fromString(e).version;return n?decodeURIComponent(n):""},C=function(e){return e.toLowerCase().replace(/./,function(e){return e.toUpperCase()})},w=function(e){var n=M(e),r="";if(n.repository_url){var i=n.repository_url.indexOf("/");r+=-1!==i?n.repository_url.substring(i+1):""}else r+="".concat(n.short_name);return n.tag&&(r+=":".concat(n.tag)),r},M=function(e){var n=e.split("?"),r=n[0],i=n[1],t=new URLSearchParams(i),a=t.get("repository_url")||"",o=t.get("tag")||"",s=t.get("arch")||"",c=r.split("@");return{repository_url:a,tag:o,short_name:c[0].substring(c[0].indexOf("/")+1),version:r.substring(r.lastIndexOf("@")).replace("%3A",":"),arch:s}},T=function(e,n,r,i){var t=S(n),a=i||"";for(var o in t){var s=t[o].report.dependencies;if(s){var c=Object.values(s).find(function(n){var r,i=n.ref,t=decodeURIComponent(i),a=(r=e,m.test(r)?decodeURIComponent(e):e);return D.fromString(t).toString()===D.fromString(a).toString()});if(c&&c.recommendation&&a){var l=decodeURIComponent(c.recommendation);if(void 0!==N(l,r))return a+w(l)}}}return a+"search"},N=function(e,n){var r=JSON.parse(n).find(function(n){return D.fromString(n.purl).toString()===D.fromString(e).toString()});return null===r||void 0===r?void 0:r.catalogUrl},D=function(){function e(n,r,i,t){(0,c.A)(this,e),this.type=void 0,this.namespace=void 0,this.name=void 0,this.version=void 0,this.type=n,this.namespace=r,this.name=i,this.version=t}return(0,l.A)(e,[{key:"toString",value:function(){var e=this.name;return this.version&&(e+="@".concat(this.version)),this.namespace?"".concat(p).concat(this.type,"/").concat(this.namespace,"/").concat(e):"".concat(p).concat(this.type,"/").concat(e)}}],[{key:"fromString",value:function(n){var r=n.replace(p,""),i=r.indexOf("?");-1!==i&&(r=r.substring(0,i));var t,a,o=r.substring(0,r.indexOf("/")),s=r.split("/");s.length>2&&(t=s.slice(1,s.length-1).join("/")),-1!==r.indexOf("@")&&(a=r.substring(r.indexOf("@")+1));var c=s[s.length-1];return a&&(c=c.substring(0,c.indexOf("@"))),new e(o,t,c,a)}}])}();function S(e){var n=[];return Object.keys(e.providers).forEach(function(r){var i=e.providers[r].sources;void 0!==i&&Object.keys(i).length>0?Object.keys(i).forEach(function(e){n.push({provider:r,source:e,report:i[e]})}):"trusted-content"!==r&&n.push({provider:r,source:r,report:{}})}),n.sort(function(e,n){return 0===Object.keys(e.report).length&&0===Object.keys(n.report).length?""===j(e.provider)?""===j(n.provider)?0:-1:1:Object.keys(n.report).length-Object.keys(e.report).length})}function O(e){var n,r;if(!e||!e.provider)return"unknown";var i=null!==(n=e.provider)&&void 0!==n?n:"unknown";return i===(null!==(r=e.source)&&void 0!==r?r:"unknown")?i:"".concat(e.provider,"/").concat(e.source)}function P(e){var n;return!(!e.remediation||!(e.remediation.fixedIn||null!==(n=e.remediation)&&void 0!==n&&n.trustedContent))}function E(e){var n=[];return e.map(function(e){return{dependencyRef:e.ref,vulnerabilities:e.issues||[]}}).forEach(function(e){var r;null===(r=e.vulnerabilities)||void 0===r||r.forEach(function(r){r.cves&&r.cves.length>0?r.cves.forEach(function(i){n.push({id:i,dependencyRef:e.dependencyRef,vulnerability:r})}):n.push({id:r.id,dependencyRef:e.dependencyRef,vulnerability:r})})}),n.sort(function(e,n){return n.vulnerability.cvssScore-e.vulnerability.cvssScore})}var k=r(9292),z=r(6180),F=r(9341),L=r(3844),B=r(2881),Z=r(4900),R=r(3144),V=r(2092),U=r(5767),Q=r(4646),G=r(260),W=r(1352),H=r(3571),Y=r(628),K=r(2972),X=r(8056),q=r(9922),J=r(481),_=["#800000","#FF0000","#FFA500","#5BA352"],$=function(e){var n,r,i,t,a,o=e.summary,s=null!==(n=null===o||void 0===o?void 0:o.critical)&&void 0!==n?n:0,c=null!==(r=null===o||void 0===o?void 0:o.high)&&void 0!==r?r:0,l=null!==(i=null===o||void 0===o?void 0:o.medium)&&void 0!==i?i:0,d=null!==(t=null===o||void 0===o?void 0:o.low)&&void 0!==t?t:0,u=null!==(a=null===o||void 0===o?void 0:o.total)&&void 0!==a?a:0,h=s+c+l+d>0,g=h?_:["#D5F5E3"],v=[{name:"Critical: ".concat(s),symbol:{type:"square",fill:_[0]}},{name:"High: ".concat(c),symbol:{type:"square",fill:_[1]}},{name:"Medium: ".concat(l),symbol:{type:"square",fill:_[2]}},{name:"Low: ".concat(d),symbol:{type:"square",fill:_[3]}}];return(0,J.jsx)("div",{children:(0,J.jsx)(V.b,{style:{paddingBottom:"inherit",padding:"0"},children:(0,J.jsx)(X.a,{children:(0,J.jsx)("div",{style:{height:"230px",width:"350px"},children:(0,J.jsx)(q.H,{constrainToVisibleArea:!0,data:h?[{x:"Critical",y:s},{x:"High",y:c},{x:"Medium",y:l},{x:"Low",y:d}]:[{x:"Empty",y:1e-10}],labels:function(e){var n=e.datum;return h?"".concat(n.x,": ").concat(n.y):"No vulnerabilities"},legendData:v,legendOrientation:"vertical",legendPosition:"right",padding:{left:20,right:140},subTitle:"Unique vulnerabilities",title:"".concat(u),width:350,colorScale:g})})})})})},ee="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTJweCIgaGVpZ2h0PSIxM3B4IiB2aWV3Qm94PSIwIDAgMTIgMTMiIGlkPSJTZWN1cml0eUNoZWNrSWNvbiIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KICAgIDx0aXRsZT5Db21iaW5lZCBTaGFwZTwvdGl0bGU+CiAgICA8ZyBpZD0iTXVsdGktdmVuZG9yIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iT3ZlcnZpZXctQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEyMDcsIC05OTMpIiBmaWxsPSIjM0U4NjM1Ij4KICAgICAgICAgICAgPGcgaWQ9IkRldGFpbHMtb2YtZGVwZW5kZW5jeS1jb20uZ2l0aHViIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MjcsIDgxOSkiPgogICAgICAgICAgICAgICAgPGcgaWQ9IkRlcGVuZGVuY3ktMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMTQ0KSI+CiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc4MC4xNzI4LCAyNCkiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMy4yKSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iSWNvbnMvMi4tU2l6ZS1zbS9BY3Rpb25zL2NoZWNrIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAyLjgpIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTAuNTU2NTc4OSwwIEMxMC43OTA2MjQ5LDAgMTAuOTc5MzMyMiwwLjE4MTU0Mjk2OSAxMC45NzkzMzIyLDAuNDA2MjUgTDEwLjk3OTMzMjIsNS43NDA4MjAzMSBDMTAuOTc5MzMyMiw5Ljc1IDYuMjQwODE5MDcsMTMgNS40OTU3OTI5NiwxMyBDNC43NTA3NjY4NCwxMyAwLDkuNzUgMCw1LjczOTU1MDc4IEwwLDAuNDA2MjUgQzAsMC4xODE1NDI5NjkgMC4xODg3MDcyNzIsMCAwLjQyMjc1MzMwNCwwIFogTTguNTQyNzc4ODMsMy4xMTc4MjY2NyBMNC43OTEyOTYxLDYuODkwODczNTMgTDMuMDM5ODEzMzgsNS4xMjkzMjQ0IEMyLjg4MzYwOSw0Ljk3MjIwNjgzIDIuNjMwMzI4MTIsNC45NzIyMDY4MyAyLjQ3NDEyMzc1LDUuMTI5MzI0NCBMMS45MDg0NDkzOCw1LjY5ODI2NTU2IEMxLjc1MjI0NTAxLDUuODU1MzgzMTIgMS43NTIyNDUwMSw2LjExMDEwNDQ5IDEuOTA4NDQ5MzgsNi4yNjcyMDY3MSBMNC41MDg0NTc5Nyw4Ljg4MjE1OTkxIEM0LjY2NDY0NzA4LDkuMDM5Mjc3NDcgNC45MTc5MTI3LDkuMDM5Mjc3NDcgNS4wNzQxMzIzMyw4Ljg4MjE3NTI1IEw5LjY3NDE0MjgyLDQuMjU1NzA4OTggQzkuODMwMzQ3Miw0LjA5ODU5MTQxIDkuODMwMzQ3MiwzLjg0Mzg3MDA0IDkuNjc0MTQyODIsMy42ODY3Njc4MiBMOS4xMDg0Njg0NiwzLjExNzgyNjY3IEM4Ljk1MjI2NDA4LDIuOTYwNzI0NDQgOC42OTg5ODMyLDIuOTYwNzI0NDQgOC41NDI3Nzg4MywzLjExNzgyNjY3IFoiIGlkPSJDb21iaW5lZC1TaGFwZSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4=",ne={width:"16px",height:"16px",verticalAlign:"middle"},re=function(e){var n,r=e.report,i=e.isReportMap,t=e.purl,a=xn(),c=(n=a.report.providers)&&"object"===typeof n&&"rhtpa"in n,l=a.brandingConfig||{displayName:"Trustify",exploreUrl:"https://guac.sh/trustify/",exploreTitle:"Learn more about Trustify",exploreDescription:"The Trustify project is a collection of software components that enables you to store and retrieve Software Bill of Materials (SBOMs), and advisory documents.",imageRecommendation:"",imageRemediationLink:""};return(0,J.jsxs)(o.x,{hasGutter:!0,children:[(0,J.jsxs)(k.h,{headingLevel:"h3",size:k.J["2xl"],style:{paddingLeft:"15px"},children:[(0,J.jsx)(z.I,{isInline:!0,status:"info",children:(0,J.jsx)(K.Ay,{style:{fill:"#f0ab00"}})}),"\xa0",l.displayName," Overview of security issues"]}),(0,J.jsx)(F.c,{}),(0,J.jsx)(s.E,{children:(0,J.jsxs)(L.Z,{isFlat:!0,isFullHeight:!0,children:[(0,J.jsx)(B.a,{children:(0,J.jsx)(Z.Z,{children:(0,J.jsx)(R.X,{style:{fontSize:"large"},children:i?(0,J.jsxs)(J.Fragment,{children:[t?w(t):"No Image name"," - Vendor Issues"]}):(0,J.jsx)(J.Fragment,{children:"Vendor Issues"})})})}),(0,J.jsxs)(V.b,{children:[(0,J.jsx)(U.W,{children:(0,J.jsx)(Q.d,{children:(0,J.jsx)(R.X,{children:"Below is a list of dependencies affected with CVE."})})}),(0,J.jsx)(G.B,{isAutoFit:!0,style:{paddingTop:"10px"},children:S(r).map(function(e,n){return(0,J.jsxs)(U.W,{style:{display:"flex",flexDirection:"column",alignItems:"center"},children:[(0,J.jsx)(J.Fragment,{children:(0,J.jsx)(R.X,{style:{fontSize:"large"},children:O(e)})}),(0,J.jsx)(Q.d,{children:(0,J.jsx)($,{summary:e.report.summary})})]},n)})})]}),(0,J.jsx)(F.c,{})]})}),(!i||l.imageRecommendation.trim()&&l.imageRemediationLink.trim())&&(0,J.jsxs)(s.E,{md:c?6:void 0,children:[(0,J.jsx)(L.Z,{isFlat:!0,children:(0,J.jsxs)(U.W,{children:[(0,J.jsx)(Z.Z,{component:"h4",children:(0,J.jsxs)(R.X,{style:{fontSize:"large"},children:[(0,J.jsx)("img",{src:"data:image/png;base64,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",alt:"Trustify Icon",style:ne}),"\xa0",l.displayName," Remediations"]})}),(0,J.jsx)(V.b,{children:(0,J.jsx)(Q.d,{children:i?(0,J.jsxs)(W.B8,{isPlain:!0,children:[(0,J.jsx)(H.c,{children:l.imageRecommendation}),(0,J.jsx)(H.c,{children:(0,J.jsx)("a",{href:t?T(t,r,a.imageMapping,l.imageRemediationLink):"###",target:"_blank",rel:"noreferrer",children:(0,J.jsx)(Y.$n,{variant:"primary",size:"sm",children:"Take me there"})})})]}):(0,J.jsx)(W.B8,{isPlain:!0,children:S(r).map(function(e,n){var r=e&&e.source&&e.provider?e.source===e.provider?e.provider:"".concat(e.provider,"/").concat(e.source):"default_value";return Object.keys(e.report).length>0?(0,J.jsxs)(H.c,{children:[(0,J.jsx)(z.I,{isInline:!0,status:"success",children:(0,J.jsx)("img",{src:ee,alt:"Security Check Icon"})}),"\xa0",e.report.summary.remediations," remediations are available for ",r]}):(0,J.jsxs)(H.c,{children:[(0,J.jsx)(z.I,{isInline:!0,status:"success",children:(0,J.jsx)("img",{src:ee,alt:"Security Check Icon"})}),"\xa0 There are no available remediations for your SBOM at this time for ",e.provider]})})})})})]})}),"\xa0"]}),c&&(0,J.jsxs)(s.E,{md:6,children:[(0,J.jsx)(L.Z,{isFlat:!0,children:(0,J.jsxs)(U.W,{children:[(0,J.jsx)(Z.Z,{component:"h4",children:(0,J.jsx)(R.X,{style:{fontSize:"large"},children:l.exploreTitle})}),(0,J.jsx)(V.b,{children:(0,J.jsx)(Q.d,{children:(0,J.jsxs)(W.B8,{isPlain:!0,children:[(0,J.jsx)(H.c,{children:l.exploreDescription}),(0,J.jsx)(H.c,{children:(0,J.jsx)("a",{href:l.exploreUrl,target:"_blank",rel:"noopener noreferrer",children:(0,J.jsx)(Y.$n,{variant:"primary",size:"sm",children:"Take me there"})})})]})})})]})}),"\xa0"]})]})},ie=r(8132),te=function(e){var n=e.report,r=Object.keys(n.providers).map(function(e){return n.providers[e].status}).filter(function(e){return!e.ok&&!(!(n=e).ok&&401===n.code&&"Unauthenticated"===n.message&&x.includes(n.name));var n});return(0,J.jsx)(J.Fragment,{children:r.map(function(e,n){return"trusted-content"===e.name?(0,J.jsx)(ie.F,{variant:ie.w.info,title:"".concat(C(e.name),": Recommendations and remediations are not currently available")},n):(0,J.jsx)(ie.F,{variant:e.code>=500?ie.w.danger:e.code>=400?ie.w.warning:void 0,title:"".concat(C(e.name),": ").concat(e.message)},n)})})},ae=r(1212),oe=r(467),se=r(296),ce=r(6334),le=r(4248),de=r(4471),ue=r(9379),he=r(1792),ge=r(3093),ve=r(297),pe=r(4072),xe=r(8380),fe=r(1417),me=r(7066),je=r(2227),ye=r(99),Ie=r(7172),Ae=r(8516),be=r(8579),Ce=r(9205),we=r(8099),Me=r(4785),Te=r(4224),Ne=r(5772),De=r(4593),Se=r(5458),Oe=r(4546),Pe=function(e){return e[e.SET_PAGE=0]="SET_PAGE",e[e.SET_SORT_BY=1]="SET_SORT_BY",e}(Pe||{}),Ee={changed:!1,currentPage:{page:1,perPage:10},sortBy:void 0},ke=function(e,n){switch(n.type){case Pe.SET_PAGE:var r=n.payload;return(0,ue.A)((0,ue.A)({},e),{},{changed:!0,currentPage:{page:r.page,perPage:r.perPage}});case Pe.SET_SORT_BY:var i=n.payload;return(0,ue.A)((0,ue.A)({},e),{},{changed:!0,sortBy:{index:i.index,direction:i.direction}});default:return e}},ze=r(2514),Fe=r(3842),Le=function(e){var n,r=e.count,i=e.params,t=e.isTop,a=e.perPageOptions,o=e.onChange,s=function(){return i.perPage||10};return(0,J.jsx)(ze.d,{itemCount:r,page:i.page||1,perPage:s(),onPageInput:function(e,n){o({page:n,perPage:s()})},onSetPage:function(e,n){o({page:n,perPage:s()})},onPerPageSelect:function(e,n){o({page:1,perPage:n})},widgetId:"pagination-options-menu",variant:t?ze.A.top:ze.A.bottom,perPageOptions:(n=a||[10,20,50,100],n.map(function(e){return{title:String(e),value:e}})),toggleTemplate:function(e){return(0,J.jsx)(Fe.D,(0,ue.A)({},e))}})},Be=function(e){var n=e.name,r=e.showVersion,i=void 0!==r&&r;return(0,J.jsx)(J.Fragment,{children:(0,J.jsx)("a",{href:A(n),target:"_blank",rel:"noreferrer",children:y(n,i)})})},Ze=r(9694),Re=r(6911),Ve=r(6464),Ue=r(1413),Qe=function(e){var n=e.numRenderedColumns,r=e.isLoading,i=void 0!==r&&r,t=e.isError,a=void 0!==t&&t,o=e.isNoData,s=void 0!==o&&o,c=e.errorEmptyState,l=void 0===c?null:c,d=e.noDataEmptyState,u=void 0===d?null:d,h=e.children,g=(0,J.jsxs)(he.p,{variant:he.s.sm,children:[(0,J.jsx)(ve.q,{icon:Ve.Ay,color:Ue.D.value}),(0,J.jsx)(k.h,{headingLevel:"h2",size:"lg",children:"Unable to connect"}),(0,J.jsx)(pe.h,{children:"There was an error retrieving data. Check your connection and try again."})]}),v=(0,J.jsxs)(he.p,{variant:he.s.sm,children:[(0,J.jsx)(ve.q,{icon:Re.Ay}),(0,J.jsx)(k.h,{headingLevel:"h2",size:"lg",children:"No data available"}),(0,J.jsx)(pe.h,{children:"No data available to be shown here."})]});return(0,J.jsx)(J.Fragment,{children:i?(0,J.jsx)(Me.N,{children:(0,J.jsx)(Ce.Tr,{children:(0,J.jsx)(Te.Td,{colSpan:n,children:(0,J.jsx)(X.a,{children:(0,J.jsx)(Ze.y,{size:"xl"})})})})}):a?(0,J.jsx)(Me.N,{"aria-label":"Table error",children:(0,J.jsx)(Ce.Tr,{children:(0,J.jsx)(Te.Td,{colSpan:n,children:(0,J.jsx)(X.a,{children:l||g})})})}):s?(0,J.jsx)(Me.N,{"aria-label":"Table no data",children:(0,J.jsx)(Ce.Tr,{children:(0,J.jsx)(Te.Td,{colSpan:n,children:(0,J.jsx)(X.a,{children:u||v})})})}):h})},Ge=function(e){var n=e.packageName;e.cves;return(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(z.I,{isInline:!0,status:"success",children:(0,J.jsx)("img",{src:ee,alt:"Security Check Icon"})}),"\xa0",(0,J.jsx)("a",{href:I(n),target:"_blank",rel:"noreferrer",children:b(n)})]})},We=function(e){e.sourceName;var n,r,i,t,a,o=e.vulnerability,s=xn();return(0,J.jsx)(J.Fragment,{children:null===(null===(n=o.remediation)||void 0===n?void 0:n.fixedIn)||0===(null===(r=o.remediation)||void 0===r||null===(i=r.fixedIn)||void 0===i?void 0:i.length)?(0,J.jsx)("p",{}):(0,J.jsx)("a",{href:(t=o.id,a=s,a.nvdIssueTemplate.replace(v,t)),target:"_blank",rel:"noreferrer",children:o.id})})},He=r(8559),Ye=r(7540),Ke=r(8762),Xe=r(8642),qe=function(e){var n,r=e.vulnerability;switch(r.severity){case"CRITICAL":n="bar-critical";break;case"HIGH":n="bar-high";break;case"MEDIUM":n="bar-medium";break;case"LOW":n="bar-low";break;default:n="bar-default"}return(0,J.jsx)(J.Fragment,{children:(0,J.jsx)(He.B,{hasGutter:!0,children:(0,J.jsx)(Ye.o,{isFilled:!0,children:(0,J.jsx)(Ke.k,{title:"".concat(r.cvssScore,"/10"),"aria-label":"cvss-score",value:r.cvssScore,min:0,max:10,size:Ke.j.sm,measureLocation:Xe.Ri.none,className:"".concat(n)})})})})},Je=r(4102),_e=function(e){var n,r=e.vulnerability;switch(r.severity){case"CRITICAL":n="#800000";break;case"HIGH":n="#FF0000";break;case"MEDIUM":n="#FFA500";break;case"LOW":n="#5BA352";break;default:n="grey"}return(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(z.I,{isInline:!0,children:(0,J.jsx)(Je.Ay,{style:{fill:n,height:"13px"}})}),"\xa0",C(r.severity)]})},$e=function(e){var n,r,i=e.id,t=xn();return(0,J.jsx)("a",{href:(n=i,r=t,r.cveIssueTemplate.replace(v,n)),target:"_blank",rel:"noreferrer",children:i})},en=r(4486),nn=function(e){var n=e.title,r=i.useState(!1),t=(0,se.A)(r,2),a=t[0],o=t[1];return(0,J.jsx)(en.Q,{variant:en.J.truncate,toggleText:a?"Show less":"Show more",onToggle:function(e,n){o(n)},isExpanded:a,children:n||"-"})},rn=function(e){var n,r,i,t,a,o=e.item,s=e.providerName,c=e.rowIndex;return a=o.vulnerability.cves&&o.vulnerability.cves.length>0?o.vulnerability.cves:[o.vulnerability.id],(0,J.jsxs)(Ce.Tr,{children:[(0,J.jsx)(Te.Td,{children:a.map(function(e,n){return(0,J.jsx)("p",{children:(0,J.jsx)($e,{id:e})},n)})}),(0,J.jsx)(Te.Td,{children:(0,J.jsx)(nn,{title:o.vulnerability.title})}),(0,J.jsx)(Te.Td,{noPadding:!0,children:(0,J.jsx)(_e,{vulnerability:o.vulnerability})}),(0,J.jsx)(Te.Td,{children:(0,J.jsx)(qe,{vulnerability:o.vulnerability})}),(0,J.jsx)(Te.Td,{children:(0,J.jsx)(Be,{name:o.dependencyRef,showVersion:!0})}),(0,J.jsx)(Te.Td,{children:null!==(n=o.vulnerability.remediation)&&void 0!==n&&n.trustedContent?(0,J.jsx)(Ge,{cves:o.vulnerability.cves||[],packageName:null===(r=o.vulnerability.remediation)||void 0===r||null===(i=r.trustedContent)||void 0===i?void 0:i.ref},c):null!==(t=o.vulnerability.remediation)&&void 0!==t&&t.fixedIn?(0,J.jsx)(We,{sourceName:s,vulnerability:o.vulnerability}):P(o.vulnerability)?null:(0,J.jsx)("span",{})})]},c)},tn=function(e){var n=e.providerName,r=e.transitiveDependencies;return(0,J.jsx)(L.Z,{style:{backgroundColor:"var(--pf-v5-global--BackgroundColor--100)"},children:(0,J.jsxs)(Ie.X,{variant:Ae.a.compact,"aria-label":(null!==n&&void 0!==n?n:"Default")+" transitive vulnerabilities",children:[(0,J.jsx)(be.d,{children:(0,J.jsxs)(Ce.Tr,{children:[(0,J.jsx)(we.Th,{width:15,children:"Vulnerability ID"}),(0,J.jsx)(we.Th,{width:20,children:"Description"}),(0,J.jsx)(we.Th,{width:10,children:"Severity"}),(0,J.jsx)(we.Th,{width:15,children:"CVSS Score"}),(0,J.jsx)(we.Th,{width:20,children:"Transitive Dependency"}),(0,J.jsx)(we.Th,{width:20,children:"Remediation"})]})}),(0,J.jsx)(Qe,{isNoData:0===r.length,numRenderedColumns:7,children:E(r).map(function(e,r){return(0,J.jsx)(Me.N,{children:(0,J.jsx)(rn,{item:e,providerName:n,rowIndex:r})},r)})})]})})},an=function(e){var n=e.providerName,r=e.dependency,i=e.vulnerabilities;return(0,J.jsx)(L.Z,{style:{backgroundColor:"var(--pf-v5-global--BackgroundColor--100)"},children:(0,J.jsxs)(Ie.X,{variant:Ae.a.compact,"aria-label":(null!==n&&void 0!==n?n:"Default")+" direct vulnerabilities",children:[(0,J.jsx)(be.d,{children:(0,J.jsxs)(Ce.Tr,{children:[(0,J.jsx)(we.Th,{width:15,children:"Vulnerability ID"}),(0,J.jsx)(we.Th,{width:20,children:"Description"}),(0,J.jsx)(we.Th,{width:10,children:"Severity"}),(0,J.jsx)(we.Th,{width:15,children:"CVSS Score"}),(0,J.jsx)(we.Th,{width:20,children:"Direct Dependency"}),(0,J.jsx)(we.Th,{width:20,children:"Remediation"})]})}),(0,J.jsx)(Qe,{isNoData:0===i.length,numRenderedColumns:6,children:null===i||void 0===i?void 0:i.map(function(e,i){var t=[];return e.cves&&e.cves.length>0?e.cves.forEach(function(e){return t.push(e)}):e.unique&&t.push(e.id),(0,J.jsx)(Me.N,{children:t.map(function(t,a){return(0,J.jsx)(rn,{item:{id:e.id,dependencyRef:r.ref,vulnerability:e},providerName:n,rowIndex:i},"".concat(i,"-").concat(a))})},i)})})]})})},on=r(4388),sn=function(e){var n=e.vulnerabilities,r=void 0===n?[]:n,i=e.transitiveDependencies,t=void 0===i?[]:i,a={CRITICAL:0,HIGH:0,MEDIUM:0,LOW:0};return r.length>0?r.forEach(function(e){var n=e.severity;a.hasOwnProperty(n)&&a[n]++}):null===t||void 0===t||t.forEach(function(e){var n;null===(n=e.issues)||void 0===n||n.forEach(function(e){var n=e.severity;a.hasOwnProperty(n)&&a[n]++})}),(0,J.jsxs)(on.Z,{children:[a.CRITICAL>0&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(z.I,{isInline:!0,children:(0,J.jsx)(Je.Ay,{style:{fill:"#800000",height:"13px"}})}),"\xa0",a.CRITICAL,"\xa0"]}),a.HIGH>0&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(z.I,{isInline:!0,children:(0,J.jsx)(Je.Ay,{style:{fill:"#FF0000",height:"13px"}})}),"\xa0",a.HIGH,"\xa0"]}),a.MEDIUM>0&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(z.I,{isInline:!0,children:(0,J.jsx)(Je.Ay,{style:{fill:"#FFA500",height:"13px"}})}),"\xa0",a.MEDIUM,"\xa0"]}),a.LOW>0&&(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(z.I,{isInline:!0,children:(0,J.jsx)(Je.Ay,{style:{fill:"#5BA352",height:"13px"}})}),"\xa0",a.LOW]})]})},cn=r(8480),ln=function(e){var n,r,i=e.dependency,t=null===(n=i.issues)||void 0===n?void 0:n.some(function(e){return P(e)}),a=(null===(r=i.transitive)||void 0===r?void 0:r.some(function(e){var n;return null===(n=e.issues)||void 0===n?void 0:n.some(function(e){return P(e)})}))||!1;return(0,J.jsx)(J.Fragment,{children:t||a?"Yes":"No"})},dn=function(e){var n=e.name,r=e.dependencies,t=(0,i.useState)(""),a=(0,se.A)(t,2),o=a[0],s=a[1],c=function(e){var n=(0,i.useReducer)(ke,(0,ue.A)((0,ue.A)({},Ee),{},{currentPage:e&&e.page?(0,ue.A)({},e.page):(0,ue.A)({},Ee.currentPage),sortBy:e&&e.sortBy?(0,ue.A)({},e.sortBy):Ee.sortBy})),r=(0,se.A)(n,2),t=r[0],a=r[1],o=(0,i.useCallback)(function(e){var n;a({type:Pe.SET_PAGE,payload:{page:e.page>=1?e.page:1,perPage:null!==(n=e.perPage)&&void 0!==n?n:Ee.currentPage.perPage}})},[]),s=(0,i.useCallback)(function(e,n,r,i){a({type:Pe.SET_SORT_BY,payload:{index:n,direction:r}})},[]);return{page:t.currentPage,sortBy:t.sortBy,changePage:o,changeSortBy:s}}(),l=c.page,d=c.sortBy,u=c.changePage,h=c.changeSortBy,g=function(e){var n=e.items,r=e.currentSortBy,t=e.currentPage,a=e.filterItem,o=e.compareToByColumn;return(0,i.useMemo)(function(){var e,i=(0,Se.A)(n||[]).filter(a),s=!1;return e=(0,Se.A)(i).sort(function(e,n){var i=o(e,n,null===r||void 0===r?void 0:r.index);return 0!==i&&(s=!0),i}),s&&(null===r||void 0===r?void 0:r.direction)===Oe.l.desc&&(e=e.reverse()),{pageItems:e.slice((t.page-1)*t.perPage,t.page*t.perPage),filteredItems:i}},[n,t,r,o,a])}({items:r,currentPage:l,currentSortBy:d,compareToByColumn:function(e,n,r){return 1===r?e.ref.localeCompare(n.ref):0},filterItem:function(e){var n=!0;return o&&o.trim().length>0&&(n=-1!==e.ref.toLowerCase().indexOf(o.toLowerCase())),n}}),v=g.pageItems,p=g.filteredItems,x={name:"Dependency Name",version:"Current Version",direct:"Direct Vulnerabilities",transitive:"Transitive Vulnerabilities",rhRemediation:"Remediation available"},f=i.useState({"siemur/test-space":"name"}),m=(0,se.A)(f,2),y=m[0],I=m[1],A=function(e,n,r,i){return{isExpanded:y[e.ref]===n,onToggle:function(){return function(e,n){var r=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],i=(0,ue.A)({},y);r?i[e.ref]=n:delete i[e.ref],I(i)}(e,n,y[e.ref]!==n)},expandId:"compound-expandable-example",rowIndex:r,columnIndex:i}};return(0,J.jsx)(L.Z,{children:(0,J.jsx)(V.b,{children:(0,J.jsx)("div",{style:{backgroundColor:"var(--pf-v5-global--BackgroundColor--100)"},children:""!==j(n)&&void 0===r?(0,J.jsx)("div",{children:(0,J.jsxs)(he.p,{variant:he.s.sm,children:[(0,J.jsx)(ge.o,{icon:(0,J.jsx)(ve.q,{icon:Re.Ay}),titleText:"Set up "+n,headingLevel:"h2"}),(0,J.jsxs)(pe.h,{children:["You need to provide a valid credentials to see ",n," data. You can use the button below to sing-up for ",n,". If you have already signed up, enter your credentials in your extension settings and then regenerate the Dependency Analytics report."]}),(0,J.jsx)("br",{}),(0,J.jsx)("br",{}),(0,J.jsx)("a",{href:j(n),target:"_blank",rel:"noopener noreferrer",children:(0,J.jsxs)(Y.$n,{variant:"primary",size:"sm",children:["Sign up for ",n]})})]})}):(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(xe.M,{children:(0,J.jsxs)(fe.P,{children:[(0,J.jsx)(me.h,{toggleIcon:(0,J.jsx)(De.Ay,{}),breakpoint:"xl",children:(0,J.jsx)(je.T,{variant:"search-filter",children:(0,J.jsx)(ye.D,{id:n+"-dependency-filter",style:{width:"250px"},placeholder:"Filter by Dependency name",value:o,onChange:function(e,n){return s(n)},onClear:function(){return s("")}})})}),(0,J.jsx)(je.T,{variant:je.U.pagination,align:{default:"alignRight"},children:(0,J.jsx)(Le,{isTop:!0,count:p.length,params:l,onChange:u})})]})}),(0,J.jsxs)(Ie.X,{"aria-label":(null!==n&&void 0!==n?n:"Default")+" dependencies",variant:Ae.a.compact,children:[(0,J.jsx)(be.d,{children:(0,J.jsxs)(Ce.Tr,{children:[(0,J.jsx)(we.Th,{width:25,sort:{columnIndex:1,sortBy:(0,ue.A)({},d),onSort:h},children:x.name}),(0,J.jsx)(we.Th,{children:x.version}),(0,J.jsx)(we.Th,{children:x.direct}),(0,J.jsx)(we.Th,{children:x.transitive}),(0,J.jsx)(we.Th,{children:x.rhRemediation})]})}),(0,J.jsx)(Qe,{isNoData:0===p.length,numRenderedColumns:8,noDataEmptyState:(0,J.jsxs)(he.p,{variant:he.s.sm,children:[(0,J.jsx)(ge.o,{icon:(0,J.jsx)(ve.q,{icon:cn.Ay}),titleText:"No results found",headingLevel:"h2"}),(0,J.jsx)(pe.h,{children:"Clear all filters and try again."})]}),children:null===v||void 0===v?void 0:v.map(function(e,r){var i,t,a,o,s,c=y[e.ref],l=!!c;return null!==(i=e.issues)&&void 0!==i&&i.length||null!==(t=e.transitive)&&void 0!==t&&t.length?(0,J.jsxs)(Me.N,{isExpanded:l,children:[(0,J.jsxs)(Ce.Tr,{children:[(0,J.jsx)(Te.Td,{width:30,dataLabel:x.name,component:"th",children:(0,J.jsx)(Be,{name:e.ref})}),(0,J.jsx)(Te.Td,{width:15,dataLabel:x.version,children:b(e.ref)}),(0,J.jsx)(Te.Td,{width:15,dataLabel:x.direct,compoundExpand:A(e,"direct",r,2),children:null!==(a=e.issues)&&void 0!==a&&a.length?(0,J.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[(0,J.jsx)("div",{style:{width:"25px"},children:null===(o=e.issues)||void 0===o?void 0:o.length}),(0,J.jsx)(F.c,{orientation:{default:"vertical"},style:{paddingRight:"10px"}}),(0,J.jsx)(sn,{vulnerabilities:e.issues})]}):0}),(0,J.jsx)(Te.Td,{width:15,dataLabel:x.transitive,compoundExpand:A(e,"transitive",r,3),children:null!==(s=e.transitive)&&void 0!==s&&s.length?(0,J.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[(0,J.jsx)("div",{style:{width:"25px"},children:e.transitive.map(function(e){var n;return null===(n=e.issues)||void 0===n?void 0:n.length}).reduce(function(){return(arguments.length>0&&void 0!==arguments[0]?arguments[0]:0)+(arguments.length>1&&void 0!==arguments[1]?arguments[1]:0)})}),(0,J.jsx)(F.c,{orientation:{default:"vertical"},style:{paddingRight:"10px"}}),(0,J.jsx)(sn,{transitiveDependencies:e.transitive})]}):0}),(0,J.jsx)(Te.Td,{width:15,dataLabel:x.rhRemediation,children:(0,J.jsx)(ln,{dependency:e})})]}),l?(0,J.jsx)(Ce.Tr,{isExpanded:l,children:(0,J.jsx)(Te.Td,{dataLabel:x[c],noPadding:!0,colSpan:6,children:(0,J.jsx)(Ne.g,{children:(0,J.jsx)("div",{className:"pf-v5-u-m-md",children:"direct"===c&&e.issues&&e.issues.length>0?(0,J.jsx)(an,{providerName:n,dependency:e,vulnerabilities:e.issues}):"transitive"===c&&e.transitive&&e.transitive.length>0?(0,J.jsx)(tn,{providerName:n,transitiveDependencies:e.transitive}):null})})})}):null]},e.ref):null})})]}),(0,J.jsx)(Le,{isTop:!1,count:p.length,params:l,onChange:u})]})})})})},un=r(1157),hn=function(e){var n=e.report,r=xn(),t=S(n),o=i.useState(O(t[0])),s=(0,se.A)(o,2),c=s[0],l=s[1],d=i.useState(!0),u=(0,se.A)(d,1)[0],h=r.writeKey&&""!==r.writeKey.trim()?un.N.load({writeKey:r.writeKey}):null,g=(0,i.useRef)(""),v=(0,i.useRef)(!1);(0,i.useEffect)(function(){h&&(null!=r.anonymousId&&h.setAnonymousId(r.anonymousId),v.current=!0)},[]),(0,i.useEffect)(function(){if(h){var e=function(){var e=(0,oe.A)((0,ae.A)().m(function e(n){return(0,ae.A)().w(function(e){for(;;)switch(e.n){case 0:n!==g.current&&(h.track("rhda.exhort.tab",{tabName:n}),g.current=n);case 1:return e.a(2)}},e)}));return function(n){return e.apply(this,arguments)}}();e(c)}},[c,h]);var p=t.map(function(e){var n,r=O(e),i=null===(n=e.report.dependencies)||void 0===n?void 0:n.filter(function(e){return e.highestVulnerability});return(0,J.jsx)(ce.o,{eventKey:r,title:(0,J.jsx)(le.V,{children:r}),"aria-label":"".concat(r," source"),children:(0,J.jsx)(a.d8,{variant:a.zC.default,children:(0,J.jsx)(dn,{name:r,dependencies:i})})})});return(0,J.jsx)("div",{children:(0,J.jsx)(de.t,{activeKey:c,onSelect:function(e,n){l(n)},"aria-label":"Providers",role:"region",variant:u?"light300":"default",isBox:!0,children:p})})},gn=function(e){var n=e.report,r=i.useState(Object.keys(n)[0]||""),t=(0,se.A)(r,2),c=t[0],l=t[1],d=i.useState(!0),u=(0,se.A)(d,1)[0],h=Object.entries(n).map(function(e){var n=(0,se.A)(e,2),r=n[0],i=n[1];return(0,J.jsxs)(ce.o,{eventKey:r,title:(0,J.jsx)(le.V,{children:w(r)}),"aria-label":"".concat(r," source"),children:[(0,J.jsx)(te,{report:i}),(0,J.jsx)(a.d8,{variant:a.zC.light,children:(0,J.jsx)(o.x,{hasGutter:!0,children:(0,J.jsx)(s.E,{children:(0,J.jsx)(re,{report:i,isReportMap:!0,purl:r})})})}),(0,J.jsx)(a.d8,{variant:a.zC.default,children:(0,J.jsx)(hn,{report:i})})]})});return(0,J.jsx)("div",{children:(0,J.jsx)(de.t,{activeKey:c,onSelect:function(e,n){l(n)},"aria-label":"Providers",role:"region",variant:u?"light300":"default",isBox:!0,children:h})})},vn=window.appData,pn=(0,i.createContext)(vn),xn=function(){return(0,i.useContext)(pn)};var fn=function(){return(0,J.jsx)(pn.Provider,{value:vn,children:(e=vn.report,"object"===typeof e&&null!==e&&Object.keys(e).every(function(n){return"scanned"in e[n]&&"providers"in e[n]&&"object"===typeof e[n].scanned&&"object"===typeof e[n].providers})?(0,J.jsx)(a.d8,{variant:a.zC.default,children:(0,J.jsx)(gn,{report:vn.report})}):(0,J.jsxs)(J.Fragment,{children:[(0,J.jsx)(te,{report:vn.report}),(0,J.jsx)(a.d8,{variant:a.zC.light,children:(0,J.jsx)(o.x,{hasGutter:!0,children:(0,J.jsx)(s.E,{children:(0,J.jsx)(re,{report:vn.report})})})}),(0,J.jsx)(a.d8,{variant:a.zC.default,children:(0,J.jsx)(hn,{report:vn.report})})]}))});var e},mn=function(e){e&&e instanceof Function&&r.e(121).then(r.bind(r,6895)).then(function(n){var r=n.getCLS,i=n.getFID,t=n.getFCP,a=n.getLCP,o=n.getTTFB;r(e),i(e),t(e),a(e),o(e)})};t.createRoot(document.getElementById("root")).render((0,J.jsx)(i.StrictMode,{children:(0,J.jsx)(fn,{})})),mn()}},n={};function r(i){var t=n[i];if(void 0!==t)return t.exports;var a=n[i]={id:i,loaded:!1,exports:{}};return e[i].call(a.exports,a,a.exports,r),a.loaded=!0,a.exports}r.m=e,function(){var e=[];r.O=function(n,i,t,a){if(!i){var o=1/0;for(d=0;d=a)&&Object.keys(r.O).every(function(e){return r.O[e](i[c])})?i.splice(c--,1):(s=!1,a0&&e[d-1][2]>a;d--)e[d]=e[d-1];e[d]=[i,t,a]}}(),r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,{a:n}),n},function(){var e,n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__};r.t=function(i,t){if(1&t&&(i=this(i)),8&t)return i;if("object"===typeof i&&i){if(4&t&&i.__esModule)return i;if(16&t&&"function"===typeof i.then)return i}var a=Object.create(null);r.r(a);var o={};e=e||[null,n({}),n([]),n(n)];for(var s=2&t&&i;("object"==typeof s||"function"==typeof s)&&!~e.indexOf(s);s=n(s))Object.getOwnPropertyNames(s).forEach(function(e){o[e]=function(){return i[e]}});return o.default=function(){return i},r.d(a,o),a}}(),r.d=function(e,n){for(var i in n)r.o(n,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:n[i]})},r.e=function(){return Promise.resolve()},r.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}(),r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},function(){var e={792:0};r.O.j=function(n){return 0===e[n]};var n=function(n,i){var t,a,o=i[0],s=i[1],c=i[2],l=0;if(o.some(function(n){return 0!==e[n]})){for(t in s)r.o(s,t)&&(r.m[t]=s[t]);if(c)var d=c(r)}for(n&&n(i);l2&&(o=l.slice(1,l.length-1).join("/")),-1!==t.indexOf("@")&&(s=t.substring(t.indexOf("@")+1));var d=l[l.length-1];return s&&(d=d.substring(0,d.indexOf("@"))),new e(c,o,d,s,new Map((null===(r=i)||void 0===r?void 0:r.split("&").map(function(e){return e.split("=")}))||[]))}}])}();function O(e){var n=[];return Object.keys(e.providers).forEach(function(r){var i=e.providers[r].sources;void 0!==i&&Object.keys(i).length>0?Object.keys(i).forEach(function(e){n.push({provider:r,source:e,report:i[e]})}):"trusted-content"!==r&&n.push({provider:r,source:r,report:{}})}),n.sort(function(e,n){return 0===Object.keys(e.report).length&&0===Object.keys(n.report).length?""===y(e.provider)?""===y(n.provider)?0:-1:1:Object.keys(n.report).length-Object.keys(e.report).length})}function P(e){var n,r;if(!e||!e.provider)return"unknown";var i=null!==(n=e.provider)&&void 0!==n?n:"unknown";return i===(null!==(r=e.source)&&void 0!==r?r:"unknown")?i:"".concat(e.provider,"/").concat(e.source)}function E(e){var n;return!(!e.remediation||!(e.remediation.fixedIn||null!==(n=e.remediation)&&void 0!==n&&n.trustedContent))}function k(e){var n=[];return e.map(function(e){return{dependencyRef:e.ref,vulnerabilities:e.issues||[]}}).forEach(function(e){var r;null===(r=e.vulnerabilities)||void 0===r||r.forEach(function(r){r.cves&&r.cves.length>0?r.cves.forEach(function(i){n.push({id:i,dependencyRef:e.dependencyRef,vulnerability:r})}):n.push({id:r.id,dependencyRef:e.dependencyRef,vulnerability:r})})}),n.sort(function(e,n){return n.vulnerability.cvssScore-e.vulnerability.cvssScore})}var z=r(9292),F=r(6180),L=r(9341),Z=r(3844),B=r(2881),R=r(4900),V=r(3144),U=r(2092),Q=r(5767),G=r(4646),W=r(260),H=r(1352),Y=r(3571),K=r(628),X=r(2972),q=r(8056),J=r(9922),_=r(481),$=["#800000","#FF0000","#FFA500","#5BA352"],ee=function(e){var n,r,i,t,a,o=e.summary,s=null!==(n=null===o||void 0===o?void 0:o.critical)&&void 0!==n?n:0,c=null!==(r=null===o||void 0===o?void 0:o.high)&&void 0!==r?r:0,l=null!==(i=null===o||void 0===o?void 0:o.medium)&&void 0!==i?i:0,d=null!==(t=null===o||void 0===o?void 0:o.low)&&void 0!==t?t:0,u=null!==(a=null===o||void 0===o?void 0:o.total)&&void 0!==a?a:0,h=s+c+l+d>0,g=h?$:["#D5F5E3"],v=[{name:"Critical: ".concat(s),symbol:{type:"square",fill:$[0]}},{name:"High: ".concat(c),symbol:{type:"square",fill:$[1]}},{name:"Medium: ".concat(l),symbol:{type:"square",fill:$[2]}},{name:"Low: ".concat(d),symbol:{type:"square",fill:$[3]}}];return(0,_.jsx)("div",{children:(0,_.jsx)(U.b,{style:{paddingBottom:"inherit",padding:"0"},children:(0,_.jsx)(q.a,{children:(0,_.jsx)("div",{style:{height:"230px",width:"350px"},children:(0,_.jsx)(J.H,{constrainToVisibleArea:!0,data:h?[{x:"Critical",y:s},{x:"High",y:c},{x:"Medium",y:l},{x:"Low",y:d}]:[{x:"Empty",y:1e-10}],labels:function(e){var n=e.datum;return h?"".concat(n.x,": ").concat(n.y):"No vulnerabilities"},legendData:v,legendOrientation:"vertical",legendPosition:"right",padding:{left:20,right:140},subTitle:"Unique vulnerabilities",title:"".concat(u),width:350,colorScale:g})})})})})},ne="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTJweCIgaGVpZ2h0PSIxM3B4IiB2aWV3Qm94PSIwIDAgMTIgMTMiIGlkPSJTZWN1cml0eUNoZWNrSWNvbiIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KICAgIDx0aXRsZT5Db21iaW5lZCBTaGFwZTwvdGl0bGU+CiAgICA8ZyBpZD0iTXVsdGktdmVuZG9yIiBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj4KICAgICAgICA8ZyBpZD0iT3ZlcnZpZXctQ29weSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoLTEyMDcsIC05OTMpIiBmaWxsPSIjM0U4NjM1Ij4KICAgICAgICAgICAgPGcgaWQ9IkRldGFpbHMtb2YtZGVwZW5kZW5jeS1jb20uZ2l0aHViIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0MjcsIDgxOSkiPgogICAgICAgICAgICAgICAgPGcgaWQ9IkRlcGVuZGVuY3ktMSIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMTQ0KSI+CiAgICAgICAgICAgICAgICAgICAgPGcgaWQ9Ikdyb3VwLTkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDc4MC4xNzI4LCAyNCkiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iR3JvdXAtNCIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoMCwgMy4yKSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZyBpZD0iSWNvbnMvMi4tU2l6ZS1zbS9BY3Rpb25zL2NoZWNrIiB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLCAyLjgpIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTAuNTU2NTc4OSwwIEMxMC43OTA2MjQ5LDAgMTAuOTc5MzMyMiwwLjE4MTU0Mjk2OSAxMC45NzkzMzIyLDAuNDA2MjUgTDEwLjk3OTMzMjIsNS43NDA4MjAzMSBDMTAuOTc5MzMyMiw5Ljc1IDYuMjQwODE5MDcsMTMgNS40OTU3OTI5NiwxMyBDNC43NTA3NjY4NCwxMyAwLDkuNzUgMCw1LjczOTU1MDc4IEwwLDAuNDA2MjUgQzAsMC4xODE1NDI5NjkgMC4xODg3MDcyNzIsMCAwLjQyMjc1MzMwNCwwIFogTTguNTQyNzc4ODMsMy4xMTc4MjY2NyBMNC43OTEyOTYxLDYuODkwODczNTMgTDMuMDM5ODEzMzgsNS4xMjkzMjQ0IEMyLjg4MzYwOSw0Ljk3MjIwNjgzIDIuNjMwMzI4MTIsNC45NzIyMDY4MyAyLjQ3NDEyMzc1LDUuMTI5MzI0NCBMMS45MDg0NDkzOCw1LjY5ODI2NTU2IEMxLjc1MjI0NTAxLDUuODU1MzgzMTIgMS43NTIyNDUwMSw2LjExMDEwNDQ5IDEuOTA4NDQ5MzgsNi4yNjcyMDY3MSBMNC41MDg0NTc5Nyw4Ljg4MjE1OTkxIEM0LjY2NDY0NzA4LDkuMDM5Mjc3NDcgNC45MTc5MTI3LDkuMDM5Mjc3NDcgNS4wNzQxMzIzMyw4Ljg4MjE3NTI1IEw5LjY3NDE0MjgyLDQuMjU1NzA4OTggQzkuODMwMzQ3Miw0LjA5ODU5MTQxIDkuODMwMzQ3MiwzLjg0Mzg3MDA0IDkuNjc0MTQyODIsMy42ODY3Njc4MiBMOS4xMDg0Njg0NiwzLjExNzgyNjY3IEM4Ljk1MjI2NDA4LDIuOTYwNzI0NDQgOC42OTg5ODMyLDIuOTYwNzI0NDQgOC41NDI3Nzg4MywzLjExNzgyNjY3IFoiIGlkPSJDb21iaW5lZC1TaGFwZSI+PC9wYXRoPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgICAgICAgICA8L2c+CiAgICAgICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICAgICAgPC9nPgogICAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgPC9nPgo8L3N2Zz4=",re={width:"16px",height:"16px",verticalAlign:"middle"},ie=function(e){var n=e.report,r=e.isReportMap,i=e.purl,t=xn(),a=t.brandingConfig||{displayName:"Trustify",exploreUrl:"https://guac.sh/trustify/",exploreTitle:"Learn more about Trustify",exploreDescription:"The Trustify project is a collection of software components that enables you to store and retrieve Software Bill of Materials (SBOMs), and advisory documents.",imageRecommendation:"",imageRecommendationLink:""},c=a.exploreTitle.trim()&&a.exploreUrl.trim()&&a.exploreDescription.trim(),l=r&&a.imageRecommendation.trim()&&a.imageRecommendationLink.trim(),d=function(){return(0,_.jsx)("img",{src:"data:image/png;base64,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",alt:"Trustify Icon",style:re})},u=12/(1+Number(c)+Number(l));return(0,_.jsxs)(o.x,{hasGutter:!0,children:[(0,_.jsxs)(z.h,{headingLevel:"h3",size:z.J["2xl"],style:{paddingLeft:"15px"},children:[(0,_.jsx)(F.I,{isInline:!0,status:"info",children:(0,_.jsx)(X.Ay,{style:{fill:"#f0ab00"}})}),"\xa0",a.displayName," overview of security issues"]}),(0,_.jsx)(L.c,{}),(0,_.jsx)(s.E,{children:(0,_.jsxs)(Z.Z,{isFlat:!0,isFullHeight:!0,children:[(0,_.jsx)(B.a,{children:(0,_.jsx)(R.Z,{children:(0,_.jsx)(V.X,{style:{fontSize:"large"},children:r?(0,_.jsxs)(_.Fragment,{children:[i?M(i):"No Image name"," - Vendor Issues"]}):(0,_.jsx)(_.Fragment,{children:"Vendor Issues"})})})}),(0,_.jsxs)(U.b,{children:[(0,_.jsx)(Q.W,{children:(0,_.jsx)(G.d,{children:(0,_.jsx)(V.X,{children:"Below is a list of dependencies affected with CVE."})})}),(0,_.jsx)(W.B,{isAutoFit:!0,style:{paddingTop:"10px"},children:O(n).map(function(e,n){return(0,_.jsxs)(Q.W,{style:{display:"flex",flexDirection:"column",alignItems:"center"},children:[(0,_.jsx)(_.Fragment,{children:(0,_.jsx)(V.X,{style:{fontSize:"large"},children:P(e)})}),(0,_.jsx)(G.d,{children:(0,_.jsx)(ee,{summary:e.report.summary})})]},n)})})]}),(0,_.jsx)(L.c,{})]})}),(0,_.jsxs)(s.E,{md:u,children:[(0,_.jsx)(Z.Z,{isFlat:!0,children:(0,_.jsxs)(Q.W,{children:[(0,_.jsx)(R.Z,{component:"h4",children:(0,_.jsxs)(V.X,{style:{fontSize:"large"},children:[d(),"\xa0",a.displayName," Dependency Remediations"]})}),(0,_.jsx)(U.b,{children:(0,_.jsx)(G.d,{children:(0,_.jsx)(H.B8,{isPlain:!0,children:O(n).map(function(e,n){var r=e&&e.source&&e.provider?e.source===e.provider?e.provider:"".concat(e.provider,"/").concat(e.source):"default_value";return Object.keys(e.report).length>0?(0,_.jsxs)(Y.c,{children:[(0,_.jsx)(F.I,{isInline:!0,status:"success",children:(0,_.jsx)("img",{src:ne,alt:"Security Check Icon"})}),"\xa0",e.report.summary.remediations," remediations are available for ",r]}):(0,_.jsxs)(Y.c,{children:[(0,_.jsx)(F.I,{isInline:!0,status:"success",children:(0,_.jsx)("img",{src:ne,alt:"Security Check Icon"})}),"\xa0 There are no available remediations for your SBOM at this time for ",e.provider]})})})})})]})}),"\xa0"]}),l&&(0,_.jsxs)(s.E,{md:u,children:[(0,_.jsx)(Z.Z,{isFlat:!0,children:(0,_.jsxs)(Q.W,{children:[(0,_.jsx)(R.Z,{component:"h4",children:(0,_.jsxs)(V.X,{style:{fontSize:"large"},children:[d(),"\xa0",a.displayName," Container Recommendations"]})}),(0,_.jsx)(U.b,{children:(0,_.jsx)(G.d,{children:(0,_.jsxs)(H.B8,{isPlain:!0,children:[(0,_.jsx)(Y.c,{children:a.imageRecommendation}),(0,_.jsx)(Y.c,{children:(0,_.jsx)("a",{href:i?N(i,n,t.imageMapping,a.imageRecommendationLink):"###",target:"_blank",rel:"noreferrer",children:(0,_.jsx)(K.$n,{variant:"primary",size:"sm",children:"Take me there"})})})]})})})]})}),"\xa0"]}),c&&(0,_.jsxs)(s.E,{md:u,children:[(0,_.jsx)(Z.Z,{isFlat:!0,children:(0,_.jsxs)(Q.W,{children:[(0,_.jsx)(R.Z,{component:"h4",children:(0,_.jsx)(V.X,{style:{fontSize:"large"},children:a.exploreTitle})}),(0,_.jsx)(U.b,{children:(0,_.jsx)(G.d,{children:(0,_.jsxs)(H.B8,{isPlain:!0,children:[(0,_.jsx)(Y.c,{children:a.exploreDescription}),(0,_.jsx)(Y.c,{children:(0,_.jsx)("a",{href:a.exploreUrl,target:"_blank",rel:"noopener noreferrer",children:(0,_.jsx)(K.$n,{variant:"primary",size:"sm",children:"Take me there"})})})]})})})]})}),"\xa0"]})]})},te=r(8132),ae=function(e){var n=e.report,r=Object.keys(n.providers).map(function(e){return n.providers[e].status}).filter(function(e){return!e.ok&&!(!(n=e).ok&&401===n.code&&"Unauthenticated"===n.message&&m.includes(n.name));var n});return(0,_.jsx)(_.Fragment,{children:r.map(function(e,n){return"trusted-content"===e.name?(0,_.jsx)(te.F,{variant:te.w.info,title:"".concat(w(e.name),": Recommendations and remediations are not currently available")},n):(0,_.jsx)(te.F,{variant:e.code>=500?te.w.danger:e.code>=400?te.w.warning:void 0,title:"".concat(w(e.name),": ").concat(e.message)},n)})})},oe=r(1212),se=r(467),ce=r(6334),le=r(4248),de=r(4471),ue=r(9379),he=r(1792),ge=r(3093),ve=r(297),pe=r(4072),xe=r(8380),fe=r(1417),me=r(7066),je=r(2227),ye=r(99),Ie=r(7172),Ae=r(8516),be=r(8579),Ce=r(9205),we=r(8099),Me=r(4785),Te=r(4224),Ne=r(5772),De=r(4593),Se=r(5458),Oe=r(4546),Pe=function(e){return e[e.SET_PAGE=0]="SET_PAGE",e[e.SET_SORT_BY=1]="SET_SORT_BY",e}(Pe||{}),Ee={changed:!1,currentPage:{page:1,perPage:10},sortBy:void 0},ke=function(e,n){switch(n.type){case Pe.SET_PAGE:var r=n.payload;return(0,ue.A)((0,ue.A)({},e),{},{changed:!0,currentPage:{page:r.page,perPage:r.perPage}});case Pe.SET_SORT_BY:var i=n.payload;return(0,ue.A)((0,ue.A)({},e),{},{changed:!0,sortBy:{index:i.index,direction:i.direction}});default:return e}},ze=r(2514),Fe=r(3842),Le=function(e){var n,r=e.count,i=e.params,t=e.isTop,a=e.perPageOptions,o=e.onChange,s=function(){return i.perPage||10};return(0,_.jsx)(ze.d,{itemCount:r,page:i.page||1,perPage:s(),onPageInput:function(e,n){o({page:n,perPage:s()})},onSetPage:function(e,n){o({page:n,perPage:s()})},onPerPageSelect:function(e,n){o({page:1,perPage:n})},widgetId:"pagination-options-menu",variant:t?ze.A.top:ze.A.bottom,perPageOptions:(n=a||[10,20,50,100],n.map(function(e){return{title:String(e),value:e}})),toggleTemplate:function(e){return(0,_.jsx)(Fe.D,(0,ue.A)({},e))}})},Ze=function(e){var n=e.name,r=e.showVersion,i=void 0!==r&&r;return(0,_.jsx)(_.Fragment,{children:(0,_.jsx)("a",{href:b(n),target:"_blank",rel:"noreferrer",children:I(n,i)})})},Be=r(9694),Re=r(6911),Ve=r(6464),Ue=r(1413),Qe=function(e){var n=e.numRenderedColumns,r=e.isLoading,i=void 0!==r&&r,t=e.isError,a=void 0!==t&&t,o=e.isNoData,s=void 0!==o&&o,c=e.errorEmptyState,l=void 0===c?null:c,d=e.noDataEmptyState,u=void 0===d?null:d,h=e.children,g=(0,_.jsxs)(he.p,{variant:he.s.sm,children:[(0,_.jsx)(ve.q,{icon:Ve.Ay,color:Ue.D.value}),(0,_.jsx)(z.h,{headingLevel:"h2",size:"lg",children:"Unable to connect"}),(0,_.jsx)(pe.h,{children:"There was an error retrieving data. Check your connection and try again."})]}),v=(0,_.jsxs)(he.p,{variant:he.s.sm,children:[(0,_.jsx)(ve.q,{icon:Re.Ay}),(0,_.jsx)(z.h,{headingLevel:"h2",size:"lg",children:"No data available"}),(0,_.jsx)(pe.h,{children:"No data available to be shown here."})]});return(0,_.jsx)(_.Fragment,{children:i?(0,_.jsx)(Me.N,{children:(0,_.jsx)(Ce.Tr,{children:(0,_.jsx)(Te.Td,{colSpan:n,children:(0,_.jsx)(q.a,{children:(0,_.jsx)(Be.y,{size:"xl"})})})})}):a?(0,_.jsx)(Me.N,{"aria-label":"Table error",children:(0,_.jsx)(Ce.Tr,{children:(0,_.jsx)(Te.Td,{colSpan:n,children:(0,_.jsx)(q.a,{children:l||g})})})}):s?(0,_.jsx)(Me.N,{"aria-label":"Table no data",children:(0,_.jsx)(Ce.Tr,{children:(0,_.jsx)(Te.Td,{colSpan:n,children:(0,_.jsx)(q.a,{children:u||v})})})}):h})},Ge=function(e){var n=e.packageName;e.cves;return(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(F.I,{isInline:!0,status:"success",children:(0,_.jsx)("img",{src:ne,alt:"Security Check Icon"})}),"\xa0",(0,_.jsx)("a",{href:A(n),target:"_blank",rel:"noreferrer",children:C(n)})]})},We=function(e){e.sourceName;var n,r,i,t,a,o=e.vulnerability,s=xn();return(0,_.jsx)(_.Fragment,{children:null===(null===(n=o.remediation)||void 0===n?void 0:n.fixedIn)||0===(null===(r=o.remediation)||void 0===r||null===(i=r.fixedIn)||void 0===i?void 0:i.length)?(0,_.jsx)("p",{}):(0,_.jsx)("a",{href:(t=o.id,a=s,a.nvdIssueTemplate.replace(x,t)),target:"_blank",rel:"noreferrer",children:o.id})})},He=r(8559),Ye=r(7540),Ke=r(8762),Xe=r(8642),qe=function(e){var n,r=e.vulnerability;switch(r.severity){case"CRITICAL":n="bar-critical";break;case"HIGH":n="bar-high";break;case"MEDIUM":n="bar-medium";break;case"LOW":n="bar-low";break;default:n="bar-default"}return(0,_.jsx)(_.Fragment,{children:(0,_.jsx)(He.B,{hasGutter:!0,children:(0,_.jsx)(Ye.o,{isFilled:!0,children:(0,_.jsx)(Ke.k,{title:"".concat(r.cvssScore,"/10"),"aria-label":"cvss-score",value:r.cvssScore,min:0,max:10,size:Ke.j.sm,measureLocation:Xe.Ri.none,className:"".concat(n)})})})})},Je=r(4102),_e=function(e){var n,r=e.vulnerability;switch(r.severity){case"CRITICAL":n="#800000";break;case"HIGH":n="#FF0000";break;case"MEDIUM":n="#FFA500";break;case"LOW":n="#5BA352";break;default:n="grey"}return(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(F.I,{isInline:!0,children:(0,_.jsx)(Je.Ay,{style:{fill:n,height:"13px"}})}),"\xa0",w(r.severity)]})},$e=function(e){var n,r,i=e.id,t=xn();return(0,_.jsx)("a",{href:(n=i,r=t,r.cveIssueTemplate.replace(x,n)),target:"_blank",rel:"noreferrer",children:i})},en=r(4486),nn=function(e){var n=e.title,r=i.useState(!1),t=(0,c.A)(r,2),a=t[0],o=t[1];return(0,_.jsx)(en.Q,{variant:en.J.truncate,toggleText:a?"Show less":"Show more",onToggle:function(e,n){o(n)},isExpanded:a,children:n||"-"})},rn=function(e){var n,r,i,t,a,o=e.item,s=e.providerName,c=e.rowIndex;return a=o.vulnerability.cves&&o.vulnerability.cves.length>0?o.vulnerability.cves:[o.vulnerability.id],(0,_.jsxs)(Ce.Tr,{children:[(0,_.jsx)(Te.Td,{children:a.map(function(e,n){return(0,_.jsx)("p",{children:(0,_.jsx)($e,{id:e})},n)})}),(0,_.jsx)(Te.Td,{children:(0,_.jsx)(nn,{title:o.vulnerability.title})}),(0,_.jsx)(Te.Td,{noPadding:!0,children:(0,_.jsx)(_e,{vulnerability:o.vulnerability})}),(0,_.jsx)(Te.Td,{children:(0,_.jsx)(qe,{vulnerability:o.vulnerability})}),(0,_.jsx)(Te.Td,{children:(0,_.jsx)(Ze,{name:o.dependencyRef,showVersion:!0})}),(0,_.jsx)(Te.Td,{children:null!==(n=o.vulnerability.remediation)&&void 0!==n&&n.trustedContent?(0,_.jsx)(Ge,{cves:o.vulnerability.cves||[],packageName:null===(r=o.vulnerability.remediation)||void 0===r||null===(i=r.trustedContent)||void 0===i?void 0:i.ref},c):null!==(t=o.vulnerability.remediation)&&void 0!==t&&t.fixedIn?(0,_.jsx)(We,{sourceName:s,vulnerability:o.vulnerability}):E(o.vulnerability)?null:(0,_.jsx)("span",{})})]},c)},tn=function(e){var n=e.providerName,r=e.transitiveDependencies;return(0,_.jsx)(Z.Z,{style:{backgroundColor:"var(--pf-v5-global--BackgroundColor--100)"},children:(0,_.jsxs)(Ie.X,{variant:Ae.a.compact,"aria-label":(null!==n&&void 0!==n?n:"Default")+" transitive vulnerabilities",children:[(0,_.jsx)(be.d,{children:(0,_.jsxs)(Ce.Tr,{children:[(0,_.jsx)(we.Th,{width:15,children:"Vulnerability ID"}),(0,_.jsx)(we.Th,{width:20,children:"Description"}),(0,_.jsx)(we.Th,{width:10,children:"Severity"}),(0,_.jsx)(we.Th,{width:15,children:"CVSS Score"}),(0,_.jsx)(we.Th,{width:20,children:"Transitive Dependency"}),(0,_.jsx)(we.Th,{width:20,children:"Remediation"})]})}),(0,_.jsx)(Qe,{isNoData:0===r.length,numRenderedColumns:7,children:k(r).map(function(e,r){return(0,_.jsx)(Me.N,{children:(0,_.jsx)(rn,{item:e,providerName:n,rowIndex:r})},r)})})]})})},an=function(e){var n=e.providerName,r=e.dependency,i=e.vulnerabilities;return(0,_.jsx)(Z.Z,{style:{backgroundColor:"var(--pf-v5-global--BackgroundColor--100)"},children:(0,_.jsxs)(Ie.X,{variant:Ae.a.compact,"aria-label":(null!==n&&void 0!==n?n:"Default")+" direct vulnerabilities",children:[(0,_.jsx)(be.d,{children:(0,_.jsxs)(Ce.Tr,{children:[(0,_.jsx)(we.Th,{width:15,children:"Vulnerability ID"}),(0,_.jsx)(we.Th,{width:20,children:"Description"}),(0,_.jsx)(we.Th,{width:10,children:"Severity"}),(0,_.jsx)(we.Th,{width:15,children:"CVSS Score"}),(0,_.jsx)(we.Th,{width:20,children:"Direct Dependency"}),(0,_.jsx)(we.Th,{width:20,children:"Remediation"})]})}),(0,_.jsx)(Qe,{isNoData:0===i.length,numRenderedColumns:6,children:null===i||void 0===i?void 0:i.map(function(e,i){var t=[];return e.cves&&e.cves.length>0?e.cves.forEach(function(e){return t.push(e)}):e.unique&&t.push(e.id),(0,_.jsx)(Me.N,{children:t.map(function(t,a){return(0,_.jsx)(rn,{item:{id:e.id,dependencyRef:r.ref,vulnerability:e},providerName:n,rowIndex:i},"".concat(i,"-").concat(a))})},i)})})]})})},on=r(4388),sn=function(e){var n=e.vulnerabilities,r=void 0===n?[]:n,i=e.transitiveDependencies,t=void 0===i?[]:i,a={CRITICAL:0,HIGH:0,MEDIUM:0,LOW:0};return r.length>0?r.forEach(function(e){var n=e.severity;a.hasOwnProperty(n)&&a[n]++}):null===t||void 0===t||t.forEach(function(e){var n;null===(n=e.issues)||void 0===n||n.forEach(function(e){var n=e.severity;a.hasOwnProperty(n)&&a[n]++})}),(0,_.jsxs)(on.Z,{children:[a.CRITICAL>0&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(F.I,{isInline:!0,children:(0,_.jsx)(Je.Ay,{style:{fill:"#800000",height:"13px"}})}),"\xa0",a.CRITICAL,"\xa0"]}),a.HIGH>0&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(F.I,{isInline:!0,children:(0,_.jsx)(Je.Ay,{style:{fill:"#FF0000",height:"13px"}})}),"\xa0",a.HIGH,"\xa0"]}),a.MEDIUM>0&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(F.I,{isInline:!0,children:(0,_.jsx)(Je.Ay,{style:{fill:"#FFA500",height:"13px"}})}),"\xa0",a.MEDIUM,"\xa0"]}),a.LOW>0&&(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(F.I,{isInline:!0,children:(0,_.jsx)(Je.Ay,{style:{fill:"#5BA352",height:"13px"}})}),"\xa0",a.LOW]})]})},cn=r(8480),ln=function(e){var n,r,i=e.dependency,t=null===(n=i.issues)||void 0===n?void 0:n.some(function(e){return E(e)}),a=(null===(r=i.transitive)||void 0===r?void 0:r.some(function(e){var n;return null===(n=e.issues)||void 0===n?void 0:n.some(function(e){return E(e)})}))||!1;return(0,_.jsx)(_.Fragment,{children:t||a?"Yes":"No"})},dn=function(e){var n=e.name,r=e.dependencies,t=(0,i.useState)(""),a=(0,c.A)(t,2),o=a[0],s=a[1],l=function(e){var n=(0,i.useReducer)(ke,(0,ue.A)((0,ue.A)({},Ee),{},{currentPage:e&&e.page?(0,ue.A)({},e.page):(0,ue.A)({},Ee.currentPage),sortBy:e&&e.sortBy?(0,ue.A)({},e.sortBy):Ee.sortBy})),r=(0,c.A)(n,2),t=r[0],a=r[1],o=(0,i.useCallback)(function(e){var n;a({type:Pe.SET_PAGE,payload:{page:e.page>=1?e.page:1,perPage:null!==(n=e.perPage)&&void 0!==n?n:Ee.currentPage.perPage}})},[]),s=(0,i.useCallback)(function(e,n,r,i){a({type:Pe.SET_SORT_BY,payload:{index:n,direction:r}})},[]);return{page:t.currentPage,sortBy:t.sortBy,changePage:o,changeSortBy:s}}(),d=l.page,u=l.sortBy,h=l.changePage,g=l.changeSortBy,v=function(e){var n=e.items,r=e.currentSortBy,t=e.currentPage,a=e.filterItem,o=e.compareToByColumn;return(0,i.useMemo)(function(){var e,i=(0,Se.A)(n||[]).filter(a),s=!1;return e=(0,Se.A)(i).sort(function(e,n){var i=o(e,n,null===r||void 0===r?void 0:r.index);return 0!==i&&(s=!0),i}),s&&(null===r||void 0===r?void 0:r.direction)===Oe.l.desc&&(e=e.reverse()),{pageItems:e.slice((t.page-1)*t.perPage,t.page*t.perPage),filteredItems:i}},[n,t,r,o,a])}({items:r,currentPage:d,currentSortBy:u,compareToByColumn:function(e,n,r){return 1===r?e.ref.localeCompare(n.ref):0},filterItem:function(e){var n=!0;return o&&o.trim().length>0&&(n=-1!==e.ref.toLowerCase().indexOf(o.toLowerCase())),n}}),p=v.pageItems,x=v.filteredItems,f={name:"Dependency Name",version:"Current Version",direct:"Direct Vulnerabilities",transitive:"Transitive Vulnerabilities",rhRemediation:"Remediation available"},m=i.useState({"siemur/test-space":"name"}),j=(0,c.A)(m,2),I=j[0],A=j[1],b=function(e,n,r,i){return{isExpanded:I[e.ref]===n,onToggle:function(){return function(e,n){var r=!(arguments.length>2&&void 0!==arguments[2])||arguments[2],i=(0,ue.A)({},I);r?i[e.ref]=n:delete i[e.ref],A(i)}(e,n,I[e.ref]!==n)},expandId:"compound-expandable-example",rowIndex:r,columnIndex:i}};return(0,_.jsx)(Z.Z,{children:(0,_.jsx)(U.b,{children:(0,_.jsx)("div",{style:{backgroundColor:"var(--pf-v5-global--BackgroundColor--100)"},children:""!==y(n)&&void 0===r?(0,_.jsx)("div",{children:(0,_.jsxs)(he.p,{variant:he.s.sm,children:[(0,_.jsx)(ge.o,{icon:(0,_.jsx)(ve.q,{icon:Re.Ay}),titleText:"Set up "+n,headingLevel:"h2"}),(0,_.jsxs)(pe.h,{children:["You need to provide a valid credentials to see ",n," data. You can use the button below to sing-up for ",n,". If you have already signed up, enter your credentials in your extension settings and then regenerate the Dependency Analytics report."]}),(0,_.jsx)("br",{}),(0,_.jsx)("br",{}),(0,_.jsx)("a",{href:y(n),target:"_blank",rel:"noopener noreferrer",children:(0,_.jsxs)(K.$n,{variant:"primary",size:"sm",children:["Sign up for ",n]})})]})}):(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(xe.M,{children:(0,_.jsxs)(fe.P,{children:[(0,_.jsx)(me.h,{toggleIcon:(0,_.jsx)(De.Ay,{}),breakpoint:"xl",children:(0,_.jsx)(je.T,{variant:"search-filter",children:(0,_.jsx)(ye.D,{id:n+"-dependency-filter",style:{width:"250px"},placeholder:"Filter by Dependency name",value:o,onChange:function(e,n){return s(n)},onClear:function(){return s("")}})})}),(0,_.jsx)(je.T,{variant:je.U.pagination,align:{default:"alignRight"},children:(0,_.jsx)(Le,{isTop:!0,count:x.length,params:d,onChange:h})})]})}),(0,_.jsxs)(Ie.X,{"aria-label":(null!==n&&void 0!==n?n:"Default")+" dependencies",variant:Ae.a.compact,children:[(0,_.jsx)(be.d,{children:(0,_.jsxs)(Ce.Tr,{children:[(0,_.jsx)(we.Th,{width:25,sort:{columnIndex:1,sortBy:(0,ue.A)({},u),onSort:g},children:f.name}),(0,_.jsx)(we.Th,{children:f.version}),(0,_.jsx)(we.Th,{children:f.direct}),(0,_.jsx)(we.Th,{children:f.transitive}),(0,_.jsx)(we.Th,{children:f.rhRemediation})]})}),(0,_.jsx)(Qe,{isNoData:0===x.length,numRenderedColumns:8,noDataEmptyState:(0,_.jsxs)(he.p,{variant:he.s.sm,children:[(0,_.jsx)(ge.o,{icon:(0,_.jsx)(ve.q,{icon:cn.Ay}),titleText:"No results found",headingLevel:"h2"}),(0,_.jsx)(pe.h,{children:"Clear all filters and try again."})]}),children:null===p||void 0===p?void 0:p.map(function(e,r){var i,t,a,o,s,c=I[e.ref],l=!!c;return null!==(i=e.issues)&&void 0!==i&&i.length||null!==(t=e.transitive)&&void 0!==t&&t.length?(0,_.jsxs)(Me.N,{isExpanded:l,children:[(0,_.jsxs)(Ce.Tr,{children:[(0,_.jsx)(Te.Td,{width:30,dataLabel:f.name,component:"th",children:(0,_.jsx)(Ze,{name:e.ref})}),(0,_.jsx)(Te.Td,{width:15,dataLabel:f.version,children:C(e.ref)}),(0,_.jsx)(Te.Td,{width:15,dataLabel:f.direct,compoundExpand:b(e,"direct",r,2),children:null!==(a=e.issues)&&void 0!==a&&a.length?(0,_.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[(0,_.jsx)("div",{style:{width:"25px"},children:null===(o=e.issues)||void 0===o?void 0:o.length}),(0,_.jsx)(L.c,{orientation:{default:"vertical"},style:{paddingRight:"10px"}}),(0,_.jsx)(sn,{vulnerabilities:e.issues})]}):0}),(0,_.jsx)(Te.Td,{width:15,dataLabel:f.transitive,compoundExpand:b(e,"transitive",r,3),children:null!==(s=e.transitive)&&void 0!==s&&s.length?(0,_.jsxs)("div",{style:{display:"flex",alignItems:"center"},children:[(0,_.jsx)("div",{style:{width:"25px"},children:e.transitive.map(function(e){var n;return null===(n=e.issues)||void 0===n?void 0:n.length}).reduce(function(){return(arguments.length>0&&void 0!==arguments[0]?arguments[0]:0)+(arguments.length>1&&void 0!==arguments[1]?arguments[1]:0)})}),(0,_.jsx)(L.c,{orientation:{default:"vertical"},style:{paddingRight:"10px"}}),(0,_.jsx)(sn,{transitiveDependencies:e.transitive})]}):0}),(0,_.jsx)(Te.Td,{width:15,dataLabel:f.rhRemediation,children:(0,_.jsx)(ln,{dependency:e})})]}),l?(0,_.jsx)(Ce.Tr,{isExpanded:l,children:(0,_.jsx)(Te.Td,{dataLabel:f[c],noPadding:!0,colSpan:6,children:(0,_.jsx)(Ne.g,{children:(0,_.jsx)("div",{className:"pf-v5-u-m-md",children:"direct"===c&&e.issues&&e.issues.length>0?(0,_.jsx)(an,{providerName:n,dependency:e,vulnerabilities:e.issues}):"transitive"===c&&e.transitive&&e.transitive.length>0?(0,_.jsx)(tn,{providerName:n,transitiveDependencies:e.transitive}):null})})})}):null]},e.ref):null})})]}),(0,_.jsx)(Le,{isTop:!1,count:x.length,params:d,onChange:h})]})})})})},un=r(1157),hn=function(e){var n=e.report,r=xn(),t=O(n),o=i.useState(P(t[0])),s=(0,c.A)(o,2),l=s[0],d=s[1],u=i.useState(!0),h=(0,c.A)(u,1)[0],g=r.writeKey&&""!==r.writeKey.trim()?un.N.load({writeKey:r.writeKey}):null,v=(0,i.useRef)(""),p=(0,i.useRef)(!1);(0,i.useEffect)(function(){g&&(null!=r.anonymousId&&g.setAnonymousId(r.anonymousId),p.current=!0)},[]),(0,i.useEffect)(function(){if(g){var e=function(){var e=(0,se.A)((0,oe.A)().m(function e(n){return(0,oe.A)().w(function(e){for(;;)switch(e.n){case 0:n!==v.current&&(g.track("rhda.exhort.tab",{tabName:n}),v.current=n);case 1:return e.a(2)}},e)}));return function(n){return e.apply(this,arguments)}}();e(l)}},[l,g]);var x=t.map(function(e){var n,r=P(e),i=null===(n=e.report.dependencies)||void 0===n?void 0:n.filter(function(e){return e.highestVulnerability});return(0,_.jsx)(ce.o,{eventKey:r,title:(0,_.jsx)(le.V,{children:r}),"aria-label":"".concat(r," source"),children:(0,_.jsx)(a.d8,{variant:a.zC.default,children:(0,_.jsx)(dn,{name:r,dependencies:i})})})});return(0,_.jsx)("div",{children:(0,_.jsx)(de.t,{activeKey:l,onSelect:function(e,n){d(n)},"aria-label":"Providers",role:"region",variant:h?"light300":"default",isBox:!0,children:x})})},gn=function(e){var n=e.report,r=i.useState(Object.keys(n)[0]||""),t=(0,c.A)(r,2),l=t[0],d=t[1],u=i.useState(!0),h=(0,c.A)(u,1)[0],g=Object.entries(n).map(function(e){var n=(0,c.A)(e,2),r=n[0],i=n[1];return(0,_.jsxs)(ce.o,{eventKey:r,title:(0,_.jsx)(le.V,{children:M(r)}),"aria-label":"".concat(r," source"),children:[(0,_.jsx)(ae,{report:i}),(0,_.jsx)(a.d8,{variant:a.zC.light,children:(0,_.jsx)(o.x,{hasGutter:!0,children:(0,_.jsx)(s.E,{children:(0,_.jsx)(ie,{report:i,isReportMap:!0,purl:r})})})}),(0,_.jsx)(a.d8,{variant:a.zC.default,children:(0,_.jsx)(hn,{report:i})})]})});return(0,_.jsx)("div",{children:(0,_.jsx)(de.t,{activeKey:l,onSelect:function(e,n){d(n)},"aria-label":"Providers",role:"region",variant:h?"light300":"default",isBox:!0,children:g})})},vn=window.appData,pn=(0,i.createContext)(vn),xn=function(){return(0,i.useContext)(pn)};var fn=function(){return(0,_.jsx)(pn.Provider,{value:vn,children:(e=vn.report,"object"===typeof e&&null!==e&&Object.keys(e).every(function(n){return"scanned"in e[n]&&"providers"in e[n]&&"object"===typeof e[n].scanned&&"object"===typeof e[n].providers})?(0,_.jsx)(a.d8,{variant:a.zC.default,children:(0,_.jsx)(gn,{report:vn.report})}):(0,_.jsxs)(_.Fragment,{children:[(0,_.jsx)(ae,{report:vn.report}),(0,_.jsx)(a.d8,{variant:a.zC.light,children:(0,_.jsx)(o.x,{hasGutter:!0,children:(0,_.jsx)(s.E,{children:(0,_.jsx)(ie,{report:vn.report})})})}),(0,_.jsx)(a.d8,{variant:a.zC.default,children:(0,_.jsx)(hn,{report:vn.report})})]}))});var e},mn=function(e){e&&e instanceof Function&&r.e(121).then(r.bind(r,6895)).then(function(n){var r=n.getCLS,i=n.getFID,t=n.getFCP,a=n.getLCP,o=n.getTTFB;r(e),i(e),t(e),a(e),o(e)})};t.createRoot(document.getElementById("root")).render((0,_.jsx)(i.StrictMode,{children:(0,_.jsx)(fn,{})})),mn()}},n={};function r(i){var t=n[i];if(void 0!==t)return t.exports;var a=n[i]={id:i,loaded:!1,exports:{}};return e[i].call(a.exports,a,a.exports,r),a.loaded=!0,a.exports}r.m=e,function(){var e=[];r.O=function(n,i,t,a){if(!i){var o=1/0;for(d=0;d=a)&&Object.keys(r.O).every(function(e){return r.O[e](i[c])})?i.splice(c--,1):(s=!1,a0&&e[d-1][2]>a;d--)e[d]=e[d-1];e[d]=[i,t,a]}}(),r.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(n,{a:n}),n},function(){var e,n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__};r.t=function(i,t){if(1&t&&(i=this(i)),8&t)return i;if("object"===typeof i&&i){if(4&t&&i.__esModule)return i;if(16&t&&"function"===typeof i.then)return i}var a=Object.create(null);r.r(a);var o={};e=e||[null,n({}),n([]),n(n)];for(var s=2&t&&i;("object"==typeof s||"function"==typeof s)&&!~e.indexOf(s);s=n(s))Object.getOwnPropertyNames(s).forEach(function(e){o[e]=function(){return i[e]}});return o.default=function(){return i},r.d(a,o),a}}(),r.d=function(e,n){for(var i in n)r.o(n,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:n[i]})},r.e=function(){return Promise.resolve()},r.g=function(){if("object"===typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(e){if("object"===typeof window)return window}}(),r.o=function(e,n){return Object.prototype.hasOwnProperty.call(e,n)},r.r=function(e){"undefined"!==typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=function(e){return e.paths=[],e.children||(e.children=[]),e},function(){var e={792:0};r.O.j=function(n){return 0===e[n]};var n=function(n,i){var t,a,o=i[0],s=i[1],c=i[2],l=0;if(o.some(function(n){return 0!==e[n]})){for(t in s)r.o(s,t)&&(r.m[t]=s[t]);if(c)var d=c(r)}for(n&&n(i);l start() {
stubTrustifyClientToken();
var oidcConfig = new HashMap<>(base);
+ // Use the server's base URL for keycloak.url to ensure consistency
oidcConfig.put("keycloak.url", server.baseUrl());
return oidcConfig;
}
@@ -130,4 +131,13 @@ protected void stubTrustifyClientToken() {
.withHeader("Content-Type", "application/json")
.withBody(openIdConfigJson)));
}
+
+ @Override
+ public void inject(TestInjector testInjector) {
+ // Override to inject this extension's server instance (not the parent's)
+ // This ensures the same server is used for both keycloak.url and @InjectWireMock
+ testInjector.injectIntoFields(
+ server,
+ new TestInjector.AnnotatedAndMatchesType(InjectWireMock.class, WireMockServer.class));
+ }
}
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/AbstractAnalysisTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/AbstractAnalysisTest.java
index 761f7412..12943237 100644
--- a/src/test/java/io/github/guacsec/trustifyda/integration/AbstractAnalysisTest.java
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/AbstractAnalysisTest.java
@@ -50,8 +50,6 @@
import io.github.guacsec.trustifyda.extensions.InjectWireMock;
import io.github.guacsec.trustifyda.extensions.OidcWiremockExtension;
-import io.github.guacsec.trustifyda.extensions.WiremockExtension;
-import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.config.DecoderConfig;
@@ -60,7 +58,8 @@
import jakarta.ws.rs.core.MediaType;
@QuarkusTest
-@QuarkusTestResource(WiremockExtension.class)
+// Note: Subclasses should specify their own @QuarkusTestResource
+// (e.g., OidcWiremockExtension.class for tests that need OIDC)
public abstract class AbstractAnalysisTest {
public static final String OK_TOKEN = "test-token";
@@ -176,7 +175,7 @@ protected void verifyTrustifyRequest(String token, int count) {
protected void stubAllProviders() {
stubOsvRequests();
stubTrustifyRequests();
- stubTrustedContentRequests();
+ stubRecommendRequests();
}
protected void verifyProviders(Collection providers, Map credentials) {
@@ -185,21 +184,19 @@ protected void verifyProviders(Collection providers, Map
p -> {
verifyTrustifyRequest(credentials.get(Constants.TRUSTIFY_TOKEN_HEADER));
});
- verifyTrustedContentRequest();
+ verifyRecommendRequest();
}
- protected void stubTrustedContentRequests() {
+ protected void stubRecommendRequests() {
server.stubFor(
- post(Constants.TRUSTED_CONTENT_PATH)
- .withHeader(Exchange.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON))
+ post(Constants.TRUSTIFY_RECOMMEND_PATH)
.willReturn(
aResponse()
.withStatus(200)
.withHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBodyFile("trustedcontent/empty_report.json")));
server.stubFor(
- post(Constants.TRUSTED_CONTENT_PATH)
- .withHeader(Exchange.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON))
+ post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withRequestBody(
equalToJson(
loadFileAsString("__files/trustedcontent/maven_request.json"), true, false))
@@ -209,8 +206,7 @@ protected void stubTrustedContentRequests() {
.withHeader(Exchange.CONTENT_TYPE, MediaType.APPLICATION_JSON)
.withBodyFile("trustedcontent/maven_report.json")));
server.stubFor(
- post(Constants.TRUSTED_CONTENT_PATH)
- .withHeader(Exchange.CONTENT_TYPE, equalTo(MediaType.APPLICATION_JSON))
+ post(Constants.TRUSTIFY_RECOMMEND_PATH)
.withRequestBody(
equalToJson(
loadFileAsString("__files/trustedcontent/batch_request.json"), true, false))
@@ -329,7 +325,6 @@ protected void stubTrustifyRequests() {
}
protected void stubTokenValidationEndpoint() {
-
server.stubFor(
post(urlMatching(".*/realms/.*/token/introspect.*"))
.withBasicAuth(OidcWiremockExtension.CLIENT_ID, OidcWiremockExtension.CLIENT_SECRET)
@@ -360,8 +355,8 @@ private String getRequestBody(String token) {
+ "&token_type_hint=access_token";
}
- protected void verifyTrustedContentRequest() {
- server.verify(1, postRequestedFor(urlEqualTo(Constants.TRUSTED_CONTENT_PATH)));
+ protected void verifyRecommendRequest() {
+ server.verify(1, postRequestedFor(urlEqualTo(Constants.TRUSTIFY_RECOMMEND_PATH)));
}
protected void verifyOsvRequest() {
@@ -377,8 +372,8 @@ protected void verifyNoInteractions() {
verifyNoInteractionsWithTrustify();
}
- protected void verifyNoInteractionsWithTrustedContent() {
- server.verify(0, postRequestedFor(urlEqualTo(Constants.TRUSTED_CONTENT_PATH)));
+ protected void verifyNoInteractionsWithRecommend() {
+ server.verify(0, postRequestedFor(urlEqualTo(Constants.TRUSTIFY_RECOMMEND_PATH)));
}
protected void verifyNoInteractionsWithOsv() {
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/AnalysisTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/AnalysisTest.java
index 71e5693b..10c9f8f3 100644
--- a/src/test/java/io/github/guacsec/trustifyda/integration/AnalysisTest.java
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/AnalysisTest.java
@@ -55,7 +55,6 @@
import io.github.guacsec.trustifyda.api.PackageRef;
import io.github.guacsec.trustifyda.api.v5.AnalysisReport;
-import io.github.guacsec.trustifyda.api.v5.DependencyReport;
import io.github.guacsec.trustifyda.api.v5.Scanned;
import io.github.guacsec.trustifyda.api.v5.Source;
import io.github.guacsec.trustifyda.extensions.OidcWiremockExtension;
@@ -185,7 +184,7 @@ public void testEmptySbom(Map providers, Map au
});
verifyNoInteractionsWithTrustify();
- verifyNoInteractionsWithTrustedContent();
+ verifyNoInteractionsWithRecommend();
}
private static Stream emptySbomArguments() {
@@ -216,10 +215,10 @@ public void testDefaultTokens() {
.extract()
.body()
.asPrettyString();
- assertJson("reports/report_default_token.json", body);
+ assertJson("reports/report.json", body);
verifyTrustifyRequest(TRUSTIFY_TOKEN);
verifyOsvRequest();
- verifyTrustedContentRequest();
+ verifyRecommendRequest();
}
@Test
@@ -248,7 +247,7 @@ public void testDefaultTokensOptOutRecommendations() {
assertRecommendations(body, TRUSTIFY_PROVIDER, OSV_SOURCE, 0);
verifyTrustifyRequest(TRUSTIFY_TOKEN);
verifyOsvRequest();
- verifyNoInteractionsWithTrustedContent();
+ verifyNoInteractionsWithRecommend();
}
@Test
@@ -273,7 +272,7 @@ public void testAllWithToken() {
.extract()
.body()
.asPrettyString();
- assertJson("reports/report_all_token.json", body);
+ assertJson("reports/report.json", body);
verifyTrustifyRequest(OK_TOKEN);
verifyOsvRequest();
}
@@ -301,7 +300,7 @@ public void testUnauthorizedRequest() {
.body()
.as(AnalysisReport.class);
- assertEquals(3, report.getProviders().size());
+ assertEquals(2, report.getProviders().size());
assertEquals(
Response.Status.UNAUTHORIZED.getStatusCode(),
report.getProviders().get(TRUSTIFY_PROVIDER).getStatus().getCode());
@@ -332,44 +331,13 @@ public void testForbiddenRequest() {
.body()
.as(AnalysisReport.class);
- assertEquals(3, report.getProviders().size());
+ assertEquals(2, report.getProviders().size());
assertEquals(401, report.getProviders().get(TRUSTIFY_PROVIDER).getStatus().getCode());
assertTrue(report.getProviders().get(TRUSTIFY_PROVIDER).getSources().isEmpty());
verifyTrustifyRequest(INVALID_TOKEN);
}
- @Test
- public void testSBOMJsonWithToken() {
- stubAllProviders();
-
- var report =
- given()
- .header(CONTENT_TYPE, Constants.CYCLONEDX_MEDIATYPE_JSON)
- .body(loadSBOMFile(CYCLONEDX))
- .header("Accept", MediaType.APPLICATION_JSON)
- .header(Constants.TRUSTIFY_TOKEN_HEADER, OK_TOKEN)
- .when()
- .post("/api/v5/analysis")
- .then()
- .assertThat()
- .statusCode(200)
- .contentType(MediaType.APPLICATION_JSON)
- .header(
- Constants.EXHORT_REQUEST_ID_HEADER,
- MatchesPattern.matchesPattern(REGEX_MATCHER_REQUEST_ID))
- .extract()
- .body()
- .as(AnalysisReport.class);
-
- assertScanned(report.getScanned());
- var osvSource = report.getProviders().get(TRUSTIFY_PROVIDER).getSources().get(OSV_SOURCE);
- assertOsvSummary(osvSource);
- assertOsvDependenciesReport(osvSource.getDependencies());
-
- verifyTrustifyRequest(OK_TOKEN);
- }
-
@Test
public void testNonVerboseJson() {
stubAllProviders();
@@ -553,12 +521,8 @@ public void testInvalidGzipDeflatedContentEncoding(String sbom) throws IOExcepti
.asString();
switch (sbom) {
- case CYCLONEDX:
- assertTrue(response.startsWith("CycloneDX Validation"));
- break;
- case SPDX:
- assertTrue(response.startsWith("SPDX-2.3 Validation"));
- break;
+ case CYCLONEDX -> assertTrue(response.startsWith("CycloneDX Validation"));
+ case SPDX -> assertTrue(response.startsWith("SPDX-2.3 Validation"));
}
}
@@ -585,7 +549,7 @@ public void testBatchSBOMAllWithToken(String sbom) {
.extract()
.body()
.asPrettyString();
- assertJson("reports/batch_report_all_token.json", body);
+ assertJson("reports/batch_report.json", body);
verifyTrustifyRequest(OK_TOKEN, 3);
verifyOsvRequest(3);
}
@@ -621,50 +585,6 @@ private void assertCsafSummary(Source source) {
assertEquals(0, summary.getLow());
}
- private void assertOsvDependenciesReport(List dependencies) {
- assertEquals(2, dependencies.size());
-
- var hibernate =
- new PackageRef("pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar");
- var report = getReport(hibernate.name(), dependencies);
- assertNotNull(report);
- assertEquals(hibernate, report.getRef());
- assertTrue(report.getIssues().isEmpty());
-
- assertEquals(2, report.getTransitive().size());
- report
- .getTransitive()
- .forEach(
- t -> {
- if (t.getRef().name().equals("io.quarkus:quarkus-core")) {
- var jackson =
- new PackageRef("pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar");
- assertEquals(jackson, t.getRef());
- assertEquals(2, t.getIssues().size());
- assertEquals(t.getHighestVulnerability(), t.getIssues().get(0));
- }
- if (t.getRef().name().equals("com.fasterxml.jackson.core:jackson-databind")) {
- var jackson =
- new PackageRef(
- "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar");
- assertEquals(jackson, t.getRef());
- assertEquals(3, t.getIssues().size());
- assertEquals(t.getHighestVulnerability(), t.getIssues().get(0));
- assertEquals(report.getHighestVulnerability(), t.getHighestVulnerability());
- }
- });
- }
-
- private DependencyReport getReport(String pkgName, List dependencies) {
- var dep =
- dependencies.stream()
- .filter(d -> d.getRef().name().equals(pkgName))
- .findFirst()
- .orElse(null);
- assertNotNull(dep);
- return dep;
- }
-
private void assertRecommendations(
String body, String provider, String source, int recommendations) {
try {
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/HtmlReportTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/HtmlReportTest.java
index 13ed2c31..c1006e0f 100644
--- a/src/test/java/io/github/guacsec/trustifyda/integration/HtmlReportTest.java
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/HtmlReportTest.java
@@ -17,7 +17,6 @@
package io.github.guacsec.trustifyda.integration;
-import static io.github.guacsec.trustifyda.extensions.WiremockExtension.TRUSTIFY_TOKEN;
import static io.restassured.RestAssured.given;
import static org.apache.camel.Exchange.CONTENT_TYPE;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -31,6 +30,7 @@
import org.hamcrest.text.MatchesPattern;
import org.htmlunit.BrowserVersion;
import org.htmlunit.WebClient;
+import org.htmlunit.html.DomAttr;
import org.htmlunit.html.DomElement;
import org.htmlunit.html.DomNodeList;
import org.htmlunit.html.HtmlAnchor;
@@ -52,7 +52,7 @@ public class HtmlReportTest extends AbstractAnalysisTest {
private static final String CYCLONEDX = "cyclonedx";
@Test
- public void testHtmlWithoutToken() throws IOException {
+ public void testHtml() throws IOException {
stubAllProviders();
String body =
@@ -82,27 +82,30 @@ public void testHtmlWithoutToken() throws IOException {
DomNodeList tables = page.getElementsByTagName("table");
assertEquals(3, tables.size()); // osv | trustify/osv | trustify/csaf
- DomElement table = tables.get(tables.size() - 1); // trustify/csaf
+ DomElement table = tables.get(1); // trustify/osv
HtmlTableBody tbody = getTableBodyForDependency("io.quarkus:quarkus-hibernate-orm", table);
assertNotNull(tbody);
page = expandTransitiveTableDataCell(webClient, tbody);
table =
page.getFirstByXPath(
- "//table[contains(@aria-label, 'trustify/csaf transitive vulnerabilities')]");
+ "//table[contains(@aria-label, 'trustify/osv transitive vulnerabilities')]");
List tbodies = table.getByXPath(".//tbody");
- HtmlTableBody issue =
+ List issues =
tbodies.stream()
.filter(
issuesTbody -> {
List tds = issuesTbody.getByXPath("./tr/td");
return tds.size() == 6;
})
- .findFirst()
- .get();
- assertNotNull(issue);
-
- verifyTrustifyRequest(TRUSTIFY_TOKEN);
+ .toList();
+ assertNotNull(issues);
+ assertEquals(5, issues.size());
+ DomAttr remediationLink = (DomAttr) issues.get(0).getByXPath("./tr/td[6]/a/@href").get(0);
+ assertNotNull(remediationLink);
+ assertEquals(
+ "https://maven.repository.redhat.com/ga/com/fasterxml/jackson/core/jackson-databind/2.13.1.2-redhat-00001",
+ remediationLink.getValue());
}
@Test
@@ -263,7 +266,7 @@ public void testBatchHtmlWithToken() throws IOException {
List sectionElements = rootElement.getByXPath("./section");
assertEquals(1, sectionElements.size());
List anchorElements =
- page.getByXPath("//a[contains(@href, 'https://test-catalog.example.com/containers/ubi9')]");
+ page.getByXPath("//a[contains(@href, 'https://test-catalog.example.com/containers')]");
assertTrue(!anchorElements.isEmpty(), "At least one href contains the desired substring");
verifyTrustifyRequest(OK_TOKEN, 3);
}
@@ -291,17 +294,6 @@ private HtmlPage expandTableDataCell(WebClient webClient, HtmlTableBody tbody, S
return tbody.getHtmlPageOrNull();
}
HtmlButton button = td.getFirstByXPath("./button");
-
- // Debug: Print button details
- System.err.println(
- "*** DEBUG: Found button: "
- + button.getAttribute("id")
- + ", aria-expanded: "
- + button.getAttribute("aria-expanded")
- + ", class: "
- + button.getAttribute("class")
- + " ***");
-
return click(webClient, button);
}
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/TokenValidationTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/TokenValidationTest.java
index 69c60582..faa5b27f 100644
--- a/src/test/java/io/github/guacsec/trustifyda/integration/TokenValidationTest.java
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/TokenValidationTest.java
@@ -26,12 +26,15 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import io.github.guacsec.trustifyda.extensions.OidcWiremockExtension;
+import io.quarkus.test.common.QuarkusTestResource;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
@QuarkusTest
+@QuarkusTestResource(OidcWiremockExtension.class)
public class TokenValidationTest extends AbstractAnalysisTest {
@BeforeEach
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheServiceTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheServiceTest.java
deleted file mode 100644
index f7f39084..00000000
--- a/src/test/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheServiceTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright 2023-2025 Trustify Dependency Analytics Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package io.github.guacsec.trustifyda.integration.cache;
-
-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 java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import org.junit.jupiter.api.Test;
-
-import io.github.guacsec.trustifyda.api.PackageRef;
-import io.github.guacsec.trustifyda.api.v5.ProviderStatus;
-import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
-import io.quarkus.test.junit.QuarkusTest;
-
-import jakarta.inject.Inject;
-
-@QuarkusTest
-public class RedisCacheServiceTest {
-
- private static final PackageRef[] TEST_PURLS = {
- new PackageRef("pkg:maven/io.quarkus/quarkus-core@2.13.6.Final"),
- new PackageRef("pkg:maven/io.quarkus/quarkus-arc@2.13.6.Final")
- };
-
- @Inject CacheService cacheService;
-
- @Test
- void testFailedResponse() {
- var failedResponse =
- new TrustedContentResponse(buildRecommendations(), new ProviderStatus().ok(Boolean.FALSE));
- cacheService.cacheRecommendations(failedResponse, Set.of(TEST_PURLS));
-
- assertTrue(cacheService.getRecommendations(Set.of(TEST_PURLS)).isEmpty());
- }
-
- @Test
- void testCacheAllData() {
- var failedResponse =
- new TrustedContentResponse(buildRecommendations(), new ProviderStatus().ok(Boolean.TRUE));
- cacheService.cacheRecommendations(failedResponse, Set.of(TEST_PURLS));
-
- var cachedData = cacheService.getRecommendations(Set.of(TEST_PURLS));
- assertEquals(2, cachedData.size());
- Stream.of(TEST_PURLS)
- .forEach(
- p -> {
- var cachedPurl = cachedData.get(p);
- assertNotNull(cachedPurl);
- assertEquals(p, cachedPurl.ref());
- assertEquals(
- p.ref() + "-redhat-00001", cachedPurl.recommendation().packageName().ref());
- });
- }
-
- private Map buildRecommendations() {
- return Stream.of(TEST_PURLS)
- .collect(
- Collectors.toMap(
- p -> p,
- p ->
- new IndexedRecommendation(
- new PackageRef(p.ref() + "-redhat-00001"), Collections.emptyMap())));
- }
-}
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandlerTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandlerTest.java
index e03476b6..0fd9c7a1 100644
--- a/src/test/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandlerTest.java
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandlerTest.java
@@ -19,11 +19,12 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
-import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -40,17 +41,17 @@
import io.github.guacsec.trustifyda.api.v5.DependencyReport;
import io.github.guacsec.trustifyda.api.v5.Issue;
import io.github.guacsec.trustifyda.api.v5.ProviderReport;
+import io.github.guacsec.trustifyda.api.v5.Remediation;
+import io.github.guacsec.trustifyda.api.v5.RemediationTrustedContent;
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.api.v5.TransitiveDependencyReport;
-import io.github.guacsec.trustifyda.api.v5.UnscannedDependency;
import io.github.guacsec.trustifyda.model.DependencyTree;
import io.github.guacsec.trustifyda.model.DirectDependency;
+import io.github.guacsec.trustifyda.model.PackageItem;
import io.github.guacsec.trustifyda.model.ProviderResponse;
-import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation;
-import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse;
-import io.github.guacsec.trustifyda.model.trustedcontent.Vulnerability;
+import io.github.guacsec.trustifyda.model.trustify.Recommendation;
+import io.github.guacsec.trustifyda.model.trustify.Vulnerability;
import jakarta.ws.rs.core.Response;
@@ -59,27 +60,18 @@ public class ProviderResponseHandlerTest {
private static final String NPM_PURL_TYPE = "npm";
private static final String TEST_PROVIDER = "example";
private static final String TEST_SOURCE = "test-source";
- private static final TrustedContentResponse EMPTY_TRUSTED_CONTENT_RESPONSE =
- new TrustedContentResponse(null, null);
@ParameterizedTest
@MethodSource("getSummaryValues")
public void testSummary(
- Map> issuesData,
- List unscanned,
- DependencyTree tree,
- SourceSummary expected)
+ Map data, DependencyTree tree, SourceSummary expected, String sourceName)
throws IOException {
ProviderResponseHandler handler = new TestResponseHandler();
ProviderReport response =
- handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issuesData, null, unscanned),
- tree,
- EMPTY_TRUSTED_CONTENT_RESPONSE);
+ handler.buildReport(Mockito.mock(Exchange.class), new ProviderResponse(data, null), tree);
assertOkStatus(response);
- SourceSummary summary = getValidSource(response).getSummary();
+ SourceSummary summary = getValidSource(response, sourceName).getSummary();
assertEquals(expected.getDirect(), summary.getDirect());
assertEquals(expected.getTotal(), summary.getTotal());
@@ -91,154 +83,124 @@ public void testSummary(
assertEquals(expected.getRecommendations(), summary.getRecommendations());
assertEquals(expected.getRemediations(), summary.getRemediations());
assertEquals(expected.getDependencies(), summary.getDependencies());
- assertEquals(expected.getUnscanned(), summary.getUnscanned());
}
private static Stream getSummaryValues() {
return Stream.of(
+ // Case 1: 0 issues 2 recommendations
+ Arguments.of(
+ Map.of(
+ "pkg:npm/aa@1",
+ new PackageItem(
+ "pkg:npm/aa@1",
+ buildRecommendation("pkg:npm/aa@2", Collections.emptyMap()),
+ Collections.emptyList()),
+ "pkg:npm/ab@1",
+ new PackageItem(
+ "pkg:npm/ab@1",
+ buildRecommendation("pkg:npm/ab@2", Collections.emptyMap()),
+ Collections.emptyList())),
+ tree().direct("aa").direct("ab").build(),
+ new SourceSummary().direct(0).total(0).dependencies(0).recommendations(2),
+ TEST_PROVIDER),
+ // Case 2: 2 issues 0 recommendations
Arguments.of(
- Map.of("pkg:npm/aa@1", List.of(buildIssue(1, 5f))),
- List.of(
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aaa@1")).reason("aaa"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aaaaa@1")).reason("aaaaa")),
- buildTree(),
- new SourceSummary().direct(1).total(1).medium(1).dependencies(1).unscanned(2)),
+ Map.of(
+ "pkg:npm/aa@1", new PackageItem("pkg:npm/aa@1", null, List.of(buildIssue(1, 5f))),
+ "pkg:npm/ab@1", new PackageItem("pkg:npm/ab@1", null, List.of(buildIssue(2, 8f))),
+ "pkg:npm/aaa@1", new PackageItem("pkg:npm/aaa@1", null, List.of(buildIssue(3, 4f))),
+ "pkg:npm/aba@1",
+ new PackageItem("pkg:npm/aba@1", null, List.of(buildIssue(4, 7f)))),
+ tree().direct("aa").withTransitive("aaa").direct("ab").withTransitive("aab").build(),
+ new SourceSummary().direct(2).transitive(2).total(4).high(2).medium(2).dependencies(4),
+ TEST_SOURCE),
+ // Case 3: 2 issues with 1 remediation and 1 recommendation
Arguments.of(
Map.of(
- "pkg:npm/aa@1", List.of(buildIssue(1, 3f)),
- "pkg:npm/aaa@1", List.of(buildIssue(2, 4f)),
- "pkg:npm/aba@1", List.of(buildIssue(3, 8f))),
- List.of(
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aab@1")).reason("aab"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aabbb@1")).reason("aabbb"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aabbc@1")).reason("aabbc")),
- buildTree(),
+ "pkg:npm/aa@1",
+ new PackageItem(
+ "pkg:npm/aa@1",
+ buildRecommendation("pkg:npm/aa@2", Map.of("ISSUE-001", "Fixed")),
+ List.of(
+ buildIssueWithTcRemediation(
+ 1, 5f, "pkg:npm/aa@2-redhat-00001"))), // Issue with remediation (ID
+ // matches)
+ "pkg:npm/ab@1",
+ new PackageItem(
+ "pkg:npm/ab@1",
+ buildRecommendation("pkg:npm/ab@2", Collections.emptyMap()),
+ Collections.emptyList())), // Recommendation only
+ tree().direct("aa").direct("ab").build(),
new SourceSummary()
- .total(3)
.direct(1)
- .transitive(2)
- .high(1)
+ .total(1)
.medium(1)
- .low(1)
- .dependencies(3)
- .unscanned(3)),
+ .dependencies(1)
+ .recommendations(2)
+ .remediations(1),
+ TEST_SOURCE),
+ // Case 4: 2 direct issues with 4 transitive
Arguments.of(
Map.of(
- "pkg:npm/aa@1", List.of(buildIssue(1, 5f)),
- "pkg:npm/aaa@1", List.of(buildIssue(2, 5f))),
- null,
- buildTreeWithDuplicates(),
+ "pkg:npm/aa@1", new PackageItem("pkg:npm/aa@1", null, List.of(buildIssue(1, 5f))),
+ "pkg:npm/ab@1", new PackageItem("pkg:npm/ab@1", null, List.of(buildIssue(2, 7f))),
+ "pkg:npm/aaa@1", new PackageItem("pkg:npm/aaa@1", null, List.of(buildIssue(3, 4f))),
+ "pkg:npm/aab@1", new PackageItem("pkg:npm/aab@1", null, List.of(buildIssue(4, 6f))),
+ "pkg:npm/aba@1", new PackageItem("pkg:npm/aba@1", null, List.of(buildIssue(5, 3f))),
+ "pkg:npm/abb@1",
+ new PackageItem("pkg:npm/abb@1", null, List.of(buildIssue(6, 8f)))),
+ tree()
+ .direct("aa")
+ .withTransitive("aaa", "aab")
+ .direct("ab")
+ .withTransitive("aba", "abb")
+ .build(),
new SourceSummary()
- .total(2)
- .direct(1)
- .transitive(1)
- .medium(2)
- .dependencies(2)
- .unscanned(0)));
- }
-
- @Test
- public void testFilterDepsWithoutIssues() throws IOException {
- Map> issues = Map.of("pkg:npm/aa@1", List.of(buildIssue(1, 5f)));
- ProviderResponseHandler handler = new TestResponseHandler();
- DependencyTree tree = buildTree();
- ProviderReport response =
- handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- tree,
- EMPTY_TRUSTED_CONTENT_RESPONSE);
- assertOkStatus(response);
- assertEquals(1, response.getSources().size());
- Source report = response.getSources().get(TEST_SOURCE);
- assertEquals(1, report.getDependencies().size());
- assertEquals(1, report.getDependencies().get(0).getIssues().size());
- assertEquals("aa", report.getDependencies().get(0).getRef().name());
- }
-
- @Test
- public void testFilterTransitiveWithoutIssues() throws IOException {
-
- Map> issues =
- Map.of(
- "pkg:npm/aa@1", List.of(buildIssue(1, 3f)),
- "pkg:npm/aaa@1", List.of(buildIssue(2, 4f)),
- "pkg:npm/aba@1", List.of(buildIssue(3, 8f)));
- ProviderResponseHandler handler = new TestResponseHandler();
-
- ProviderReport response =
- handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- buildTree(),
- EMPTY_TRUSTED_CONTENT_RESPONSE);
-
- assertOkStatus(response);
-
- // Validate aa has 1 direct and 1 transitive being the transitive the
- // highestVulnerability
- assertEquals(1, response.getSources().size());
- Source source = response.getSources().get(TEST_SOURCE);
- assertEquals(2, source.getDependencies().size());
- DependencyReport report =
- source.getDependencies().stream()
- .filter(r -> "aa".equals(r.getRef().name()))
- .findFirst()
- .get();
- assertEquals(1, report.getIssues().size());
- assertEquals("aa", report.getRef().name());
- assertEquals(4f, report.getHighestVulnerability().getCvssScore());
- assertEquals("ISSUE-002", report.getHighestVulnerability().getId());
-
- assertEquals(1, report.getTransitive().size());
- TransitiveDependencyReport transitive = report.getTransitive().get(0);
- assertEquals("aaa", transitive.getRef().name());
-
- // Validate that ab has no issues and one transitive.
- report =
- source.getDependencies().stream()
- .filter(r -> "ab".equals(r.getRef().name()))
- .findFirst()
- .get();
- assertTrue(report.getIssues().isEmpty());
- assertEquals("ab", report.getRef().name());
- assertEquals(8f, report.getHighestVulnerability().getCvssScore());
- assertEquals("ISSUE-003", report.getHighestVulnerability().getId());
-
- assertEquals(1, report.getTransitive().size());
- transitive = report.getTransitive().get(0);
- assertEquals("aba", transitive.getRef().name());
+ .direct(2)
+ .transitive(4)
+ .total(6)
+ .high(2)
+ .medium(3)
+ .low(1)
+ .dependencies(6),
+ TEST_SOURCE),
+ // Case 5: 0 direct issues, 2 transitive
+ Arguments.of(
+ Map.of(
+ "pkg:npm/aaa@1", new PackageItem("pkg:npm/aaa@1", null, List.of(buildIssue(1, 5f))),
+ "pkg:npm/aab@1",
+ new PackageItem("pkg:npm/aab@1", null, List.of(buildIssue(2, 7f)))),
+ tree().direct("aa").withTransitive("aaa").direct("ab").withTransitive("aab").build(),
+ new SourceSummary().direct(0).transitive(2).total(2).high(1).medium(1).dependencies(2),
+ TEST_SOURCE));
}
@Test
public void testSorted() throws IOException {
- Map> issues =
+ Map data =
Map.of(
- "pkg:npm/aa@1", List.of(buildIssue(1, 4f)),
- "pkg:npm/aaa@1", List.of(buildIssue(2, 3f)),
- "pkg:npm/aab@1", List.of(buildIssue(3, 1f)),
- "pkg:npm/ab@1", List.of(buildIssue(4, 2f)),
- "pkg:npm/aba@1", List.of(buildIssue(5, 3f)),
- "pkg:npm/abb@1", List.of(buildIssue(6, 9f)),
- "pkg:npm/abc@1", List.of(buildIssue(7, 6f)));
+ "pkg:npm/aa@1", new PackageItem("pkg:npm/aa@1", null, List.of(buildIssue(1, 4f))),
+ "pkg:npm/aaa@1", new PackageItem("pkg:npm/aaa@1", null, List.of(buildIssue(2, 3f))),
+ "pkg:npm/aab@1", new PackageItem("pkg:npm/aab@1", null, List.of(buildIssue(3, 1f))),
+ "pkg:npm/ab@1", new PackageItem("pkg:npm/ab@1", null, List.of(buildIssue(4, 2f))),
+ "pkg:npm/aba@1", new PackageItem("pkg:npm/aba@1", null, List.of(buildIssue(5, 3f))),
+ "pkg:npm/abb@1", new PackageItem("pkg:npm/abb@1", null, List.of(buildIssue(6, 9f))),
+ "pkg:npm/abc@1", new PackageItem("pkg:npm/abc@1", null, List.of(buildIssue(7, 6f))));
ProviderResponseHandler handler = new TestResponseHandler();
ProviderReport response =
handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- buildTree(),
- EMPTY_TRUSTED_CONTENT_RESPONSE);
+ Mockito.mock(Exchange.class), new ProviderResponse(data, null), buildTree());
assertOkStatus(response);
- DependencyReport reportHighest = getValidSource(response).getDependencies().get(0);
+ DependencyReport reportHighest = getValidSource(response, TEST_SOURCE).getDependencies().get(0);
assertEquals("ab", reportHighest.getRef().name());
assertEquals("abb", reportHighest.getTransitive().get(0).getRef().name());
assertEquals("abc", reportHighest.getTransitive().get(1).getRef().name());
assertEquals("aba", reportHighest.getTransitive().get(2).getRef().name());
- DependencyReport reportLowest = getValidSource(response).getDependencies().get(1);
+ DependencyReport reportLowest = getValidSource(response, TEST_SOURCE).getDependencies().get(1);
assertEquals("aa", reportLowest.getRef().name());
assertEquals("aaa", reportLowest.getTransitive().get(0).getRef().name());
assertEquals("aab", reportLowest.getTransitive().get(1).getRef().name());
@@ -249,236 +211,47 @@ public void testSorted() throws IOException {
@Test
public void testHighestVulnerabilityInDirectDependency() throws IOException {
- Map> issues =
- Map.of("pkg:npm/aa@1", List.of(buildIssue(1, 4f), buildIssue(2, 9f), buildIssue(3, 1f)));
+ Map data =
+ Map.of(
+ "pkg:npm/aa@1",
+ new PackageItem(
+ "pkg:npm/aa@1",
+ null,
+ List.of(buildIssue(1, 4f), buildIssue(2, 9f), buildIssue(3, 1f))));
ProviderResponseHandler handler = new TestResponseHandler();
ProviderReport response =
handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- buildTree(),
- EMPTY_TRUSTED_CONTENT_RESPONSE);
+ Mockito.mock(Exchange.class), new ProviderResponse(data, null), buildTree());
assertOkStatus(response);
- DependencyReport highest = getValidSource(response).getDependencies().get(0);
+ DependencyReport highest = getValidSource(response, TEST_SOURCE).getDependencies().get(0);
assertEquals("ISSUE-002", highest.getHighestVulnerability().getId());
assertEquals(9f, highest.getHighestVulnerability().getCvssScore());
}
@Test
public void testHighestVulnerabilityInTransitiveDependency() throws IOException {
- Map> issues =
+ Map data =
Map.of(
- "pkg:npm/aa@1", Collections.emptyList(),
- "pkg:npm/aaa@1", List.of(buildIssue(1, 4f), buildIssue(2, 9f), buildIssue(3, 1f)));
+ "pkg:npm/aa@1", new PackageItem("pkg:npm/aa@1", null, Collections.emptyList()),
+ "pkg:npm/aaa@1",
+ new PackageItem(
+ "pkg:npm/aaa@1",
+ null,
+ List.of(buildIssue(1, 4f), buildIssue(2, 9f), buildIssue(3, 1f))));
ProviderResponseHandler handler = new TestResponseHandler();
ProviderReport response =
handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- buildTree(),
- EMPTY_TRUSTED_CONTENT_RESPONSE);
+ Mockito.mock(Exchange.class), new ProviderResponse(data, null), buildTree());
assertOkStatus(response);
- DependencyReport highest = getValidSource(response).getDependencies().get(0);
+ DependencyReport highest = getValidSource(response, TEST_SOURCE).getDependencies().get(0);
assertEquals("ISSUE-002", highest.getHighestVulnerability().getId());
assertEquals(9f, highest.getHighestVulnerability().getCvssScore());
}
- @Test
- public void testRecommendations() throws IOException {
- PackageRef abRecommendation = new PackageRef("pkg:npm/ab@1-redhat-00001");
- PackageRef acRecommendation = new PackageRef("pkg:npm/ac@1-redhat-00006");
- Map recommendations =
- Map.of(
- new PackageRef("pkg:npm/ab@1"),
- new IndexedRecommendation(abRecommendation, Collections.emptyMap()),
- acRecommendation, // recommend the same should be ignored
- new IndexedRecommendation(acRecommendation, Collections.emptyMap()));
-
- Map> issues = Map.of("pkg:npm/aa@1", List.of(buildIssue(1, 4f)));
-
- ProviderResponseHandler handler = new TestResponseHandler();
-
- var report =
- handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- buildTree(),
- new TrustedContentResponse(recommendations, null));
- Source source = getValidSource(report);
-
- assertEquals(1, source.getSummary().getRecommendations());
- assertEquals(2, source.getDependencies().size());
- assertTrue(
- source.getDependencies().stream()
- .anyMatch(d -> d.getRef().name().equals("aa") && d.getRecommendation() == null));
- assertTrue(
- source.getDependencies().stream()
- .anyMatch(
- d ->
- d.getRef().name().equals("ab")
- && d.getRecommendation().equals(abRecommendation)));
- assertTrue(source.getDependencies().stream().noneMatch(d -> d.getRef().name().equals("ac")));
- }
-
- @Test
- public void testRecommendationsWithoutIssues() throws IOException {
- PackageRef abRecommendation = new PackageRef("pkg:npm/ab@1-redhat-00001");
- PackageRef acRecommendation = new PackageRef("pkg:npm/ac@1-redhat-00006");
- Map recommendations =
- Map.of(
- new PackageRef("pkg:npm/ab@1"),
- new IndexedRecommendation(abRecommendation, Collections.emptyMap()),
- acRecommendation, // recommend the same should be ignored
- new IndexedRecommendation(acRecommendation, Collections.emptyMap()));
-
- Map> issues = Collections.emptyMap();
-
- ProviderResponseHandler handler = new TestResponseHandler();
-
- var report =
- handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- buildTree(),
- new TrustedContentResponse(recommendations, null));
-
- assertNotNull(report);
- assertNotNull(report.getSources());
- Source source = report.getSources().get(TEST_PROVIDER);
- assertNotNull(source);
-
- assertEquals(1, source.getSummary().getRecommendations());
- assertEquals(1, source.getDependencies().size());
-
- assertTrue(
- source.getDependencies().stream()
- .anyMatch(
- d ->
- d.getRef().name().equals("ab")
- && d.getRecommendation().equals(abRecommendation)));
- assertTrue(source.getDependencies().stream().noneMatch(d -> d.getRef().name().equals("aa")));
- assertTrue(source.getDependencies().stream().noneMatch(d -> d.getRef().name().equals("ac")));
- }
-
- @Test
- public void testRemediations() throws IOException {
- PackageRef abRemediation = new PackageRef("pkg:npm/ab@1-redhat-00008");
- PackageRef aabRemediation = new PackageRef("pkg:npm/aab@1-redhat-00001");
- PackageRef acRemediation = new PackageRef("pkg:npm/ac@1-redhat-00006");
-
- Map recommendations =
- Map.of(
- new PackageRef("pkg:npm/aab@1"),
- new IndexedRecommendation(
- aabRemediation, Map.of("CVE-003", new Vulnerability("cve-003", "Fixed", "test"))),
- new PackageRef("pkg:npm/ab@1"),
- new IndexedRecommendation(
- abRemediation, Map.of("CVE-005", new Vulnerability("cve-005", "Affected", "test"))),
- new PackageRef("pkg:npm/ac@1-redhat-00006"),
- new IndexedRecommendation(
- acRemediation,
- Map.of(
- "CVE-007",
- new Vulnerability("cve-007", "Not Affected", "test"),
- "CVE-008",
- new Vulnerability("cve-008", null, "test"))));
- Map> issues =
- Map.of(
- "pkg:npm/aa@1", List.of(buildIssue(1, 4f)),
- "pkg:npm/aaa@1", List.of(buildIssue(2, 3f)),
- "pkg:npm/aab@1", List.of(buildIssue(3, 1f), buildIssue(4, 3f)),
- "pkg:npm/ab@1", List.of(buildIssue(5, 2f)),
- "pkg:npm/abb@1", List.of(buildIssue(6, 9f)),
- "pkg:npm/ac@1-redhat-00006", List.of(buildIssue(7, 6f), buildIssue(8, 5f)));
- ProviderResponseHandler handler = new TestResponseHandler();
-
- var report =
- handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, null),
- buildTree(),
- new TrustedContentResponse(recommendations, null));
- Source source = getValidSource(report);
- assertEquals(1, source.getSummary().getRecommendations());
- assertEquals(1, source.getSummary().getRemediations());
- assertEquals(3, source.getDependencies().size());
-
- // ab has an affected vuln so it should not contain the remediation
- var dep =
- source.getDependencies().stream().filter(d -> d.getRef().name().equals("ab")).findFirst();
- assertTrue(dep.isPresent());
- assertEquals(1, dep.get().getIssues().size());
- assertNull(dep.get().getIssues().get(0).getRemediation());
-
- // aab has a remediation for cve-003 but not for cve-004
- var tdep =
- source.getDependencies().stream()
- .filter(d -> d.getRef().name().equals("aa"))
- .map(DependencyReport::getTransitive)
- .flatMap(List::stream)
- .filter(t -> t.getRef().name().equals("aab"))
- .findFirst();
- assertTrue(tdep.isPresent());
- assertEquals(2, tdep.get().getIssues().size());
- assertNull(tdep.get().getIssues().get(0).getRemediation());
- assertEquals("CVE-004", tdep.get().getIssues().get(0).getCves().get(0));
- assertEquals(
- aabRemediation,
- tdep.get().getIssues().get(1).getRemediation().getTrustedContent().getRef());
- assertEquals("CVE-003", tdep.get().getIssues().get(1).getCves().get(0));
-
- // ac the provider reports a vulnerability but TC says it's Not Affected
- dep = source.getDependencies().stream().filter(d -> d.getRef().name().equals("ac")).findFirst();
- assertTrue(dep.isPresent());
- assertEquals(2, dep.get().getIssues().size());
- assertEquals("CVE-007", dep.get().getIssues().get(0).getCves().get(0));
- assertEquals("CVE-008", dep.get().getIssues().get(1).getCves().get(0));
- assertNull(dep.get().getIssues().get(0).getRemediation());
- }
-
- @Test
- void testUnscanned() throws IOException {
- Map> issues = Map.of("pkg:npm/aa@1", List.of(buildIssue(1, 5f)));
- ProviderResponseHandler handler = new TestResponseHandler();
- List unscanned =
- List.of(
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aab@1")).reason("aab"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aabbb@1")).reason("aabbb"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aabbc@1")).reason("aabbc"));
- DependencyTree tree = buildTree();
- ProviderReport response =
- handler.buildReport(
- Mockito.mock(Exchange.class),
- new ProviderResponse(issues, null, unscanned),
- tree,
- EMPTY_TRUSTED_CONTENT_RESPONSE);
- assertOkStatus(response);
- assertEquals(1, response.getSources().size());
- Source report = response.getSources().get(TEST_SOURCE);
- assertEquals(unscanned, report.getUnscanned());
- }
-
- @Test
- void testUnscannedResponse() {
- var unscanned =
- List.of(
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aa@1")).reason("aa"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aaa@1")).reason("aaa"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/aab@1")).reason("aab"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/ab@1")).reason("ab"),
- new UnscannedDependency().ref(new PackageRef("pkg:npm/abb@1")).reason("abb"));
- var handler = new TestResponseHandler();
- var response = handler.unscannedResponse(unscanned);
- assertNotNull(response);
- assertTrue(response.issues().isEmpty());
- assertNull(response.status());
- assertEquals(unscanned, response.unscanned());
- }
-
private void assertOkStatus(ProviderReport response) {
assertNotNull(response);
assertNotNull(response.getStatus());
@@ -489,10 +262,10 @@ private void assertOkStatus(ProviderReport response) {
assertEquals(TEST_PROVIDER, response.getStatus().getName());
}
- private Source getValidSource(ProviderReport report) {
+ private Source getValidSource(ProviderReport report, String sourceName) {
assertNotNull(report);
assertNotNull(report.getSources());
- Source source = report.getSources().get(TEST_SOURCE);
+ Source source = report.getSources().get(sourceName);
assertNotNull(source);
return source;
}
@@ -548,42 +321,78 @@ private static DependencyTree buildTree() {
return DependencyTree.builder().dependencies(direct).build();
}
- private static DependencyTree buildTreeWithDuplicates() {
- Map direct =
- Map.of(
- PackageRef.builder().name("aa").version("1").pkgManager(NPM_PURL_TYPE).build(),
+ private static DependencyTreeBuilder tree() {
+ return new DependencyTreeBuilder();
+ }
+
+ private static class DependencyTreeBuilder {
+ private final Map dependencies = new HashMap<>();
+ private PackageRef currentDirect;
+ private Set currentTransitive = new HashSet<>();
+
+ DependencyTreeBuilder direct(String name) {
+ return direct(name, "1");
+ }
+
+ DependencyTreeBuilder direct(String name, String version) {
+ // Save previous direct dependency if exists
+ if (currentDirect != null) {
+ dependencies.put(
+ currentDirect,
DirectDependency.builder()
- .ref(PackageRef.builder().name("aa").version("1").pkgManager(NPM_PURL_TYPE).build())
+ .ref(currentDirect)
.transitive(
- Set.of(
- PackageRef.builder()
- .name("aaa")
- .version("1")
- .pkgManager(NPM_PURL_TYPE)
- .build(),
- PackageRef.builder()
- .name("aab")
- .version("1")
- .pkgManager(NPM_PURL_TYPE)
- .build()))
- .build(),
- PackageRef.builder().name("ab").version("1").pkgManager(NPM_PURL_TYPE).build(),
+ currentTransitive.isEmpty()
+ ? Collections.emptySet()
+ : Set.copyOf(currentTransitive))
+ .build());
+ }
+ // Start new direct dependency
+ currentDirect =
+ PackageRef.builder().name(name).version(version).pkgManager(NPM_PURL_TYPE).build();
+ currentTransitive = new HashSet<>();
+ return this;
+ }
+
+ DependencyTreeBuilder withTransitive(String... names) {
+ if (currentDirect == null) {
+ throw new IllegalStateException("Must call direct() before withTransitive()");
+ }
+ for (String name : names) {
+ currentTransitive.add(
+ PackageRef.builder().name(name).version("1").pkgManager(NPM_PURL_TYPE).build());
+ }
+ return this;
+ }
+
+ DependencyTree build() {
+ // Save last direct dependency
+ if (currentDirect != null) {
+ dependencies.put(
+ currentDirect,
DirectDependency.builder()
- .ref(PackageRef.builder().name("ab").version("1").pkgManager(NPM_PURL_TYPE).build())
+ .ref(currentDirect)
.transitive(
- Set.of(
- PackageRef.builder()
- .name("aaa")
- .version("1")
- .pkgManager(NPM_PURL_TYPE)
- .build(),
- PackageRef.builder()
- .name("abb")
- .version("1")
- .pkgManager(NPM_PURL_TYPE)
- .build()))
+ currentTransitive.isEmpty()
+ ? Collections.emptySet()
+ : Set.copyOf(currentTransitive))
.build());
- return DependencyTree.builder().dependencies(direct).build();
+ }
+ return DependencyTree.builder().dependencies(dependencies).build();
+ }
+ }
+
+ /**
+ * @param ref - the recommendation reference
+ * @param cves - the vulnerabilities map CVE -> Status
+ * @return the tc recommendation
+ */
+ private static Recommendation buildRecommendation(String ref, Map cves) {
+ return new Recommendation(
+ new PackageRef(ref),
+ cves.entrySet().stream()
+ .map(e -> new Vulnerability(e.getKey(), e.getValue(), e.getValue() + " justification"))
+ .toList());
}
private static Issue buildIssue(int id, Float score) {
@@ -596,6 +405,19 @@ private static Issue buildIssue(int id, Float score) {
.cvssScore(score);
}
+ private static Issue buildIssueWithTcRemediation(int id, Float score, String remediation) {
+ var r = new Remediation();
+ r.trustedContent(new RemediationTrustedContent().ref(new PackageRef(remediation)));
+ return new Issue()
+ .id(String.format("ISSUE-00%d", id))
+ .title(String.format("ISSUE Example 00%d", id))
+ .source(TEST_SOURCE)
+ .severity(SeverityUtils.fromScore(score))
+ .cves(List.of(String.format("CVE-00%d", id)))
+ .cvssScore(score)
+ .remediation(r);
+ }
+
private static class TestResponseHandler extends ProviderResponseHandler {
@Override
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandlerTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandlerTest.java
index 908dbfaa..f6e67e91 100644
--- a/src/test/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandlerTest.java
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/providers/osv/OsvResponseHandlerTest.java
@@ -19,6 +19,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
@@ -32,6 +33,7 @@
import io.github.guacsec.trustifyda.api.PackageRef;
import io.github.guacsec.trustifyda.model.DependencyTree;
import io.github.guacsec.trustifyda.model.DirectDependency;
+import io.github.guacsec.trustifyda.model.PackageItem;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
@@ -57,29 +59,35 @@ void testVectors() throws IOException, URISyntaxException {
var report = handler.responseToIssues(providerResponse, dependencyTree);
- assertFalse(report.issues().isEmpty());
- assertEquals(2, report.issues().size());
- var jacksonIssues = report.issues().get(jacksonRef.ref());
+ assertFalse(report.pkgItems().isEmpty());
+ assertEquals(2, report.pkgItems().size());
+ PackageItem jacksonPackageItem = report.pkgItems().get(jacksonRef.ref());
+ assertNotNull(jacksonPackageItem);
+ var jacksonIssues = jacksonPackageItem.issues();
+ assertNotNull(jacksonIssues);
assertEquals(3, jacksonIssues.size());
// Test V3.1 vector.
var issue =
jacksonIssues.stream().filter(i -> i.getCves().contains("CVE-2022-42004")).findFirst();
assertTrue(issue.isPresent());
- assertEquals(7.5f, issue.get().getCvssScore());
- assertEquals("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", issue.get().getCvss().getCvss());
+ var issueV31 = issue.get();
+ assertEquals(7.5f, issueV31.getCvssScore());
+ assertEquals("CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", issueV31.getCvss().getCvss());
// Test V3.0 vector.
issue = jacksonIssues.stream().filter(i -> i.getCves().contains("CVE-2022-42003")).findFirst();
assertTrue(issue.isPresent());
- assertEquals(7.5f, issue.get().getCvssScore());
- assertEquals("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", issue.get().getCvss().getCvss());
+ var issueV30 = issue.get();
+ assertEquals(7.5f, issueV30.getCvssScore());
+ assertEquals("CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H", issueV30.getCvss().getCvss());
// Test V2.0 vector.
issue = jacksonIssues.stream().filter(i -> i.getCves().contains("CVE-2020-36518")).findFirst();
assertTrue(issue.isPresent());
- assertEquals(5.0f, issue.get().getCvssScore());
- assertEquals("AV:N/AC:L/Au:N/C:N/I:N/A:P", issue.get().getCvss().getCvss());
+ var issueV20 = issue.get();
+ assertEquals(5.0f, issueV20.getCvssScore());
+ assertEquals("AV:N/AC:L/Au:N/C:N/I:N/A:P", issueV20.getCvss().getCvss());
}
private byte[] getProviderResponse(String fileName) throws IOException, URISyntaxException {
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregationTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregationTest.java
new file mode 100644
index 00000000..b70f09b9
--- /dev/null
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregationTest.java
@@ -0,0 +1,386 @@
+/*
+ * Copyright 2023-2025 Trustify Dependency Analytics Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.github.guacsec.trustifyda.integration.providers.trustify;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+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;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import io.github.guacsec.trustifyda.api.PackageRef;
+import io.github.guacsec.trustifyda.api.v5.Issue;
+import io.github.guacsec.trustifyda.api.v5.RemediationTrustedContent;
+import io.github.guacsec.trustifyda.api.v5.SeverityUtils;
+import io.github.guacsec.trustifyda.model.PackageItem;
+import io.github.guacsec.trustifyda.model.ProviderResponse;
+import io.github.guacsec.trustifyda.model.trustify.IndexedRecommendation;
+import io.github.guacsec.trustifyda.model.trustify.Vulnerability;
+
+public class RecommendationAggregationTest {
+
+ private final RecommendationAggregation aggregation = new RecommendationAggregation();
+
+ private record TestCase(
+ String name, Object oldMessage, Object newMessage, Consumer verifier) {}
+
+ static Stream provideTestCases() {
+ return Stream.of(
+ // ProviderResponse in oldExchange, recommendations in newExchange, no issues
+ Arguments.of(
+ "ProviderResponseInOldExchange_RecommendationsInNewExchange_NoIssues",
+ new TestCase(
+ "ProviderResponseInOldExchange_RecommendationsInNewExchange_NoIssues",
+ new ProviderResponse(new HashMap<>(), null),
+ createRecommendations(
+ "pkg:npm/package1@1.0.0", "pkg:npm/package1@2.0.0", Collections.emptyMap()),
+ result -> {
+ assertEquals(1, result.pkgItems().size());
+ assertTrue(result.pkgItems().containsKey("pkg:npm/package1@1.0.0"));
+ PackageItem item = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(item.recommendation());
+ assertTrue(item.issues().isEmpty());
+ })),
+
+ // ProviderResponse in newExchange, recommendations in oldExchange, no issues
+ Arguments.of(
+ "ProviderResponseInNewExchange_RecommendationsInOldExchange_NoIssues",
+ new TestCase(
+ "ProviderResponseInNewExchange_RecommendationsInOldExchange_NoIssues",
+ createRecommendations(
+ "pkg:npm/package1@1.0.0", "pkg:npm/package1@2.0.0", Collections.emptyMap()),
+ new ProviderResponse(new HashMap<>(), null),
+ result -> {
+ assertEquals(1, result.pkgItems().size());
+ assertTrue(result.pkgItems().containsKey("pkg:npm/package1@1.0.0"));
+ PackageItem item = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(item.recommendation());
+ assertTrue(item.issues().isEmpty());
+ })),
+
+ // Issues but no recommendations
+ Arguments.of(
+ "WithIssuesButNoRecommendations",
+ new TestCase(
+ "WithIssuesButNoRecommendations",
+ createProviderResponseWithIssues(List.of(createIssue("CVE-001", "Issue 1", 5.0f))),
+ null,
+ result -> {
+ assertEquals(1, result.pkgItems().size());
+ PackageItem resultItem = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(resultItem);
+ assertEquals(1, resultItem.issues().size());
+ assertNull(resultItem.recommendation());
+ })),
+
+ // Recommendations but no issues
+ Arguments.of(
+ "WithRecommendationsButNoIssues",
+ new TestCase(
+ "WithRecommendationsButNoIssues",
+ new ProviderResponse(new HashMap<>(), null),
+ createRecommendations(
+ "pkg:npm/package1@1.0.0",
+ "pkg:npm/package1@2.0.0",
+ Map.of(
+ "CVE-001", new Vulnerability("CVE-001", "Fixed", "Fixed justification"))),
+ result -> {
+ assertEquals(1, result.pkgItems().size());
+ PackageItem resultItem = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(resultItem);
+ assertTrue(resultItem.issues().isEmpty());
+ assertNotNull(resultItem.recommendation());
+ assertEquals(
+ "pkg:npm/package1@2.0.0", resultItem.recommendation().packageName().ref());
+ })),
+
+ // Issues and recommendations, but no matching vulnerabilities
+ Arguments.of(
+ "WithIssuesAndRecommendations_NoMatchingVulnerabilities",
+ new TestCase(
+ "WithIssuesAndRecommendations_NoMatchingVulnerabilities",
+ createProviderResponseWithIssues(List.of(createIssue("CVE-001", "Issue 1", 5.0f))),
+ createRecommendations(
+ "pkg:npm/package1@1.0.0",
+ "pkg:npm/package1@2.0.0",
+ Map.of(
+ "CVE-002", new Vulnerability("CVE-002", "Fixed", "Fixed justification"))),
+ result -> {
+ assertEquals(1, result.pkgItems().size());
+ PackageItem resultItem = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(resultItem);
+ assertEquals(1, resultItem.issues().size());
+ assertNull(resultItem.issues().get(0).getRemediation());
+ assertNotNull(resultItem.recommendation());
+ })),
+
+ // Issues and recommendations with matching vulnerabilities (remediations)
+ Arguments.of(
+ "WithIssuesAndRecommendations_WithRemediations",
+ new TestCase(
+ "WithIssuesAndRecommendations_WithRemediations",
+ createProviderResponseWithIssues(
+ List.of(
+ createIssue("CVE-001", "Issue 1", 5.0f),
+ createIssue("CVE-002", "Issue 2", 7.0f))),
+ createRecommendations(
+ "pkg:npm/package1@1.0.0",
+ "pkg:npm/package1@2.0.0",
+ Map.of(
+ "CVE-001",
+ new Vulnerability("CVE-001", "Fixed", "Fixed justification"),
+ "CVE-002",
+ new Vulnerability("CVE-002", "NotAffected", "Not affected"))),
+ result -> {
+ assertEquals(1, result.pkgItems().size());
+ PackageItem resultItem = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(resultItem);
+ assertEquals(2, resultItem.issues().size());
+
+ // CVE-001 should have remediation
+ Issue issue1Result =
+ resultItem.issues().stream()
+ .filter(i -> "CVE-001".equals(i.getId()))
+ .findFirst()
+ .orElseThrow();
+ var remediation1 = issue1Result.getRemediation();
+ assertNotNull(remediation1);
+ RemediationTrustedContent tcRemediation = remediation1.getTrustedContent();
+ assertNotNull(tcRemediation);
+ var ref = tcRemediation.getRef();
+ assertNotNull(ref);
+ assertEquals("pkg:npm/package1@2.0.0", ref.ref());
+ assertEquals("Fixed", tcRemediation.getStatus());
+ assertEquals("Fixed justification", tcRemediation.getJustification());
+
+ // CVE-002 should have remediation (even though status is "NotAffected")
+ Issue issue2Result =
+ resultItem.issues().stream()
+ .filter(i -> "CVE-002".equals(i.getId()))
+ .findFirst()
+ .orElseThrow();
+ var remediation2 = issue2Result.getRemediation();
+ assertNotNull(remediation2);
+ RemediationTrustedContent tcRemediation2 = remediation2.getTrustedContent();
+ assertNotNull(tcRemediation2);
+ assertEquals("NotAffected", tcRemediation2.getStatus());
+
+ assertNotNull(resultItem.recommendation());
+ })),
+
+ // Multiple packages
+ Arguments.of(
+ "WithMultiplePackages",
+ new TestCase(
+ "WithMultiplePackages",
+ createProviderResponseWithMultiplePackages(),
+ createMultipleRecommendations(),
+ result -> {
+ assertEquals(3, result.pkgItems().size());
+
+ // Package1: has issue with remediation
+ PackageItem item1Result = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(item1Result);
+ assertEquals(1, item1Result.issues().size());
+ assertNotNull(item1Result.issues().get(0).getRemediation());
+ assertNotNull(item1Result.recommendation());
+
+ // Package2: has issue without remediation (CVE doesn't match)
+ PackageItem item2Result = result.pkgItems().get("pkg:npm/package2@2.0.0");
+ assertNotNull(item2Result);
+ assertEquals(1, item2Result.issues().size());
+ assertNull(item2Result.issues().get(0).getRemediation());
+ assertNotNull(item2Result.recommendation());
+
+ // Package3: no issues, only recommendation
+ PackageItem item3Result = result.pkgItems().get("pkg:npm/package3@3.0.0");
+ assertNotNull(item3Result);
+ assertTrue(item3Result.issues().isEmpty());
+ assertNotNull(item3Result.recommendation());
+ })),
+
+ // Null pkgItems
+ Arguments.of(
+ "WithNullPkgItems",
+ new TestCase(
+ "WithNullPkgItems",
+ new ProviderResponse(null, null),
+ createRecommendations(
+ "pkg:npm/package1@1.0.0", "pkg:npm/package1@2.0.0", Collections.emptyMap()),
+ result -> {
+ assertNotNull(result.pkgItems());
+ assertEquals(1, result.pkgItems().size());
+ })),
+
+ // Empty recommendations
+ Arguments.of(
+ "WithEmptyRecommendations",
+ new TestCase(
+ "WithEmptyRecommendations",
+ createProviderResponseWithIssues(List.of(createIssue("CVE-001", "Issue 1", 5.0f))),
+ Collections.emptyMap(),
+ result -> {
+ assertEquals(1, result.pkgItems().size());
+ PackageItem resultItem = result.pkgItems().get("pkg:npm/package1@1.0.0");
+ assertNotNull(resultItem);
+ assertEquals(1, resultItem.issues().size());
+ assertNull(resultItem.recommendation());
+ })));
+ }
+
+ private static Map createRecommendations(
+ String packageRef, String recommendedPackage, Map vulnerabilities) {
+ Map recommendations = new HashMap<>();
+ PackageRef recPkgRef = new PackageRef(packageRef);
+ IndexedRecommendation recommendation =
+ new IndexedRecommendation(new PackageRef(recommendedPackage), vulnerabilities);
+ recommendations.put(recPkgRef, recommendation);
+ return recommendations;
+ }
+
+ private static Map createMultipleRecommendations() {
+ Map recommendations = new HashMap<>();
+ // Recommendation for package1 with matching vulnerability
+ PackageRef recPkgRef1 = new PackageRef("pkg:npm/package1@1.0.0");
+ Map vulnerabilities1 = new HashMap<>();
+ vulnerabilities1.put("CVE-001", new Vulnerability("CVE-001", "Fixed", "Fixed justification"));
+ IndexedRecommendation recommendation1 =
+ new IndexedRecommendation(new PackageRef("pkg:npm/package1@2.0.0"), vulnerabilities1);
+ recommendations.put(recPkgRef1, recommendation1);
+
+ // Recommendation for package2 without matching vulnerability
+ PackageRef recPkgRef2 = new PackageRef("pkg:npm/package2@2.0.0");
+ Map vulnerabilities2 = new HashMap<>();
+ vulnerabilities2.put("CVE-003", new Vulnerability("CVE-003", "Fixed", "Fixed justification"));
+ IndexedRecommendation recommendation2 =
+ new IndexedRecommendation(new PackageRef("pkg:npm/package2@3.0.0"), vulnerabilities2);
+ recommendations.put(recPkgRef2, recommendation2);
+
+ // Recommendation for package3 (no issues)
+ PackageRef recPkgRef3 = new PackageRef("pkg:npm/package3@3.0.0");
+ IndexedRecommendation recommendation3 =
+ new IndexedRecommendation(new PackageRef("pkg:npm/package3@4.0.0"), Collections.emptyMap());
+ recommendations.put(recPkgRef3, recommendation3);
+ return recommendations;
+ }
+
+ private static ProviderResponse createProviderResponseWithIssues(List issues) {
+ Map pkgItems = new HashMap<>();
+ PackageItem item1 = new PackageItem("pkg:npm/package1@1.0.0", null, issues);
+ pkgItems.put("pkg:npm/package1@1.0.0", item1);
+ return new ProviderResponse(pkgItems, null);
+ }
+
+ private static ProviderResponse createProviderResponseWithMultiplePackages() {
+ Map pkgItems = new HashMap<>();
+ Issue issue1 = createIssue("CVE-001", "Issue 1", 5.0f);
+ PackageItem item1 = new PackageItem("pkg:npm/package1@1.0.0", null, List.of(issue1));
+ pkgItems.put("pkg:npm/package1@1.0.0", item1);
+
+ Issue issue2 = createIssue("CVE-002", "Issue 2", 7.0f);
+ PackageItem item2 = new PackageItem("pkg:npm/package2@2.0.0", null, List.of(issue2));
+ pkgItems.put("pkg:npm/package2@2.0.0", item2);
+
+ return new ProviderResponse(pkgItems, null);
+ }
+
+ private static Issue createIssue(String id, String title, float cvssScore) {
+ Issue issue = new Issue().id(id).title(title).cvssScore(cvssScore);
+ issue.setSeverity(SeverityUtils.fromScore(cvssScore));
+ return issue;
+ }
+
+ @Test
+ void testWithNullOldExchange() {
+ Exchange newExchange = mock(Exchange.class);
+ Exchange result = aggregation.aggregate(null, newExchange);
+ assertEquals(newExchange, result);
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("provideTestCases")
+ void testAggregation(String name, TestCase testCase) {
+ Exchange oldExchange = mock(Exchange.class);
+ Exchange newExchange = mock(Exchange.class);
+ Message oldMessage = createMessageWithBodyStorage();
+ Message newMessage = mock(Message.class);
+
+ // Setup messages with the provided objects
+ if (testCase.oldMessage != null) {
+ oldMessage.setBody(testCase.oldMessage);
+ }
+ when(oldExchange.getIn()).thenReturn(oldMessage);
+ when(newExchange.getIn()).thenReturn(newMessage);
+ when(newMessage.getBody()).thenReturn(testCase.newMessage);
+
+ Exchange result = aggregation.aggregate(oldExchange, newExchange);
+
+ assertNotNull(result);
+ ProviderResponse resultResponse = result.getIn().getBody(ProviderResponse.class);
+ assertNotNull(resultResponse);
+ testCase.verifier().accept(resultResponse);
+ }
+
+ /** Creates a mock Message that actually stores and retrieves the body. */
+ @SuppressWarnings("unchecked")
+ private Message createMessageWithBodyStorage() {
+ Message message = mock(Message.class);
+ Object[] bodyStorage = new Object[1];
+
+ doAnswer(
+ invocation -> {
+ bodyStorage[0] = invocation.getArgument(0);
+ return null;
+ })
+ .when(message)
+ .setBody(any());
+
+ doAnswer(
+ invocation -> {
+ Class> type = (Class>) invocation.getArgument(0);
+ Object body = bodyStorage[0];
+ if (body != null && type.isInstance(body)) {
+ return type.cast(body);
+ }
+ return body;
+ })
+ .when(message)
+ .getBody(any(Class.class));
+
+ when(message.getBody()).thenAnswer(invocation -> bodyStorage[0]);
+
+ return message;
+ }
+}
diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandlerTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandlerTest.java
index 3d245951..0b5c2e32 100644
--- a/src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandlerTest.java
+++ b/src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/TrustifyResponseHandlerTest.java
@@ -20,28 +20,45 @@
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.ArgumentCaptor.forClass;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import java.io.IOException;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.camel.Exchange;
+import org.apache.camel.Message;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.github.guacsec.trustifyda.api.PackageRef;
import io.github.guacsec.trustifyda.api.v5.Issue;
import io.github.guacsec.trustifyda.api.v5.Severity;
+import io.github.guacsec.trustifyda.integration.Constants;
+import io.github.guacsec.trustifyda.integration.providers.trustify.ubi.UBIRecommendation;
import io.github.guacsec.trustifyda.model.DependencyTree;
import io.github.guacsec.trustifyda.model.DirectDependency;
+import io.github.guacsec.trustifyda.model.PackageItem;
import io.github.guacsec.trustifyda.model.ProviderResponse;
+import io.github.guacsec.trustifyda.model.trustify.IndexedRecommendation;
public class TrustifyResponseHandlerTest {
private TrustifyResponseHandler handler;
private DependencyTree dependencyTree;
+ private TrustifyIntegration trustifyIntegration;
+ private UBIRecommendation ubiRecommendation;
+
@BeforeEach
void setUp() {
handler = new TrustifyResponseHandler();
@@ -52,6 +69,19 @@ void setUp() {
var directDep = new DirectDependency(packageRef, Collections.emptySet());
var dependencies = Collections.singletonMap(packageRef, directDep);
dependencyTree = new DependencyTree(dependencies);
+
+ // Setup for TrustifyIntegration.processRecommendations() tests
+ trustifyIntegration = new TrustifyIntegration();
+ trustifyIntegration.mapper = new ObjectMapper();
+
+ // Create a mock UBIRecommendation
+ ubiRecommendation = mock(UBIRecommendation.class);
+ Map mapping = new HashMap<>();
+ mapping.put("alpine", "pkg:oci/ubi@0.0.2");
+ when(ubiRecommendation.mapping()).thenReturn(mapping);
+ when(ubiRecommendation.purl()).thenReturn(Collections.emptyMap());
+ when(ubiRecommendation.catalogurl()).thenReturn(Collections.emptyMap());
+ trustifyIntegration.ubiRecommendation = ubiRecommendation;
}
@Test
@@ -161,10 +191,12 @@ void testResponseToIssuesWithValidData() throws IOException {
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
assertNotNull(result);
- assertNotNull(result.issues());
- assertEquals(1, result.issues().size());
+ assertNotNull(result.pkgItems());
+ assertEquals(1, result.pkgItems().size());
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
assertNotNull(issues);
assertEquals(2, issues.size());
@@ -242,7 +274,9 @@ void testResponseToIssuesWithMultipleScoreTypes() throws IOException {
byte[] responseBytes = jsonResponse.getBytes();
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
Issue issue = issues.get(0);
// Should prioritize V4 based on SCORE_TYPE_ORDER
@@ -258,8 +292,8 @@ void testResponseToIssuesWithEmptyResponse() throws IOException {
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
assertNotNull(result);
- assertNotNull(result.issues());
- assertTrue(result.issues().isEmpty());
+ assertNotNull(result.pkgItems());
+ assertTrue(result.pkgItems().isEmpty());
}
@Test
@@ -275,7 +309,9 @@ void testResponseToIssuesWithEmptyVulnerabilityArray() throws IOException {
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
assertNotNull(result);
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
assertTrue(issues.isEmpty());
}
@@ -297,7 +333,9 @@ void testResponseToIssuesWithMissingStatusField() throws IOException {
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
assertNotNull(result);
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
assertTrue(issues.isEmpty());
}
@@ -320,7 +358,9 @@ void testResponseToIssuesWithMissingAffectedField() throws IOException {
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
assertNotNull(result);
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
assertTrue(issues.isEmpty());
}
@@ -358,7 +398,9 @@ void testResponseToIssuesWithNoScores() throws IOException {
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
assertNotNull(result);
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
assertTrue(issues.isEmpty());
}
@@ -406,7 +448,9 @@ void testResponseToIssuesWithFallbackToDescription() throws IOException {
byte[] responseBytes = jsonResponse.getBytes();
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
assertEquals(1, issues.size());
Issue issue = issues.get(0);
@@ -467,7 +511,9 @@ void testResponseToIssuesWithMultipleFixedVersions() throws IOException {
byte[] responseBytes = jsonResponse.getBytes();
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
- List issues = result.issues().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ PackageItem packageItem = result.pkgItems().get("pkg:maven/org.postgresql/postgresql@42.5.0");
+ assertNotNull(packageItem);
+ List issues = packageItem.issues();
assertEquals(1, issues.size());
Issue issue = issues.get(0);
@@ -525,6 +571,123 @@ void testResponseToIssuesWithDependencyNotInTree() throws IOException {
ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree);
assertNotNull(result);
- assertTrue(result.issues().isEmpty());
+ assertTrue(result.pkgItems().isEmpty());
+ }
+
+ @Test
+ void testProcessRecommendationsAggregation() throws IOException {
+ Exchange exchange = mock(Exchange.class);
+ Message inMessage = mock(Message.class);
+ Message outMessage = mock(Message.class);
+
+ when(exchange.getIn()).thenReturn(inMessage);
+ when(exchange.getMessage()).thenReturn(outMessage);
+ when(exchange.getProperty(Constants.SBOM_ID_PROPERTY, String.class)).thenReturn(null);
+
+ byte[] responseBytes =
+ getClass()
+ .getClassLoader()
+ .getResourceAsStream("__files/trustedcontent/simple.json")
+ .readAllBytes();
+ when(inMessage.getBody(byte[].class)).thenReturn(responseBytes);
+
+ trustifyIntegration.processRecommendations(exchange);
+
+ @SuppressWarnings("rawtypes")
+ ArgumentCaptor