From 612a655886bd0a9df16aedd3360810201496517d Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Fri, 7 Nov 2025 22:06:16 +0100 Subject: [PATCH 1/6] feat: trustify recommendations Signed-off-by: Ruben Romero Montes --- .../trustifyda/integration/Constants.java | 4 +- .../backend/ExhortIntegration.java | 4 - .../ProviderAggregationStrategy.java | 6 +- .../providers/ProviderResponseHandler.java | 394 +++++----- .../providers/osv/OsvResponseHandler.java | 12 +- .../trustify/RecommendationAggregation.java | 125 +++ .../trustify/TrustifyIntegration.java | 140 +++- .../trustify/TrustifyResponseHandler.java | 12 +- .../trustify}/ubi/UBIRecommendation.java | 2 +- .../integration/report/ReportTemplate.java | 2 +- .../trustedcontent/TcResponseAggregation.java | 91 --- .../trustedcontent/TcResponseHandler.java | 172 ----- .../TrustedContentIntegration.java | 87 --- .../TrustedContentRequestBuilder.java | 102 --- .../guacsec/trustifyda/model/PackageItem.java | 25 + .../trustifyda/model/ProviderResponse.java | 6 +- .../service/DynamicOidcClientService.java | 2 +- src/main/resources/application.properties | 4 +- .../extensions/OidcWiremockExtension.java | 10 + .../integration/AbstractAnalysisTest.java | 29 +- .../trustifyda/integration/AnalysisTest.java | 100 +-- .../integration/HtmlReportTest.java | 13 +- .../integration/TokenValidationTest.java | 3 + .../ProviderResponseHandlerTest.java | 569 +++++--------- .../providers/osv/OsvResponseHandlerTest.java | 26 +- .../RecommendationAggregationTest.java | 386 ++++++++++ .../trustify/TrustifyResponseHandlerTest.java | 192 ++++- .../trustedcontent/TcResponseHandlerTest.java | 152 ---- .../TrustedContentRequestBuilderTest.java | 166 ---- ...eport_all_token.json => batch_report.json} | 150 ++-- .../{report_all_token.json => report.json} | 105 ++- .../reports/report_all_no_snyk_token.json | 20 - .../__files/reports/report_default_token.json | 712 ------------------ .../__files/trustedcontent/maven_report.json | 92 +-- ui/src/api/report.ts | 6 - ui/src/mocks/reportDocker.mock.ts | 8 +- 36 files changed, 1430 insertions(+), 2499 deletions(-) create mode 100644 src/main/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregation.java rename src/main/java/io/github/guacsec/trustifyda/integration/{trustedcontent => providers/trustify}/ubi/UBIRecommendation.java (92%) delete mode 100644 src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseAggregation.java delete mode 100644 src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandler.java delete mode 100644 src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentIntegration.java delete mode 100644 src/main/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilder.java create mode 100644 src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java create mode 100644 src/test/java/io/github/guacsec/trustifyda/integration/providers/trustify/RecommendationAggregationTest.java delete mode 100644 src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandlerTest.java delete mode 100644 src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilderTest.java rename src/test/resources/__files/reports/{batch_report_all_token.json => batch_report.json} (88%) rename src/test/resources/__files/reports/{report_all_token.json => report.json} (88%) delete mode 100644 src/test/resources/__files/reports/report_all_no_snyk_token.json delete mode 100644 src/test/resources/__files/reports/report_default_token.json 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..269b1c95 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,7 +69,6 @@ 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"; @@ -78,7 +76,7 @@ private Constants() {} 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/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..1655f4f5 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; @@ -87,46 +84,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; } @@ -188,7 +172,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,102 +223,143 @@ private static String prettifyHttpError(HttpOperationFailedException httpExcepti public ProviderResponse emptyResponse( @ExchangeProperty(Constants.DEPENDENCY_TREE_PROPERTY) DependencyTree tree) { - return new ProviderResponse(Collections.emptyMap(), null, null); - } - - public ProviderResponse unscannedResponse( - @ExchangeProperty(Constants.UNSCANNED_REFS_PROPERTY) List unscanned) { - return new ProviderResponse(Collections.emptyMap(), null, unscanned); + return new ProviderResponse(Collections.emptyMap(), null); } - 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()); + var packageRef = depEntry.getKey(); + processedRefs.add(packageRef.ref()); + var packageItem = getPackageItem(packageRef, pkgItemsData); + var directReport = new DependencyReport().ref(packageRef); + + // Set issues if available + 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)); + } + + // Set recommendation if available (extract PackageRef from TcRecommendation) + if (packageItem != null + && packageItem.recommendation() != null + && packageItem.recommendation().packageName() != null) { + directReport.recommendation(packageItem.recommendation().packageName()); } - directReport.setHighestVulnerability( - directReport.getIssues().stream().findFirst().orElse(null)); + 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 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()); + } var highestTransitive = transitiveIssues.stream().findFirst(); if (highestTransitive.isPresent()) { if (directReport.getHighestVulnerability() == null @@ -343,10 +368,14 @@ private Source buildReportForSource( directReport.setHighestVulnerability(highestTransitive.get()); } } - return new TransitiveDependencyReport() - .ref(t) - .issues(transitiveIssues) - .highestVulnerability(highestTransitive.orElse(null)); + 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; }) .filter(transitiveReport -> !transitiveReport.getIssues().isEmpty()) .collect(Collectors.toList()); @@ -358,77 +387,47 @@ private Source buildReportForSource( } }); - sourceReport.sort(Collections.reverseOrder(new DependencyScoreComparator())); - var summary = buildSummary(issuesData, tree, sourceReport, unscanned); - return new Source().summary(summary).dependencies(sourceReport).unscanned(unscanned); - } - - 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 boolean isAffectedTrustedContent( - PackageRef ref, Issue issue, IndexedRecommendation recommendation) { - if (recommendation == null) { - return false; - } - 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)); - } - return !isAffected(vuln); - } - - 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; - } - if (i.getRemediation() == null) { - i.remediation(new Remediation()); + // Process packages with recommendations-only that are not in the tree + // (these are recommendations for packages that might not be direct dependencies) + if (pkgItemsData != null) { + 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 + } + }); } - i.getRemediation() - .setTrustedContent( - new RemediationTrustedContent() - .justification(vuln.getJustification()) - .status(vuln.getStatus()) - .ref(recommendation.packageName())); - return i; + sourceReport.sort(Collections.reverseOrder(new DependencyScoreComparator())); + var summary = buildSummary(pkgItemsData, tree, sourceReport); + return new Source().summary(summary).dependencies(sourceReport); } - private boolean isAffected(Vulnerability vuln) { - if (vuln == null || vuln.getStatus() == null) { - return true; - } - 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 +437,39 @@ 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); + if (i.getSeverity() != null) { + 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(); + } + }); } // The number of vulnerabilities is the total count of public CVEs @@ -489,8 +493,7 @@ private static final record VulnerabilityCounter( AtomicInteger direct, AtomicInteger dependencies, AtomicInteger remediations, - AtomicInteger recommendations, - AtomicInteger unscanned) { + AtomicInteger recommendations) { VulnerabilityCounter() { this( @@ -502,7 +505,6 @@ private static final record VulnerabilityCounter( new AtomicInteger(), new AtomicInteger(), new AtomicInteger(), - new AtomicInteger(), new AtomicInteger()); } @@ -517,9 +519,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..1125e7f5 --- /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.trustedcontent.IndexedRecommendation; +import io.github.guacsec.trustifyda.model.trustedcontent.TcRecommendation; + +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 tcRecommendation = toTcRecommendation(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 tcRemediation = + new RemediationTrustedContent() + .ref(value.packageName()) + .status(vuln.getStatus()) + .justification(vuln.getJustification()); + issue.remediation(new Remediation().trustedContent(tcRemediation)); + }); + } + providerResponse + .pkgItems() + .put(key.ref(), new PackageItem(key.ref(), tcRecommendation, issues)); + }); + } + + private TcRecommendation toTcRecommendation(IndexedRecommendation recommendation) { + if (recommendation.vulnerabilities() == null) { + return new TcRecommendation(recommendation.packageName(), Collections.emptyList()); + } + return new TcRecommendation( + 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..21e0c168 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,9 +31,17 @@ 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.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.Vulnerability; import io.github.guacsec.trustifyda.model.trustify.ProviderConfig; import io.github.guacsec.trustifyda.model.trustify.ProvidersConfig; import io.github.guacsec.trustifyda.service.DynamicOidcClientService; @@ -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 recommendations = mapper.readValue(tcResponse, Recommendations.class); + var mergedRecommendations = indexRecommendations(recommendations); + mergedRecommendations.putAll(getUBIRecommendation(sbomId)); + + exchange.getMessage().setBody(mergedRecommendations); + } + + private Map indexRecommendations( + Recommendations recommendations) { + Map result = new HashMap<>(); + if (recommendations == null) { + return result; + } + recommendations.getMatchings().entrySet().stream() + .filter(e -> !e.getValue().isEmpty()) + .forEach( + e -> { + List tcRecommendations = e.getValue(); + PackageRef pkgRef = tcRecommendations.get(0).packageName(); + Map vulnerabilities = + tcRecommendations.stream() + .map(TcRecommendation::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/ReportTemplate.java b/src/main/java/io/github/guacsec/trustifyda/integration/report/ReportTemplate.java index 96a6c1e5..dc421f7a 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; 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/PackageItem.java b/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java new file mode 100644 index 00000000..6534827a --- /dev/null +++ b/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java @@ -0,0 +1,25 @@ +/* + * 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; + +import java.util.List; + +import io.github.guacsec.trustifyda.api.v5.Issue; +import io.github.guacsec.trustifyda.model.trustedcontent.TcRecommendation; + +public record PackageItem(String packageRef, TcRecommendation 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/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..1b508759 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/ diff --git a/src/test/java/io/github/guacsec/trustifyda/extensions/OidcWiremockExtension.java b/src/test/java/io/github/guacsec/trustifyda/extensions/OidcWiremockExtension.java index bebfc9d3..f8bde60c 100644 --- a/src/test/java/io/github/guacsec/trustifyda/extensions/OidcWiremockExtension.java +++ b/src/test/java/io/github/guacsec/trustifyda/extensions/OidcWiremockExtension.java @@ -41,6 +41,7 @@ public Map 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..5542c469 100644 --- a/src/test/java/io/github/guacsec/trustifyda/integration/HtmlReportTest.java +++ b/src/test/java/io/github/guacsec/trustifyda/integration/HtmlReportTest.java @@ -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 = @@ -291,17 +291,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/providers/ProviderResponseHandlerTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandlerTest.java index e03476b6..ca9b4039 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,15 +41,16 @@ 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.TcRecommendation; import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentResponse; import io.github.guacsec.trustifyda.model.trustedcontent.Vulnerability; @@ -65,21 +67,14 @@ public class ProviderResponseHandlerTest { @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 +86,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 +214,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 +265,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 +324,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 TcRecommendation buildRecommendation(String ref, Map cves) { + return new TcRecommendation( + 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 +408,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..f5b8894b --- /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.trustedcontent.IndexedRecommendation; +import io.github.guacsec.trustifyda.model.trustedcontent.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..51a306ef 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,22 +20,36 @@ 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.trustedcontent.IndexedRecommendation; public class TrustifyResponseHandlerTest { @@ -52,6 +66,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 +188,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 +271,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 +289,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 +306,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 +330,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 +355,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 +395,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 +445,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 +508,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 +568,129 @@ void testResponseToIssuesWithDependencyNotInTree() throws IOException { ProviderResponse result = handler.responseToIssues(responseBytes, dependencyTree); assertNotNull(result); - assertTrue(result.issues().isEmpty()); + assertTrue(result.pkgItems().isEmpty()); } + + // Tests for TrustifyIntegration.processRecommendations() + // These tests were migrated from TcResponseHandlerTest + + private TrustifyIntegration trustifyIntegration; + private UBIRecommendation ubiRecommendation; + + @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 bodyCaptor = forClass(Map.class); + verify(outMessage).setBody(bodyCaptor.capture()); + @SuppressWarnings("unchecked") + Map recommendations = bodyCaptor.getValue(); + assertNotNull(recommendations); + assertEquals(3, recommendations.size()); + + Map expectations = new HashMap<>(); + expectations.put( + "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5?type=jar", + new ExpectedRecommendation( + "1.2.5.redhat-00003", Set.of("CVE-2023-2974", "CVE-2023-1584", "CVE-2023-28867"))); + expectations.put( + "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final?type=jar", + new ExpectedRecommendation( + "2.13.8.Final-redhat-00004", + Set.of("CVE-2020-36518", "CVE-2023-44487", "CVE-2023-4853"))); + expectations.put( + "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", + new ExpectedRecommendation("2.13.4.2-redhat-00001", Collections.emptySet())); + + expectations + .entrySet() + .forEach( + e -> { + var r = recommendations.get(new PackageRef(e.getKey())); + assertNotNull(r); + assertEquals(e.getValue().version(), r.packageName().version()); + assertEquals(e.getValue().cves().size(), r.vulnerabilities().size()); + assertTrue(e.getValue().cves().containsAll(r.vulnerabilities().keySet())); + }); + } + + @Test + void testProcessRecommendationsEmpty() 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/empty_report.json") + .readAllBytes(); + when(inMessage.getBody(byte[].class)).thenReturn(responseBytes); + + trustifyIntegration.processRecommendations(exchange); + + @SuppressWarnings("rawtypes") + ArgumentCaptor bodyCaptor = forClass(Map.class); + verify(outMessage).setBody(bodyCaptor.capture()); + @SuppressWarnings("unchecked") + Map recommendations = bodyCaptor.getValue(); + assertNotNull(recommendations); + assertTrue(recommendations.isEmpty()); + } + + @Test + void testProcessRecommendationsWithSbomId() throws IOException { + String sbomId = "pkg:oci/alpine@0.0.1"; + 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(sbomId); + + byte[] responseBytes = + getClass() + .getClassLoader() + .getResourceAsStream("__files/trustedcontent/empty_report.json") + .readAllBytes(); + when(inMessage.getBody(byte[].class)).thenReturn(responseBytes); + + trustifyIntegration.processRecommendations(exchange); + + @SuppressWarnings("rawtypes") + ArgumentCaptor bodyCaptor = forClass(Map.class); + verify(outMessage).setBody(bodyCaptor.capture()); + @SuppressWarnings("unchecked") + Map recommendations = bodyCaptor.getValue(); + assertNotNull(recommendations); + + PackageRef sbomRef = new PackageRef(sbomId); + IndexedRecommendation recommendation = + new IndexedRecommendation(new PackageRef("pkg:oci/ubi@0.0.2"), null); + assertEquals(1, recommendations.size()); + assertEquals(recommendation, recommendations.get(sbomRef)); + } + + private static final record ExpectedRecommendation(String version, Set cves) {} } diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandlerTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandlerTest.java deleted file mode 100644 index 90025e01..00000000 --- a/src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TcResponseHandlerTest.java +++ /dev/null @@ -1,152 +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 static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.Mockito.mock; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import org.apache.camel.Exchange; -import org.junit.jupiter.api.Test; - -import io.github.guacsec.trustifyda.api.PackageRef; -import io.github.guacsec.trustifyda.integration.Constants; -import io.github.guacsec.trustifyda.integration.trustedcontent.ubi.UBIRecommendation; -import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation; -import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.junit.QuarkusTestProfile; -import io.quarkus.test.junit.TestProfile; - -import jakarta.inject.Inject; - -@QuarkusTest -@TestProfile(TcResponseHandlerTest.SbomIdTestProfile.class) -class TcResponseHandlerTest { - - private static final String EXPECTED_UBI_RECOMMENDATION = "pkg:oci/ubi@0.0.2"; - - @Inject TcResponseHandler handler; - - @Inject UBIRecommendation mapping; - - @Test - void testAggregation() throws IOException { - var response = - handler.processRecommendations( - mock(Exchange.class), - getClass() - .getClassLoader() - .getResourceAsStream("__files/trustedcontent/simple.json") - .readAllBytes(), - null); - assertNotNull(response); - assertTrue(response.status().getOk()); - assertEquals("OK", response.status().getMessage()); - assertEquals(200, response.status().getCode()); - assertEquals(Constants.TRUSTED_CONTENT_PROVIDER, response.status().getName()); - - assertEquals(3, response.recommendations().size()); - - Map expectations = new HashMap<>(); - expectations.put( - "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5?type=jar", - new ExpectedRecommendation( - "1.2.5.redhat-00003", Set.of("CVE-2023-2974", "CVE-2023-1584", "CVE-2023-28867"))); - expectations.put( - "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final?type=jar", - new ExpectedRecommendation( - "2.13.8.Final-redhat-00006", - Set.of("CVE-2020-36518", "CVE-2023-44487", "CVE-2023-4853"))); - - expectations.put( - "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", - new ExpectedRecommendation("2.13.4.2-redhat-00001", Collections.emptySet())); - - expectations - .entrySet() - .forEach( - e -> { - var r = response.recommendations().get(new PackageRef(e.getKey())); - assertNotNull(r); - assertEquals(e.getValue().version(), r.packageName().version()); - assertEquals(e.getValue().cves().size(), r.vulnerabilities().size()); - assertTrue(e.getValue().cves().containsAll(r.vulnerabilities().keySet())); - }); - } - - @Test - void testEmpty() throws IOException { - var response = - handler.processRecommendations( - mock(Exchange.class), - getClass() - .getClassLoader() - .getResourceAsStream("__files/trustedcontent/empty_report.json") - .readAllBytes(), - null); - assertNotNull(response); - assertTrue(response.status().getOk()); - assertEquals("OK", response.status().getMessage()); - assertEquals(200, response.status().getCode()); - assertEquals(Constants.TRUSTED_CONTENT_PROVIDER, response.status().getName()); - - assertTrue(response.recommendations().isEmpty()); - } - - @Test - void testSbomId() throws IOException { - String sbomId = "pkg:oci/alpine@0.0.1"; - handler.ubiRecommendation = mapping; - var response = - handler.processRecommendations( - mock(Exchange.class), - getClass() - .getClassLoader() - .getResourceAsStream("__files/trustedcontent/empty_report.json") - .readAllBytes(), - sbomId); - assertNotNull(response); - assertTrue(response.status().getOk()); - assertEquals("OK", response.status().getMessage()); - assertEquals(200, response.status().getCode()); - assertEquals(Constants.TRUSTED_CONTENT_PROVIDER, response.status().getName()); - - PackageRef sbomRef = new PackageRef(sbomId); - IndexedRecommendation recommendation = - new IndexedRecommendation(new PackageRef(EXPECTED_UBI_RECOMMENDATION), null); - assertEquals(1, response.recommendations().size()); - assertEquals(recommendation, response.recommendations().get(sbomRef)); - } - - private static final record ExpectedRecommendation(String version, Set cves) {} - - public static class SbomIdTestProfile implements QuarkusTestProfile { - @Override - public Map getConfigOverrides() { - return Map.of( - "trustedcontent.recommendation.ubi.mapping.alpine", EXPECTED_UBI_RECOMMENDATION); - } - } -} diff --git a/src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilderTest.java b/src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilderTest.java deleted file mode 100644 index 949e703e..00000000 --- a/src/test/java/io/github/guacsec/trustifyda/integration/trustedcontent/TrustedContentRequestBuilderTest.java +++ /dev/null @@ -1,166 +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 static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.argThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import java.util.Collections; -import java.util.Map; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -import org.apache.camel.Exchange; -import org.junit.jupiter.api.Test; - -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.DirectDependency; -import io.github.guacsec.trustifyda.model.trustedcontent.CachedRecommendation; -import io.github.guacsec.trustifyda.model.trustedcontent.IndexedRecommendation; -import io.github.guacsec.trustifyda.model.trustedcontent.TrustedContentCachedRequest; -import io.quarkus.test.InjectMock; -import io.quarkus.test.junit.QuarkusTest; - -import jakarta.inject.Inject; - -@QuarkusTest -public class TrustedContentRequestBuilderTest { - - 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"), - new PackageRef("pkg:maven/io.quarkus/quarkus-vertx@2.13.6.Final") - }; - - @Inject TrustedContentRequestBuilder requestBuilder; - - @InjectMock CacheService cacheService; - - @Test - void testFilterCachedRecommendations_noDeps() { - Exchange exchange = mock(); - DependencyTree tree = new DependencyTree(Collections.emptyMap()); - var deps = tree.getAll(); - - when(cacheService.getRecommendations(deps)).thenReturn(Collections.emptyMap()); - - var result = requestBuilder.filterCachedRecommendations(tree, exchange); - - assertTrue(result.isEmpty()); - verify(exchange) - .setProperty( - eq(Constants.CACHED_RECOMMENDATIONS_PROPERTY), - argThat( - req -> { - var tcReq = (TrustedContentCachedRequest) req; - return tcReq.cached().isEmpty() && tcReq.miss().isEmpty(); - })); - } - - @Test - void testFilterCachedRecommendations_noCachedData() { - Exchange exchange = mock(); - DependencyTree tree = buildDeps(); - var deps = tree.getAll(); - - when(cacheService.getRecommendations(deps)).thenReturn(Collections.emptyMap()); - - var result = requestBuilder.filterCachedRecommendations(tree, exchange); - - assertEquals(deps.size(), result.size()); - verify(exchange) - .setProperty( - eq(Constants.CACHED_RECOMMENDATIONS_PROPERTY), - argThat( - req -> { - var tcReq = (TrustedContentCachedRequest) req; - return tcReq.cached().isEmpty() && tcReq.miss().size() == TEST_PURLS.length; - })); - } - - @Test - void testFilterCachedRecommendations_emptyCachedData() { - Exchange exchange = mock(); - DependencyTree tree = buildDeps(); - var deps = tree.getAll(); - - var cachedData = - Stream.of(TEST_PURLS) - .collect(Collectors.toMap(p -> p, p -> new CachedRecommendation(p, null))); - when(cacheService.getRecommendations(deps)).thenReturn(cachedData); - - var result = requestBuilder.filterCachedRecommendations(tree, exchange); - - assertTrue(result.isEmpty()); - verify(exchange) - .setProperty( - eq(Constants.CACHED_RECOMMENDATIONS_PROPERTY), - argThat( - req -> { - var tcReq = (TrustedContentCachedRequest) req; - return tcReq.cached().isEmpty() && tcReq.miss().isEmpty(); - })); - } - - @Test - void testFilterCachedRecommendations_partialCache() { - Exchange exchange = mock(); - DependencyTree tree = buildDeps(); - var deps = tree.getAll(); - - var cachedData = - Map.of( - TEST_PURLS[0], - new CachedRecommendation(TEST_PURLS[0], null), - TEST_PURLS[1], - new CachedRecommendation( - TEST_PURLS[1], - new IndexedRecommendation( - new PackageRef(TEST_PURLS[1].ref() + "-redhat-0001"), null))); - when(cacheService.getRecommendations(deps)).thenReturn(cachedData); - - var result = requestBuilder.filterCachedRecommendations(tree, exchange); - - assertEquals(1, result.size()); - assertTrue(result.contains(TEST_PURLS[2])); - verify(exchange) - .setProperty( - eq(Constants.CACHED_RECOMMENDATIONS_PROPERTY), - argThat( - req -> { - var tcReq = (TrustedContentCachedRequest) req; - return tcReq.cached().size() == 1 - && tcReq.cached().containsKey(TEST_PURLS[1]) - && tcReq.miss().contains(TEST_PURLS[2]); - })); - } - - private DependencyTree buildDeps() { - var deps = - Stream.of(TEST_PURLS).collect(Collectors.toMap(d -> d, d -> new DirectDependency(d, null))); - return new DependencyTree(deps); - } -} diff --git a/src/test/resources/__files/reports/batch_report_all_token.json b/src/test/resources/__files/reports/batch_report.json similarity index 88% rename from src/test/resources/__files/reports/batch_report_all_token.json rename to src/test/resources/__files/reports/batch_report.json index 52cefb47..5e385ee9 100644 --- a/src/test/resources/__files/reports/batch_report_all_token.json +++ b/src/test/resources/__files/reports/batch_report.json @@ -6,15 +6,6 @@ "transitive": 7 }, "providers": { - "trusted-content": { - "status": { - "ok": true, - "name": "trusted-content", - "code": 200, - "message": "OK" - }, - "sources": {} - }, "osv": { "status": { "ok": true, @@ -33,14 +24,13 @@ "high": 3, "medium": 2, "low": 0, - "remediations": 2, - "recommendations": 2, + "remediations": 0, + "recommendations": 0, "unscanned": 0 }, "dependencies": [ { "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar", @@ -69,12 +59,7 @@ "remediation": { "fixedIn": [ "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } ], @@ -102,12 +87,7 @@ "remediation": { "fixedIn": [ "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } }, @@ -193,12 +173,7 @@ "fixedIn": [ "2.13.2.1", "2.12.6.1" - ], - "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } ], @@ -232,7 +207,6 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2023-2974", "title": "A vulnerability was found in quarkus-core. This vulnerability occurs because the TLS protocol configured with quarkus.http.ssl.protocols is not enforced, and the client can force the selection of the weaker supported TLS protocol.", @@ -257,18 +231,12 @@ "remediation": { "fixedIn": [ "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } }, { "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", @@ -336,7 +304,6 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2022-41946", "title": "TemporaryFolder on unix-like systems does not limit access to created files", @@ -391,13 +358,12 @@ "medium": 2, "low": 0, "remediations": 2, - "recommendations": 2, + "recommendations": 6, "unscanned": 0 }, "dependencies": [ { "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", @@ -438,7 +404,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2024-1597", "title": "pgjdbc SQL Injection via line comment generation", @@ -453,7 +419,6 @@ }, { "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", @@ -470,7 +435,7 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } @@ -511,7 +476,7 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } @@ -544,7 +509,7 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } @@ -564,7 +529,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2020-36518", "title": "jackson-databind before 2.13.0 allows a Java StackOverflow exception and denial of service via a large depth of nested objects.", @@ -577,12 +542,28 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } } } + }, + { + "ref": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3?type=jar", + "recommendation": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3.redhat-00002?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2?type=jar", + "recommendation": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2.redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final?type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5?type=jar", + "recommendation": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5.redhat-00003?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" } ] }, @@ -597,13 +578,12 @@ "medium": 0, "low": 0, "remediations": 0, - "recommendations": 2, + "recommendations": 7, "unscanned": 0 }, "dependencies": [ { "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", @@ -633,7 +613,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2024-1597", "title": "pgjdbc SQL Injection via line comment generation", @@ -648,7 +628,6 @@ }, { "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar", @@ -678,7 +657,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2024-2700", "title": "Quarkus-core: leak of local configuration properties into quarkus applications", @@ -690,6 +669,26 @@ ], "unique": false } + }, + { + "ref": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3?type=jar", + "recommendation": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3.redhat-00002?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2?type=jar", + "recommendation": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2.redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final?type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", + "recommendation": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5?type=jar", + "recommendation": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5.redhat-00003?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" } ] } @@ -704,15 +703,6 @@ "transitive": 0 }, "providers": { - "trusted-content": { - "status": { - "ok": true, - "name": "trusted-content", - "code": 200, - "message": "OK" - }, - "sources": {} - }, "osv": { "status": { "ok": true, @@ -740,15 +730,6 @@ "transitive": 0 }, "providers": { - "trusted-content": { - "status": { - "ok": true, - "name": "trusted-content", - "code": 200, - "message": "OK" - }, - "sources": {} - }, "osv": { "status": { "ok": true, @@ -756,31 +737,7 @@ "code": 200, "message": "OK" }, - "sources": { - "osv": { - "summary": { - "direct": 0, - "transitive": 0, - "total": 0, - "dependencies": 0, - "critical": 0, - "high": 0, - "medium": 0, - "low": 0, - "remediations": 0, - "recommendations": 1, - "unscanned": 0 - }, - "dependencies": [ - { - "ref": "pkg:oci/debian@sha256%3A7c288032ecf3319045d9fa538c3b0cc868a320d01d03bce15b99c2c336319994?tag=0.0.1", - "issues": [], - "transitive": [], - "recommendation": "pkg:oci/ubi@sha256%3Af5983f7c7878cc9b26a3962be7756e3c810e9831b0b9f9613e6f6b445f884e74?arch=amd64&repository_url=registry.access.redhat.com%2Fubi9%2Fubi&tag=9.3-1552" - } - ] - } - } + "sources": {} }, "trustify": { "status": { @@ -807,7 +764,6 @@ "dependencies": [ { "ref": "pkg:oci/debian@sha256%3A7c288032ecf3319045d9fa538c3b0cc868a320d01d03bce15b99c2c336319994?tag=0.0.1", - "issues": [], "transitive": [], "recommendation": "pkg:oci/ubi@sha256%3Af5983f7c7878cc9b26a3962be7756e3c810e9831b0b9f9613e6f6b445f884e74?arch=amd64&repository_url=registry.access.redhat.com%2Fubi9%2Fubi&tag=9.3-1552" } diff --git a/src/test/resources/__files/reports/report_all_token.json b/src/test/resources/__files/reports/report.json similarity index 88% rename from src/test/resources/__files/reports/report_all_token.json rename to src/test/resources/__files/reports/report.json index 74ce1c98..0c2cd92e 100644 --- a/src/test/resources/__files/reports/report_all_token.json +++ b/src/test/resources/__files/reports/report.json @@ -5,15 +5,6 @@ "transitive": 7 }, "providers": { - "trusted-content": { - "status": { - "ok": true, - "name": "trusted-content", - "code": 200, - "message": "OK" - }, - "sources": {} - }, "osv": { "status": { "ok": true, @@ -32,14 +23,13 @@ "high": 3, "medium": 2, "low": 0, - "remediations": 2, - "recommendations": 2, + "remediations": 0, + "recommendations": 0, "unscanned": 0 }, "dependencies": [ { "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar", @@ -68,12 +58,7 @@ "remediation": { "fixedIn": [ "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } ], @@ -101,12 +86,7 @@ "remediation": { "fixedIn": [ "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } }, @@ -192,12 +172,7 @@ "fixedIn": [ "2.13.2.1", "2.12.6.1" - ], - "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } ], @@ -231,7 +206,6 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2023-2974", "title": "A vulnerability was found in quarkus-core. This vulnerability occurs because the TLS protocol configured with quarkus.http.ssl.protocols is not enforced, and the client can force the selection of the weaker supported TLS protocol.", @@ -256,18 +230,12 @@ "remediation": { "fixedIn": [ "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } + ] } } }, { "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", @@ -335,7 +303,6 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2022-41946", "title": "TemporaryFolder on unix-like systems does not limit access to created files", @@ -390,13 +357,12 @@ "medium": 2, "low": 0, "remediations": 2, - "recommendations": 2, + "recommendations": 6, "unscanned": 0 }, "dependencies": [ { "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", @@ -437,7 +403,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2024-1597", "title": "pgjdbc SQL Injection via line comment generation", @@ -452,7 +418,6 @@ }, { "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", @@ -469,7 +434,7 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } @@ -510,7 +475,7 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } @@ -543,7 +508,7 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } @@ -563,7 +528,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2020-36518", "title": "jackson-databind before 2.13.0 allows a Java StackOverflow exception and denial of service via a large depth of nested objects.", @@ -576,12 +541,28 @@ "unique": false, "remediation": { "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "status": "NotAffected", "justification": "VulnerableCodeNotPresent" } } } + }, + { + "ref": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3?type=jar", + "recommendation": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3.redhat-00002?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2?type=jar", + "recommendation": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2.redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final?type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5?type=jar", + "recommendation": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5.redhat-00003?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" } ] }, @@ -596,13 +577,12 @@ "medium": 0, "low": 0, "remediations": 0, - "recommendations": 2, + "recommendations": 7, "unscanned": 0 }, "dependencies": [ { "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", @@ -632,7 +612,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2024-1597", "title": "pgjdbc SQL Injection via line comment generation", @@ -647,7 +627,6 @@ }, { "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [], "transitive": [ { "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar", @@ -677,7 +656,7 @@ } } ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", "highestVulnerability": { "id": "CVE-2024-2700", "title": "Quarkus-core: leak of local configuration properties into quarkus applications", @@ -689,6 +668,26 @@ ], "unique": false } + }, + { + "ref": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3?type=jar", + "recommendation": "pkg:maven/jakarta.el/jakarta.el-api@3.0.3.redhat-00002?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2?type=jar", + "recommendation": "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2.redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final?type=jar", + "recommendation": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final-redhat-00004?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", + "recommendation": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" + }, + { + "ref": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5?type=jar", + "recommendation": "pkg:maven/jakarta.interceptor/jakarta.interceptor-api@1.2.5.redhat-00003?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar" } ] } diff --git a/src/test/resources/__files/reports/report_all_no_snyk_token.json b/src/test/resources/__files/reports/report_all_no_snyk_token.json deleted file mode 100644 index 0e9b6f29..00000000 --- a/src/test/resources/__files/reports/report_all_no_snyk_token.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "scanned": { - "total": 9, - "direct": 2, - "transitive": 7 - }, - "providers": { - "trusted-content": { - "status": { - "ok": true, - "name": "trusted-content", - "code": 200, - "message": "OK" - }, - "sources": { - - } - } - } -} \ No newline at end of file diff --git a/src/test/resources/__files/reports/report_default_token.json b/src/test/resources/__files/reports/report_default_token.json deleted file mode 100644 index 7d7ccc31..00000000 --- a/src/test/resources/__files/reports/report_default_token.json +++ /dev/null @@ -1,712 +0,0 @@ -{ - "scanned": { - "total": 9, - "direct": 2, - "transitive": 7 - }, - "providers": { - "trusted-content": { - "status": { - "ok": true, - "name": "trusted-content", - "code": 200, - "message": "OK" - }, - "sources": { - - } - }, - "osv": { - "status": { - "ok": true, - "name": "osv", - "code": 200, - "message": "OK" - }, - "sources": { - "osv": { - "summary": { - "direct": 0, - "transitive": 5, - "total": 5, - "dependencies": 3, - "critical": 0, - "high": 3, - "medium": 2, - "low": 0, - "remediations": 2, - "recommendations": 2, - "unscanned": 0 - }, - "dependencies": [ - { - "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [ - - ], - "transitive": [ - { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar", - "issues": [ - { - "id": "CVE-2023-2974", - "title": "A vulnerability was found in quarkus-core. This vulnerability occurs because the TLS protocol configured with quarkus.http.ssl.protocols is not enforced, and the client can force the selection of the weaker supported TLS protocol.", - "source": "osv", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "Low", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "None", - "cvss": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N" - }, - "cvssScore": 8.1, - "severity": "HIGH", - "cves": [ - "CVE-2023-2974" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - } - ], - "highestVulnerability": { - "id": "CVE-2023-2974", - "title": "A vulnerability was found in quarkus-core. This vulnerability occurs because the TLS protocol configured with quarkus.http.ssl.protocols is not enforced, and the client can force the selection of the weaker supported TLS protocol.", - "source": "osv", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "Low", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "None", - "cvss": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N" - }, - "cvssScore": 8.1, - "severity": "HIGH", - "cves": [ - "CVE-2023-2974" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - } - }, - { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", - "issues": [ - { - "id": "CVE-2022-42003", - "title": "In FasterXML jackson-databind 2.4.0-rc1 until 2.12.7.1 and in 2.13.x before 2.13.4.2 resource exhaustion can occur because of a lack of a check in primitive value deserializers to avoid deep wrapper array nesting, when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled. This was patched in 2.12.7.1, 2.13.4.2, and 2.14.0.", - "source": "osv", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "None", - "integrityImpact": "None", - "availabilityImpact": "High", - "cvss": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" - }, - "cvssScore": 7.5, - "severity": "HIGH", - "cves": [ - "CVE-2022-42003" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "2.12.7.1", - "2.13.4.2" - ] - } - }, - { - "id": "CVE-2022-42004", - "title": "Uncontrolled Resource Consumption in FasterXML jackson-databind", - "source": "osv", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "None", - "integrityImpact": "None", - "availabilityImpact": "High", - "cvss": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" - }, - "cvssScore": 7.5, - "severity": "HIGH", - "cves": [ - "CVE-2022-42004" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "2.12.7.1", - "2.13.4" - ] - } - }, - { - "id": "CVE-2020-36518", - "title": "Deeply nested json in jackson-databind", - "source": "osv", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "None", - "confidentialityImpact": "None", - "integrityImpact": "None", - "availabilityImpact": "Low", - "cvss": "AV:N/AC:L/Au:N/C:N/I:N/A:P" - }, - "cvssScore": 5.0, - "severity": "MEDIUM", - "cves": [ - "CVE-2020-36518" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "2.13.2.1", - "2.12.6.1" - ], - "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - } - ], - "highestVulnerability": { - "id": "CVE-2022-42003", - "title": "In FasterXML jackson-databind 2.4.0-rc1 until 2.12.7.1 and in 2.13.x before 2.13.4.2 resource exhaustion can occur because of a lack of a check in primitive value deserializers to avoid deep wrapper array nesting, when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled. This was patched in 2.12.7.1, 2.13.4.2, and 2.14.0.", - "source": "osv", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "None", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "None", - "integrityImpact": "None", - "availabilityImpact": "High", - "cvss": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H" - }, - "cvssScore": 7.5, - "severity": "HIGH", - "cves": [ - "CVE-2022-42003" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "2.12.7.1", - "2.13.4.2" - ] - } - } - } - ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "highestVulnerability": { - "id": "CVE-2023-2974", - "title": "A vulnerability was found in quarkus-core. This vulnerability occurs because the TLS protocol configured with quarkus.http.ssl.protocols is not enforced, and the client can force the selection of the weaker supported TLS protocol.", - "source": "osv", - "cvss": { - "attackVector": "Network", - "attackComplexity": "Low", - "privilegesRequired": "Low", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "High", - "availabilityImpact": "None", - "cvss": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N" - }, - "cvssScore": 8.1, - "severity": "HIGH", - "cves": [ - "CVE-2023-2974" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "2.16.8.Final" - ], - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - } - }, - { - "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [ - - ], - "transitive": [ - { - "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", - "issues": [ - { - "id": "CVE-2022-41946", - "title": "TemporaryFolder on unix-like systems does not limit access to created files", - "source": "osv", - "cvss": { - "attackVector": "Local", - "attackComplexity": "Low", - "privilegesRequired": "Low", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "None", - "availabilityImpact": "None", - "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N" - }, - "cvssScore": 5.5, - "severity": "MEDIUM", - "cves": [ - "CVE-2022-41946" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "42.2.27", - "42.3.8", - "42.4.3", - "42.5.1" - ] - } - } - ], - "highestVulnerability": { - "id": "CVE-2022-41946", - "title": "TemporaryFolder on unix-like systems does not limit access to created files", - "source": "osv", - "cvss": { - "attackVector": "Local", - "attackComplexity": "Low", - "privilegesRequired": "Low", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "None", - "availabilityImpact": "None", - "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N" - }, - "cvssScore": 5.5, - "severity": "MEDIUM", - "cves": [ - "CVE-2022-41946" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "42.2.27", - "42.3.8", - "42.4.3", - "42.5.1" - ] - } - } - } - ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "highestVulnerability": { - "id": "CVE-2022-41946", - "title": "TemporaryFolder on unix-like systems does not limit access to created files", - "source": "osv", - "cvss": { - "attackVector": "Local", - "attackComplexity": "Low", - "privilegesRequired": "Low", - "userInteraction": "None", - "scope": "Unchanged", - "confidentialityImpact": "High", - "integrityImpact": "None", - "availabilityImpact": "None", - "cvss": "CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N" - }, - "cvssScore": 5.5, - "severity": "MEDIUM", - "cves": [ - "CVE-2022-41946" - ], - "unique": false, - "remediation": { - "fixedIn": [ - "42.2.27", - "42.3.8", - "42.4.3", - "42.5.1" - ] - } - } - } - ] - } - } - }, - "trustify": { - "status": { - "ok": true, - "name": "trustify", - "code": 200, - "message": "OK" - }, - "sources": { - "osv": { - "summary": { - "direct": 0, - "transitive": 7, - "total": 7, - "dependencies": 3, - "critical": 1, - "high": 4, - "medium": 2, - "low": 0, - "remediations": 2, - "recommendations": 2, - "unscanned": 0 - }, - "dependencies": [ - { - "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [ - - ], - "transitive": [ - { - "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", - "issues": [ - { - "id": "CVE-2024-1597", - "title": "pgjdbc SQL Injection via line comment generation", - "source": "osv", - "cvssScore": 10.0, - "severity": "CRITICAL", - "cves": [ - "CVE-2024-1597" - ], - "unique": false - }, - { - "id": "CVE-2022-41946", - "title": "TemporaryFolder on unix-like systems does not limit access to created files in pgjdbc", - "source": "osv", - "cvssScore": 5.8, - "severity": "MEDIUM", - "cves": [ - "CVE-2022-41946" - ], - "unique": false - } - ], - "highestVulnerability": { - "id": "CVE-2024-1597", - "title": "pgjdbc SQL Injection via line comment generation", - "source": "osv", - "cvssScore": 10.0, - "severity": "CRITICAL", - "cves": [ - "CVE-2024-1597" - ], - "unique": false - } - } - ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "highestVulnerability": { - "id": "CVE-2024-1597", - "title": "pgjdbc SQL Injection via line comment generation", - "source": "osv", - "cvssScore": 10.0, - "severity": "CRITICAL", - "cves": [ - "CVE-2024-1597" - ], - "unique": false - } - }, - { - "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [ - - ], - "transitive": [ - { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar", - "issues": [ - { - "id": "CVE-2020-36518", - "title": "jackson-databind before 2.13.0 allows a Java StackOverflow exception and denial of service via a large depth of nested objects.", - "source": "osv", - "cvssScore": 8.2, - "severity": "HIGH", - "cves": [ - "CVE-2020-36518" - ], - "unique": false, - "remediation": { - "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - }, - { - "id": "CVE-2022-42003", - "title": "In FasterXML jackson-databind before versions 2.13.4.1 and 2.12.17.1, resource exhaustion can occur because of a lack of a check in primitive value deserializers to avoid deep wrapper array nesting, when the UNWRAP_SINGLE_VALUE_ARRAYS feature is enabled.", - "source": "osv", - "cvssScore": 8.2, - "severity": "HIGH", - "cves": [ - "CVE-2022-42003" - ], - "unique": false - }, - { - "id": "CVE-2022-42004", - "title": "In FasterXML jackson-databind before 2.13.4, resource exhaustion can occur because of a lack of a check in BeanDeserializer._deserializeFromArray to prevent use of deeply nested arrays. An application is vulnerable only with certain customized choices for deserialization.", - "source": "osv", - "cvssScore": 8.2, - "severity": "HIGH", - "cves": [ - "CVE-2022-42004" - ], - "unique": false - } - ], - "highestVulnerability": { - "id": "CVE-2020-36518", - "title": "jackson-databind before 2.13.0 allows a Java StackOverflow exception and denial of service via a large depth of nested objects.", - "source": "osv", - "cvssScore": 8.2, - "severity": "HIGH", - "cves": [ - "CVE-2020-36518" - ], - "unique": false, - "remediation": { - "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - } - }, - { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar", - "issues": [ - { - "id": "CVE-2024-2700", - "title": "Quarkus-core: leak of local configuration properties into quarkus applications", - "source": "osv", - "cvssScore": 7.0, - "severity": "HIGH", - "cves": [ - "CVE-2024-2700" - ], - "unique": false - }, - { - "id": "CVE-2023-2974", - "title": "Quarkus-core: tls protocol configured with quarkus.http.ssl.protocols is not enforced, client can enforce weaker supported tls protocol", - "source": "osv", - "cvssScore": 6.7, - "severity": "MEDIUM", - "cves": [ - "CVE-2023-2974" - ], - "unique": false, - "remediation": { - "trustedContent": { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - } - ], - "highestVulnerability": { - "id": "CVE-2024-2700", - "title": "Quarkus-core: leak of local configuration properties into quarkus applications", - "source": "osv", - "cvssScore": 7.0, - "severity": "HIGH", - "cves": [ - "CVE-2024-2700" - ], - "unique": false - } - } - ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "highestVulnerability": { - "id": "CVE-2020-36518", - "title": "jackson-databind before 2.13.0 allows a Java StackOverflow exception and denial of service via a large depth of nested objects.", - "source": "osv", - "cvssScore": 8.2, - "severity": "HIGH", - "cves": [ - "CVE-2020-36518" - ], - "unique": false, - "remediation": { - "trustedContent": { - "ref": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - } - } - } - ] - }, - "csaf": { - "summary": { - "direct": 0, - "transitive": 2, - "total": 2, - "dependencies": 2, - "critical": 1, - "high": 1, - "medium": 0, - "low": 0, - "remediations": 0, - "recommendations": 2, - "unscanned": 0 - }, - "dependencies": [ - { - "ref": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar", - "issues": [ - - ], - "transitive": [ - { - "ref": "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar", - "issues": [ - { - "id": "CVE-2024-1597", - "title": "pgjdbc SQL Injection via line comment generation", - "source": "csaf", - "cvssScore": 9.8, - "severity": "CRITICAL", - "cves": [ - "CVE-2024-1597" - ], - "unique": false - } - ], - "highestVulnerability": { - "id": "CVE-2024-1597", - "title": "pgjdbc SQL Injection via line comment generation", - "source": "csaf", - "cvssScore": 9.8, - "severity": "CRITICAL", - "cves": [ - "CVE-2024-1597" - ], - "unique": false - } - } - ], - "recommendation": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "highestVulnerability": { - "id": "CVE-2024-1597", - "title": "pgjdbc SQL Injection via line comment generation", - "source": "csaf", - "cvssScore": 9.8, - "severity": "CRITICAL", - "cves": [ - "CVE-2024-1597" - ], - "unique": false - } - }, - { - "ref": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar", - "issues": [ - - ], - "transitive": [ - { - "ref": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar", - "issues": [ - { - "id": "CVE-2024-2700", - "title": "Quarkus-core: leak of local configuration properties into quarkus applications", - "source": "csaf", - "cvssScore": 7.0, - "severity": "HIGH", - "cves": [ - "CVE-2024-2700" - ], - "unique": false - } - ], - "highestVulnerability": { - "id": "CVE-2024-2700", - "title": "Quarkus-core: leak of local configuration properties into quarkus applications", - "source": "csaf", - "cvssScore": 7.0, - "severity": "HIGH", - "cves": [ - "CVE-2024-2700" - ], - "unique": false - } - } - ], - "recommendation": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https%3A%2F%2Fmaven.repository.redhat.com%2Fga%2F&type=jar", - "highestVulnerability": { - "id": "CVE-2024-2700", - "title": "Quarkus-core: leak of local configuration properties into quarkus applications", - "source": "csaf", - "cvssScore": 7.0, - "severity": "HIGH", - "cves": [ - "CVE-2024-2700" - ], - "unique": false - } - } - ] - } - } - } - } -} \ No newline at end of file diff --git a/src/test/resources/__files/trustedcontent/maven_report.json b/src/test/resources/__files/trustedcontent/maven_report.json index 79d893a0..6844257b 100644 --- a/src/test/resources/__files/trustedcontent/maven_report.json +++ b/src/test/resources/__files/trustedcontent/maven_report.json @@ -44,7 +44,7 @@ ], "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final?type=jar": [ { - "package": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.8.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "package": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.5.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", "vulnerabilities": [ { "id": "cve-2020-36518", @@ -72,31 +72,11 @@ "justification": "VulnerableCodeNotPresent" } ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.8.Final-redhat-00006?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-44487", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-narayana-jta@2.13.8.Final-redhat-00005?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-4853", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] } ], "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final?type=jar": [ { - "package": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "package": "pkg:maven/io.quarkus/quarkus-core@2.13.5.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", "vulnerabilities": [ { "id": "cve-2020-36518", @@ -124,31 +104,11 @@ "justification": "VulnerableCodeNotPresent" } ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00005?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-4853", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-core@2.13.8.Final-redhat-00006?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-44487", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] } ], "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1?type=jar": [ { - "package": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.4.2-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "package": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.13.1.2-redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar", "vulnerabilities": [ { "id": "cve-2020-36518", @@ -195,7 +155,7 @@ ], "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final?type=jar": [ { - "package": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "package": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.5.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", "vulnerabilities": [ { "id": "cve-2020-36518", @@ -223,31 +183,11 @@ "justification": "VulnerableCodeNotPresent" } ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00005?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-4853", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-hibernate-orm@2.13.8.Final-redhat-00006?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-44487", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] } ], "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final?type=jar": [ { - "package": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "package": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.5.Final-redhat-00004?repository_url=https://maven.repository.redhat.com/ga/&type=jar", "vulnerabilities": [ { "id": "cve-2020-36518", @@ -275,26 +215,6 @@ "justification": "VulnerableCodeNotPresent" } ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00005?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-4853", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] - }, - { - "package": "pkg:maven/io.quarkus/quarkus-jdbc-postgresql@2.13.8.Final-redhat-00006?repository_url=https://maven.repository.redhat.com/ga/&type=jar", - "vulnerabilities": [ - { - "id": "cve-2023-44487", - "status": "NotAffected", - "justification": "VulnerableCodeNotPresent" - } - ] } ], "pkg:maven/jakarta.enterprise/jakarta.enterprise.cdi-api@2.0.2?type=jar": [ @@ -341,7 +261,7 @@ ], "pkg:maven/org.postgresql/postgresql@42.5.0?type=jar": [ { - "package": "pkg:maven/org.postgresql/postgresql@42.5.1.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar", + "package": "pkg:maven/org.postgresql/postgresql@42.5.0.redhat-00001?repository_url=https://maven.repository.redhat.com/ga/&type=jar", "vulnerabilities": [ { "id": "cve-2023-2455", diff --git a/ui/src/api/report.ts b/ui/src/api/report.ts index 77e38722..0b6b49b4 100644 --- a/ui/src/api/report.ts +++ b/ui/src/api/report.ts @@ -149,13 +149,7 @@ export interface SourceItem { export interface SourceReport { summary: Summary; dependencies: Dependency[]; - unscanned?: Unscanned[]; } -export interface Unscanned { - ref: string | null; - reason: string | null; -} - export interface VulnerabilityItem { id: string; diff --git a/ui/src/mocks/reportDocker.mock.ts b/ui/src/mocks/reportDocker.mock.ts index 0a8555f1..277c1c1c 100644 --- a/ui/src/mocks/reportDocker.mock.ts +++ b/ui/src/mocks/reportDocker.mock.ts @@ -826,19 +826,13 @@ export const dockerReport: AppData = { "low": 0, "remediations": 0, "recommendations": 1, - "unscanned": 1 + "unscanned": 0 }, "dependencies": [ { "ref": "pkg:oci/quay.io/default-app@0.0.1", "recommendation": "pkg:oci/quay.io/test-app@0.0.2" } - ], - "unscanned": [ - { - "ref": "pkg:oci/debian@sha256%3A7c288032ecf3319045d9fa538c3b0cc868a320d01d03bce15b99c2c336319994?tag=0.0.1", - "reason": "unsupported-pkg-type" - } ] } } From e51c56f4cbec1b2fbf8ff49eb82d8cd337a8ea52 Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Mon, 10 Nov 2025 09:55:40 +0100 Subject: [PATCH 2/6] feat: remove redis requirement and refactor tc package Signed-off-by: Ruben Romero Montes --- README.md | 4 +- pom.xml | 4 - .../trustifyda/integration/Constants.java | 1 - .../integration/cache/CacheService.java | 38 ----- .../integration/cache/RedisCacheService.java | 75 -------- .../providers/ProviderResponseHandler.java | 161 ++++++++++-------- .../trustify/RecommendationAggregation.java | 18 +- .../trustify/TrustifyIntegration.java | 26 +-- .../guacsec/trustifyda/model/PackageItem.java | 4 +- .../trustedcontent/CachedRecommendation.java | 22 --- .../TrustedContentCachedRequest.java | 26 --- .../TrustedContentResponse.java | 37 ---- .../IndexedRecommendation.java | 2 +- .../Recommendation.java} | 8 +- .../RecommendationsResponse.java} | 22 +-- .../Vulnerability.java | 2 +- src/main/resources/application.properties | 1 - .../cache/RedisCacheServiceTest.java | 87 ---------- .../ProviderResponseHandlerTest.java | 11 +- .../RecommendationAggregationTest.java | 4 +- .../trustify/TrustifyResponseHandlerTest.java | 11 +- 21 files changed, 140 insertions(+), 424 deletions(-) delete mode 100644 src/main/java/io/github/guacsec/trustifyda/integration/cache/CacheService.java delete mode 100644 src/main/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheService.java delete mode 100644 src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/CachedRecommendation.java delete mode 100644 src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentCachedRequest.java delete mode 100644 src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentResponse.java rename src/main/java/io/github/guacsec/trustifyda/model/{trustedcontent => trustify}/IndexedRecommendation.java (96%) rename src/main/java/io/github/guacsec/trustifyda/model/{trustedcontent/TcRecommendation.java => trustify/Recommendation.java} (87%) rename src/main/java/io/github/guacsec/trustifyda/model/{trustedcontent/Recommendations.java => trustify/RecommendationsResponse.java} (64%) rename src/main/java/io/github/guacsec/trustifyda/model/{trustedcontent => trustify}/Vulnerability.java (96%) delete mode 100644 src/test/java/io/github/guacsec/trustifyda/integration/cache/RedisCacheServiceTest.java 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/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 269b1c95..b7077f77 100644 --- a/src/main/java/io/github/guacsec/trustifyda/integration/Constants.java +++ b/src/main/java/io/github/guacsec/trustifyda/integration/Constants.java @@ -69,7 +69,6 @@ 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 CACHED_RECOMMENDATIONS_PROPERTY = "missedRecommendations"; public static final String PROVIDER_CONFIG_PROPERTY = "providerConfig"; public static final String PROVIDERS_PROPERTY = "providers"; 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/ProviderResponseHandler.java b/src/main/java/io/github/guacsec/trustifyda/integration/providers/ProviderResponseHandler.java index 1655f4f5..dc79a0a7 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 @@ -147,11 +147,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) { @@ -327,55 +326,14 @@ private Source buildReportForSource(Map pkgItemsData, Depen var packageItem = getPackageItem(packageRef, pkgItemsData); var directReport = new DependencyReport().ref(packageRef); - // Set issues if available - 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)); - } - - // Set recommendation if available (extract PackageRef from TcRecommendation) - if (packageItem != null - && packageItem.recommendation() != null - && packageItem.recommendation().packageName() != null) { - directReport.recommendation(packageItem.recommendation().packageName()); - } + setIssues(packageItem, directReport); + setRecommendations(packageItem, directReport); List transitiveReports = depEntry.getValue().transitive().stream() .map( 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()); - } - var highestTransitive = transitiveIssues.stream().findFirst(); - if (highestTransitive.isPresent()) { - if (directReport.getHighestVulnerability() == null - || directReport.getHighestVulnerability().getCvssScore() - < highestTransitive.get().getCvssScore()) { - directReport.setHighestVulnerability(highestTransitive.get()); - } - } - 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; + return getTransitiveReport(pkgItemsData, directReport, t); }) .filter(transitiveReport -> !transitiveReport.getIssues().isEmpty()) .collect(Collectors.toList()); @@ -387,32 +345,8 @@ private Source buildReportForSource(Map pkgItemsData, Depen } }); - // Process packages with recommendations-only that are not in the tree - // (these are recommendations for packages that might not be direct dependencies) if (pkgItemsData != null) { - 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 - } - }); + addRecommendationsWithoutIssues(pkgItemsData, sourceReport, processedRefs); } sourceReport.sort(Collections.reverseOrder(new DependencyScoreComparator())); @@ -420,6 +354,84 @@ private Source buildReportForSource(Map pkgItemsData, Depen return new Source().summary(summary).dependencies(sourceReport); } + 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 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()); + } + var highestTransitive = transitiveIssues.stream().findFirst(); + if (highestTransitive.isPresent()) { + if (directReport.getHighestVulnerability() == null + || directReport.getHighestVulnerability().getCvssScore() + < highestTransitive.get().getCvssScore()) { + directReport.setHighestVulnerability(highestTransitive.get()); + } + } + 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 void setRecommendations(PackageItem packageItem, DependencyReport directReport) { + if (packageItem != null + && packageItem.recommendation() != null + && packageItem.recommendation().packageName() != null) { + directReport.recommendation(packageItem.recommendation().packageName()); + } + } + + 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)); + } + } + private PackageItem getPackageItem(PackageRef ref, Map pkgItemsData) { return pkgItemsData.get(ref.ref()); } @@ -452,8 +464,9 @@ private void incrementCounter(PackageItem item, VulnerabilityCounter counter, bo .forEach( i -> { var vulnerabilities = countVulnerabilities(i); - if (i.getSeverity() != null) { - switch (i.getSeverity()) { + 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); 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 index 1125e7f5..f2eff3c8 100644 --- 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 @@ -31,8 +31,8 @@ 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.trustedcontent.IndexedRecommendation; -import io.github.guacsec.trustifyda.model.trustedcontent.TcRecommendation; +import io.github.guacsec.trustifyda.model.trustify.IndexedRecommendation; +import io.github.guacsec.trustifyda.model.trustify.Recommendation; public class RecommendationAggregation implements AggregationStrategy { @@ -86,7 +86,7 @@ private void setTrustedContent( recommendations.forEach( (key, value) -> { var pkgItem = providerResponse.pkgItems().get(key.ref()); - var tcRecommendation = toTcRecommendation(value); + var recommendation = toRecommendation(value); var issues = new ArrayList(); if (pkgItem != null && pkgItem.issues() != null) { issues.addAll(pkgItem.issues()); @@ -101,25 +101,25 @@ private void setTrustedContent( if (vuln == null) { return; } - var tcRemediation = + var remediation = new RemediationTrustedContent() .ref(value.packageName()) .status(vuln.getStatus()) .justification(vuln.getJustification()); - issue.remediation(new Remediation().trustedContent(tcRemediation)); + issue.remediation(new Remediation().trustedContent(remediation)); }); } providerResponse .pkgItems() - .put(key.ref(), new PackageItem(key.ref(), tcRecommendation, issues)); + .put(key.ref(), new PackageItem(key.ref(), recommendation, issues)); }); } - private TcRecommendation toTcRecommendation(IndexedRecommendation recommendation) { + private Recommendation toRecommendation(IndexedRecommendation recommendation) { if (recommendation.vulnerabilities() == null) { - return new TcRecommendation(recommendation.packageName(), Collections.emptyList()); + return new Recommendation(recommendation.packageName(), Collections.emptyList()); } - return new TcRecommendation( + 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 21e0c168..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 @@ -38,12 +38,12 @@ 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.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.Vulnerability; +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; @@ -335,28 +335,28 @@ 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 recommendations = mapper.readValue(tcResponse, Recommendations.class); - var mergedRecommendations = indexRecommendations(recommendations); + var response = mapper.readValue(tcResponse, RecommendationsResponse.class); + var mergedRecommendations = indexRecommendations(response); mergedRecommendations.putAll(getUBIRecommendation(sbomId)); exchange.getMessage().setBody(mergedRecommendations); } private Map indexRecommendations( - Recommendations recommendations) { + RecommendationsResponse response) { Map result = new HashMap<>(); - if (recommendations == null) { + if (response == null) { return result; } - recommendations.getMatchings().entrySet().stream() + response.getMatchings().entrySet().stream() .filter(e -> !e.getValue().isEmpty()) .forEach( e -> { - List tcRecommendations = e.getValue(); - PackageRef pkgRef = tcRecommendations.get(0).packageName(); + List recommendations = e.getValue(); + PackageRef pkgRef = recommendations.get(0).packageName(); Map vulnerabilities = - tcRecommendations.stream() - .map(TcRecommendation::vulnerabilities) + recommendations.stream() + .map(Recommendation::vulnerabilities) .flatMap(List::stream) .collect( Collectors.toMap( diff --git a/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java b/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java index 6534827a..60ea3002 100644 --- a/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java +++ b/src/main/java/io/github/guacsec/trustifyda/model/PackageItem.java @@ -20,6 +20,6 @@ import java.util.List; import io.github.guacsec.trustifyda.api.v5.Issue; -import io.github.guacsec.trustifyda.model.trustedcontent.TcRecommendation; +import io.github.guacsec.trustifyda.model.trustify.Recommendation; -public record PackageItem(String packageRef, TcRecommendation recommendation, List issues) {} +public record PackageItem(String packageRef, Recommendation recommendation, List issues) {} 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/TrustedContentCachedRequest.java b/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentCachedRequest.java deleted file mode 100644 index 92d66be2..00000000 --- a/src/main/java/io/github/guacsec/trustifyda/model/trustedcontent/TrustedContentCachedRequest.java +++ /dev/null @@ -1,26 +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.Map; -import java.util.Set; - -import io.github.guacsec.trustifyda.api.PackageRef; - -public record TrustedContentCachedRequest( - Map cached, Set miss) {} 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/resources/application.properties b/src/main/resources/application.properties index 1b508759..754155d0 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -41,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/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 ca9b4039..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 @@ -50,9 +50,8 @@ 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.TcRecommendation; -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; @@ -61,8 +60,6 @@ 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") @@ -390,8 +387,8 @@ DependencyTree build() { * @param cves - the vulnerabilities map CVE -> Status * @return the tc recommendation */ - private static TcRecommendation buildRecommendation(String ref, Map cves) { - return new TcRecommendation( + 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")) 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 index f5b8894b..b70f09b9 100644 --- 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 @@ -46,8 +46,8 @@ 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.trustedcontent.IndexedRecommendation; -import io.github.guacsec.trustifyda.model.trustedcontent.Vulnerability; +import io.github.guacsec.trustifyda.model.trustify.IndexedRecommendation; +import io.github.guacsec.trustifyda.model.trustify.Vulnerability; public class RecommendationAggregationTest { 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 51a306ef..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 @@ -49,13 +49,16 @@ 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.trustify.IndexedRecommendation; public class TrustifyResponseHandlerTest { private TrustifyResponseHandler handler; private DependencyTree dependencyTree; + private TrustifyIntegration trustifyIntegration; + private UBIRecommendation ubiRecommendation; + @BeforeEach void setUp() { handler = new TrustifyResponseHandler(); @@ -571,12 +574,6 @@ void testResponseToIssuesWithDependencyNotInTree() throws IOException { assertTrue(result.pkgItems().isEmpty()); } - // Tests for TrustifyIntegration.processRecommendations() - // These tests were migrated from TcResponseHandlerTest - - private TrustifyIntegration trustifyIntegration; - private UBIRecommendation ubiRecommendation; - @Test void testProcessRecommendationsAggregation() throws IOException { Exchange exchange = mock(Exchange.class); From 9e8a7ee2c694e3173c02b1cac4c4731a55c91c0e Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Mon, 10 Nov 2025 10:53:06 +0100 Subject: [PATCH 3/6] feat: use repository_url as main remediation link Signed-off-by: Ruben Romero Montes --- .../freemarker/templates/generated/main.js | 2 +- ui/src/mocks/reportBasic.mock.ts | 1423 ++++++++--------- ui/src/mocks/reportDocker.mock.ts | 12 +- ui/src/mocks/reportWithError.mock.ts | 4 +- ui/src/mocks/reportWithForbidden.mock.ts | 4 +- ui/src/mocks/reportWithUnauthorized.mock.ts | 4 +- ui/src/utils/utils.ts | 36 +- 7 files changed, 714 insertions(+), 771 deletions(-) diff --git a/src/main/resources/freemarker/templates/generated/main.js b/src/main/resources/freemarker/templates/generated/main.js index d95fe098..d27a50ad 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),B=r(3844),Z=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,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,_.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",l.displayName," Overview of security issues"]}),(0,_.jsx)(L.c,{}),(0,_.jsx)(s.E,{children:(0,_.jsxs)(B.Z,{isFlat:!0,isFullHeight:!0,children:[(0,_.jsx)(Z.a,{children:(0,_.jsx)(R.Z,{children:(0,_.jsx)(V.X,{style:{fontSize:"large"},children:i?(0,_.jsxs)(_.Fragment,{children:[t?M(t):"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(r).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,{})]})}),(!i||l.imageRecommendation.trim()&&l.imageRemediationLink.trim())&&(0,_.jsxs)(s.E,{md:c?6:void 0,children:[(0,_.jsx)(B.Z,{isFlat:!0,children:(0,_.jsxs)(Q.W,{children:[(0,_.jsx)(R.Z,{component:"h4",children:(0,_.jsxs)(V.X,{style:{fontSize:"large"},children:[(0,_.jsx)("img",{src:"data:image/png;base64,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",alt:"Trustify Icon",style:re}),"\xa0",l.displayName," Remediations"]})}),(0,_.jsx)(U.b,{children:(0,_.jsx)(G.d,{children:i?(0,_.jsxs)(H.B8,{isPlain:!0,children:[(0,_.jsx)(Y.c,{children:l.imageRecommendation}),(0,_.jsx)(Y.c,{children:(0,_.jsx)("a",{href:t?N(t,r,a.imageMapping,l.imageRemediationLink):"###",target:"_blank",rel:"noreferrer",children:(0,_.jsx)(K.$n,{variant:"primary",size:"sm",children:"Take me there"})})})]}):(0,_.jsx)(H.B8,{isPlain:!0,children:O(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,_.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"]}),c&&(0,_.jsxs)(s.E,{md:6,children:[(0,_.jsx)(B.Z,{isFlat:!0,children:(0,_.jsxs)(Q.W,{children:[(0,_.jsx)(R.Z,{component:"h4",children:(0,_.jsx)(V.X,{style:{fontSize:"large"},children:l.exploreTitle})}),(0,_.jsx)(U.b,{children:(0,_.jsx)(G.d,{children:(0,_.jsxs)(H.B8,{isPlain:!0,children:[(0,_.jsx)(Y.c,{children:l.exploreDescription}),(0,_.jsx)(Y.c,{children:(0,_.jsx)("a",{href:l.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))}})},Be=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)})})},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,_.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)(Ze.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)(Be,{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)(B.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)(B.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)(B.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)(Be,{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 { @@ -67,10 +64,20 @@ export const extractDependencyName = (name: string, showVersion: boolean) => { export const tcRemediationLink = (name: string) => { const pkgUrl = PackageURL.fromString(name); - let result = REDHAT_REPOSITORY; + if(pkgUrl.qualifiers && pkgUrl.qualifiers.has('repository_url')) { + let repositoryUrl = decodeURIComponent(pkgUrl.qualifiers.get('repository_url') || ''); + if(repositoryUrl.endsWith('/')) { + repositoryUrl = repositoryUrl.substring(0, repositoryUrl.length - 1); + } + let namespace = pkgUrl.namespace; + if(namespace) { + namespace = namespace.replace(/\./g, "/"); + } + return `${repositoryUrl}/${namespace}/${pkgUrl.name}/${pkgUrl.version}`; + } + let result = MAVEN_URL; if(pkgUrl.namespace) { - let namespace = pkgUrl.namespace?.replace(/\./g, "/"); - return `${REDHAT_REPOSITORY}${namespace}/${pkgUrl.name}/${pkgUrl.version}`; + return `${MAVEN_URL}${pkgUrl.namespace}/${pkgUrl.name}/${pkgUrl.version}`; } return result; }; @@ -79,11 +86,6 @@ export const extractDependencyUrl = (name: string) => { const pkgUrl = PackageURL.fromString(name); switch(pkgUrl.type) { case MAVEN_TYPE: - const versionMvn = pkgUrl.version; - if(versionMvn?.includes("redhat")){ - let namespace = pkgUrl.namespace?.replace(/\./g, "/"); - return `${REDHAT_REPOSITORY}${namespace}/${pkgUrl.name}/${pkgUrl.version}`; - } return `${MAVEN_URL}${pkgUrl.namespace}/${pkgUrl.name}/${pkgUrl.version}`; case GOLANG_TYPE: const version = pkgUrl.version; @@ -207,15 +209,18 @@ class PackageURL { readonly namespace: string | undefined | null; readonly name: string; readonly version: string | undefined | null; + readonly qualifiers: Map | undefined | null; constructor(type: string, namespace: string | undefined | null, name: string, - version: string | undefined | null) { + version: string | undefined | null, + qualifiers: Map | undefined | null) { this.type = type; this.namespace = namespace; this.name = name; this.version = version; + this.qualifiers = qualifiers; } toString(): string { @@ -226,13 +231,18 @@ class PackageURL { if(this.namespace) { return `${PURL_PKG_PREFIX}${this.type}/${this.namespace}/${name}`; } + if(this.qualifiers) { + return `${PURL_PKG_PREFIX}${this.type}/${name}?${Array.from(this.qualifiers.entries()).map(([key, value]) => `${key}=${value}`).join('&')}`; + } return `${PURL_PKG_PREFIX}${this.type}/${name}`; } static fromString(purl: string): PackageURL { let value = purl.replace(PURL_PKG_PREFIX, ''); + let qualifiers; const qualifiersIdx = value.indexOf('?'); if(qualifiersIdx !== -1) { + qualifiers = value.substring(qualifiersIdx + 1); value = value.substring(0, qualifiersIdx); } const type = value.substring(0, value.indexOf('/')); @@ -249,7 +259,7 @@ class PackageURL { if(version) { name = name.substring(0, name.indexOf('@')); } - return new PackageURL(type, namespace, name, version); + return new PackageURL(type, namespace, name, version, new Map(qualifiers?.split('&').map(q => q.split('=') as [string, string]) || [])); } } From f277d1e95ecf004b4af503b5cfe1dbd68bad9620 Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Mon, 10 Nov 2025 11:19:48 +0100 Subject: [PATCH 4/6] chore: remove REDIS dependency from deployment Signed-off-by: Ruben Romero Montes --- deploy/trust-da.yaml | 10 ---------- 1 file changed, 10 deletions(-) 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 From 2f3f54001d5ae1ac8d7e8482836870be172ca3d4 Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Mon, 10 Nov 2025 12:08:41 +0100 Subject: [PATCH 5/6] chore: add unit test and remove unused constant Signed-off-by: Ruben Romero Montes --- .../providers/ProviderResponseHandler.java | 4 ---- .../integration/HtmlReportTest.java | 21 +++++++++++-------- 2 files changed, 12 insertions(+), 13 deletions(-) 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 dc79a0a7..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 @@ -65,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; 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 5542c469..be588b92 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; @@ -82,27 +82,30 @@ public void testHtml() 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 From 77d6d0bf4db25612d03412a622bcdea94094102f Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Mon, 10 Nov 2025 17:02:28 +0100 Subject: [PATCH 6/6] chore: update recommendations html report Signed-off-by: Ruben Romero Montes --- .../integration/report/BrandingConfig.java | 2 +- .../integration/report/ReportTemplate.java | 12 +- .../freemarker/templates/generated/main.js | 2 +- .../integration/HtmlReportTest.java | 2 +- ui/src/api/report.ts | 2 +- ui/src/components/SummaryCard.tsx | 107 ++++++++++-------- ui/src/mocks/reportBasic.mock.ts | 10 +- ui/src/mocks/reportDocker.mock.ts | 66 +++-------- ui/src/utils/utils.ts | 6 +- 9 files changed, 94 insertions(+), 115 deletions(-) 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 dc421f7a..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 @@ -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/resources/freemarker/templates/generated/main.js b/src/main/resources/freemarker/templates/generated/main.js index d27a50ad..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(296),l=r(3029),d=r(2901),u="maven",h="https://central.sonatype.com/artifact/",g="https://pkg.go.dev/",v="https://www.npmjs.com/package/",p="https://pypi.org/project/",x="__ISSUE_ID__",f="pkg:",m=["oss-index"],j=/%[0-9A-Fa-f]{2}/,y=function(e){return"oss-index"===e?"https://ossindex.sonatype.org/user/register":""},I=function(e,n){var r=S.fromString(e),i=function(e){var n="";return e.namespace&&(n=e.type===u?"".concat(e.namespace,":"):"".concat(e.namespace,"/")),n+"".concat(e.name)}(r),t=r.version?decodeURIComponent(r.version):"";return n?i+"@".concat(t):i},A=function(e){var n=S.fromString(e);if(n.qualifiers&&n.qualifiers.has("repository_url")){var r=decodeURIComponent(n.qualifiers.get("repository_url")||"");r.endsWith("/")&&(r=r.substring(0,r.length-1));var i=n.namespace;return i&&(i=i.replace(/\./g,"/")),"".concat(r,"/").concat(i,"/").concat(n.name,"/").concat(n.version)}var t=h;return n.namespace?"".concat(h).concat(n.namespace,"/").concat(n.name,"/").concat(n.version):t},b=function(e){var n=S.fromString(e);switch(n.type){case u:return"".concat(h).concat(n.namespace,"/").concat(n.name,"/").concat(n.version);case"golang":var r=n.version;return null!==r&&void 0!==r&&r.match(/v\d\.\d.\d-\d{14}-\w{12}/)?"".concat(g).concat(n.namespace,"/").concat(n.name):"".concat(g).concat(n.namespace,"/").concat(n.name,"@").concat(n.version);case"npm":return n.namespace?"".concat(v).concat(n.namespace,"/").concat(n.name,"/v/").concat(n.version):"".concat(v).concat(n.name,"/v/").concat(n.version);case"pypi":return n.namespace?"".concat(p).concat(n.namespace,"/").concat(n.name,"/").concat(n.version):"".concat(p).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()}},C=function(e){var n=S.fromString(e).version;return n?decodeURIComponent(n):""},w=function(e){return e.toLowerCase().replace(/./,function(e){return e.toUpperCase()})},M=function(e){var n=T(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},T=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}},N=function(e,n,r,i){var t=O(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,j.test(r)?decodeURIComponent(e):e);return S.fromString(t).toString()===S.fromString(a).toString()});if(c&&c.recommendation&&a){var l=decodeURIComponent(c.recommendation);if(void 0!==D(l,r))return a+M(l)}}}return a+"search"},D=function(e,n){var r=JSON.parse(n).find(function(n){return S.fromString(n.purl).toString()===S.fromString(e).toString()});return null===r||void 0===r?void 0:r.catalogUrl},S=function(){function e(n,r,i,t,a){(0,l.A)(this,e),this.type=void 0,this.namespace=void 0,this.name=void 0,this.version=void 0,this.qualifiers=void 0,this.type=n,this.namespace=r,this.name=i,this.version=t,this.qualifiers=a}return(0,d.A)(e,[{key:"toString",value:function(){var e=this.name;return this.version&&(e+="@".concat(this.version)),this.namespace?"".concat(f).concat(this.type,"/").concat(this.namespace,"/").concat(e):this.qualifiers?"".concat(f).concat(this.type,"/").concat(e,"?").concat(Array.from(this.qualifiers.entries()).map(function(e){var n=(0,c.A)(e,2),r=n[0],i=n[1];return"".concat(r,"=").concat(i)}).join("&")):"".concat(f).concat(this.type,"/").concat(e)}}],[{key:"fromString",value:function(n){var r,i,t=n.replace(f,""),a=t.indexOf("?");-1!==a&&(i=t.substring(a+1),t=t.substring(0,a));var o,s,c=t.substring(0,t.indexOf("/")),l=t.split("/");l.length>2&&(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),B=r(3844),Z=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,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,_.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",l.displayName," Overview of security issues"]}),(0,_.jsx)(L.c,{}),(0,_.jsx)(s.E,{children:(0,_.jsxs)(B.Z,{isFlat:!0,isFullHeight:!0,children:[(0,_.jsx)(Z.a,{children:(0,_.jsx)(R.Z,{children:(0,_.jsx)(V.X,{style:{fontSize:"large"},children:i?(0,_.jsxs)(_.Fragment,{children:[t?M(t):"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(r).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,{})]})}),(!i||l.imageRecommendation.trim()&&l.imageRemediationLink.trim())&&(0,_.jsxs)(s.E,{md:c?6:void 0,children:[(0,_.jsx)(B.Z,{isFlat:!0,children:(0,_.jsxs)(Q.W,{children:[(0,_.jsx)(R.Z,{component:"h4",children:(0,_.jsxs)(V.X,{style:{fontSize:"large"},children:[(0,_.jsx)("img",{src:"data:image/png;base64,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",alt:"Trustify Icon",style:re}),"\xa0",l.displayName," Remediations"]})}),(0,_.jsx)(U.b,{children:(0,_.jsx)(G.d,{children:i?(0,_.jsxs)(H.B8,{isPlain:!0,children:[(0,_.jsx)(Y.c,{children:l.imageRecommendation}),(0,_.jsx)(Y.c,{children:(0,_.jsx)("a",{href:t?N(t,r,a.imageMapping,l.imageRemediationLink):"###",target:"_blank",rel:"noreferrer",children:(0,_.jsx)(K.$n,{variant:"primary",size:"sm",children:"Take me there"})})})]}):(0,_.jsx)(H.B8,{isPlain:!0,children:O(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,_.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"]}),c&&(0,_.jsxs)(s.E,{md:6,children:[(0,_.jsx)(B.Z,{isFlat:!0,children:(0,_.jsxs)(Q.W,{children:[(0,_.jsx)(R.Z,{component:"h4",children:(0,_.jsx)(V.X,{style:{fontSize:"large"},children:l.exploreTitle})}),(0,_.jsx)(U.b,{children:(0,_.jsx)(G.d,{children:(0,_.jsxs)(H.B8,{isPlain:!0,children:[(0,_.jsx)(Y.c,{children:l.exploreDescription}),(0,_.jsx)(Y.c,{children:(0,_.jsx)("a",{href:l.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))}})},Be=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)})})},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,_.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)(Ze.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)(Be,{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)(B.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)(B.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)(B.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)(Be,{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);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 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); } diff --git a/ui/src/api/report.ts b/ui/src/api/report.ts index 0b6b49b4..3e0951a5 100644 --- a/ui/src/api/report.ts +++ b/ui/src/api/report.ts @@ -19,7 +19,7 @@ export interface BrandingConfig { exploreTitle: string; exploreDescription: string; imageRecommendation: string; - imageRemediationLink: string; + imageRecommendationLink: string; } export interface ReportMap { diff --git a/ui/src/components/SummaryCard.tsx b/ui/src/components/SummaryCard.tsx index b9ef8bce..f49aaaf1 100644 --- a/ui/src/components/SummaryCard.tsx +++ b/ui/src/components/SummaryCard.tsx @@ -11,6 +11,7 @@ import { Divider, Grid, GridItem, + GridItemProps, Icon, List, ListItem, @@ -22,18 +23,13 @@ import {ChartCard} from './ChartCard'; import {getSourceName, getSources, Report, BrandingConfig} from '../api/report'; import SecurityCheckIcon from '../images/security-check.svg'; import TrustifyIcon from '../images/trustify.png'; -import {constructImageName, imageRemediationLink} from '../utils/utils'; +import {constructImageName, imageRecommendationLink } from '../utils/utils'; import {useAppContext} from "../App"; -const hasTrustifyProvider = (obj: any): boolean => { - return obj && typeof obj === 'object' && 'rhtpa' in obj; -}; - const ICON_STYLE = {width: "16px", height: "16px", verticalAlign: "middle"} as const; export const SummaryCard = ({report, isReportMap, purl}: { report: Report, isReportMap?: boolean, purl?: string }) => { const appContext = useAppContext(); - const showTrustifyCard = hasTrustifyProvider(appContext.report.providers); // Get branding config from appData with fallback defaults const brandingConfig: BrandingConfig = appContext.brandingConfig || { @@ -42,21 +38,25 @@ export const SummaryCard = ({report, isReportMap, purl}: { report: Report, isRep 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: '' + imageRecommendationLink: '' }; + const showExploreCard = brandingConfig.exploreTitle.trim() && brandingConfig.exploreUrl.trim() && brandingConfig.exploreDescription.trim(); + const showContainerRecommendationsCard = isReportMap && brandingConfig.imageRecommendation.trim() && brandingConfig.imageRecommendationLink.trim(); + const getBrandIcon = () => { // Always use the default icon - custom icons can be overridden via CSS return Trustify Icon; }; + const cardDivider = (12 / (1 + Number(showExploreCard) + Number(showContainerRecommendationsCard))) as GridItemProps['md']; return ( <Icon isInline status="info"> <ExclamationTriangleIcon style={{fill: "#f0ab00"}}/> - </Icon> {brandingConfig.displayName} Overview of security issues + </Icon> {brandingConfig.displayName} overview of security issues @@ -103,71 +103,84 @@ export const SummaryCard = ({report, isReportMap, purl}: { report: Report, isRep - {(!isReportMap || (brandingConfig.imageRecommendation.trim() && brandingConfig.imageRemediationLink.trim())) && ( - + + + + + + {getBrandIcon()}  + {brandingConfig.displayName} Dependency Remediations + + + + + + {getSources(report).map((source, index) => { + let remediationsSrc = + source && source.source && source.provider + ? source.source === source.provider + ? source.provider + : `${source.provider}/${source.source}` + : "default_value"; // Provide a fallback value + if (Object.keys(source.report).length > 0) { + return ( + + + Security Check Icon +  {source.report.summary.remediations} remediations are available + for {remediationsSrc} + + ) + } + return ( + + + Security Check Icon +   + There are no available remediations for your SBOM at this time for {source.provider} + + ) + }) + } + + + + +   + + {showContainerRecommendationsCard && ( + {getBrandIcon()}  - {brandingConfig.displayName} Remediations + {brandingConfig.displayName} Container Recommendations - {isReportMap ? ( {brandingConfig.imageRecommendation} - + - ) : ( - - {getSources(report).map((source, index) => { - let remediationsSrc = - source && source.source && source.provider - ? source.source === source.provider - ? source.provider - : `${source.provider}/${source.source}` - : "default_value"; // Provide a fallback value - if (Object.keys(source.report).length > 0) { - return ( - - - Security Check Icon -  {source.report.summary.remediations} remediations are available - for {remediationsSrc} - - ) - } - return ( - - - Security Check Icon -   - There are no available remediations for your SBOM at this time for {source.provider} - - ) - }) - } - - )}   )} - {showTrustifyCard && ( - + {showExploreCard && ( + diff --git a/ui/src/mocks/reportBasic.mock.ts b/ui/src/mocks/reportBasic.mock.ts index ca6fe4b0..62e4ce03 100644 --- a/ui/src/mocks/reportBasic.mock.ts +++ b/ui/src/mocks/reportBasic.mock.ts @@ -713,6 +713,12 @@ export const reportBasic: AppData = { "]", userId: 'testUser003', anonymousId: null, - writeKey: '', - rhdaSource: 'vscode' + brandingConfig : { + imageRecommendation: "Test container image recommendations for enhanced security.", + 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.", + imageRecommendationLink: "https://test-catalog.example.com/containers/", + displayName: "Trustify Test", + exploreTitle: "Learn more about Trustify", + exploreUrl: "https://guac.sh/trustify/" + } }; \ No newline at end of file diff --git a/ui/src/mocks/reportDocker.mock.ts b/ui/src/mocks/reportDocker.mock.ts index 9033ae55..52a2b92b 100644 --- a/ui/src/mocks/reportDocker.mock.ts +++ b/ui/src/mocks/reportDocker.mock.ts @@ -10,10 +10,10 @@ export const dockerReport: AppData = { "transitive": 7 }, "providers": { - "trustify": { + "rhtpa": { "status": { "ok": true, - "name": "trustify", + "name": "rhtpa", "code": 200, "message": "OK" }, @@ -751,31 +751,15 @@ export const dockerReport: AppData = { "transitive": 0 }, "providers": { - "oss-index": { - "status": { - "ok": false, - "name": "oss-index", - "code": 401, - "message": "Unauthorized: Verify the provided credentials are valid." - } - }, - "trusted-content": { + "rhtpa": { "status": { "ok": true, - "name": "trusted-content", - "code": 200, - "message": "OK" - } - }, - "tpa": { - "status": { - "ok": true, - "name": "tpa", + "name": "rhtpa", "code": 200, "message": "OK" }, "sources": { - "tpa": { + "osv": { "summary": { "direct": 0, "transitive": 0, @@ -797,37 +781,6 @@ export const dockerReport: AppData = { ] } } - }, - "snyk": { - "status": { - "ok": true, - "name": "snyk", - "code": 200, - "message": "OK" - }, - "sources": { - "snyk": { - "summary": { - "direct": 0, - "transitive": 0, - "total": 0, - "dependencies": 0, - "critical": 0, - "high": 0, - "medium": 0, - "low": 0, - "remediations": 0, - "recommendations": 1, - "unscanned": 0 - }, - "dependencies": [ - { - "ref": "pkg:oci/quay.io/default-app@0.0.1", - "recommendation": "pkg:oci/quay.io/test-app@0.0.2" - } - ] - } - } } } } @@ -847,5 +800,12 @@ export const dockerReport: AppData = { userId: 'testUser333', anonymousId: null, writeKey: '', - rhdaSource: 'trustify' + brandingConfig : { + imageRecommendation: "Test container image recommendations for enhanced security.", + 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.", + imageRecommendationLink: "https://test-catalog.example.com/containers/", + displayName: "Trustify Test", + exploreTitle: "Learn more about Trustify", + exploreUrl: "https://guac.sh/trustify/" + }, }; diff --git a/ui/src/utils/utils.ts b/ui/src/utils/utils.ts index 07de293a..2f3ad6c0 100644 --- a/ui/src/utils/utils.ts +++ b/ui/src/utils/utils.ts @@ -163,9 +163,9 @@ const isEncoded = (str: string): boolean => { return ENCODED_CHAR_REGEX.test(str); } -export const imageRemediationLink = (purl: string, report: Report, imageMapping: string, imageRemediationLink?: string) => { +export const imageRecommendationLink = (purl: string, report: Report, imageMapping: string, imageRecommendationLink?: string) => { const sources = getSources(report); - let result = imageRemediationLink || ''; + let result = imageRecommendationLink || ''; for (const key in sources) { const source = sources[key]; @@ -192,7 +192,7 @@ export const imageRemediationLink = (purl: string, report: Report, imageMapping: } } } - return result + "search"; + return result; }; const getCatalogUrlByPurl = (recommendPurl: string, imageMapping: string): string | undefined => {