From 34186353cb1f645624f2aaac2268a3b78dcc4751 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 1 Sep 2022 18:10:45 +0100 Subject: [PATCH 1/7] removed ecosystem hardcoding Signed-off-by: Sahiba Mittal --- .../parser/osv/model/Ecosystem.java | 33 -------- .../persistence/DefaultObjectGenerator.java | 17 +++++ .../resources/v1/OsvEcosystemResource | 60 +++++++++++++++ .../tasks/OsvDownloadTask.java | 75 +++++++++++++------ 4 files changed, 131 insertions(+), 54 deletions(-) delete mode 100644 src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java create mode 100644 src/main/java/org/dependencytrack/resources/v1/OsvEcosystemResource diff --git a/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java b/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java deleted file mode 100644 index a5e26c278..000000000 --- a/src/main/java/org/dependencytrack/parser/osv/model/Ecosystem.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.dependencytrack.parser.osv.model; - -public enum Ecosystem { - - ANDROID("Android"), - GSD("GSD"), - GO("Go"), - ERLANG("Hex"), - JAVASCRIPT("JavaScript"), - LINUX("Linux"), - MAVEN("Maven"), - NUGET("NuGet"), - OSSFUZZ("OSS-Fuzz"), - PACKAGIST("Packagist"), - PYPI("PyPI"), - RUBYGEMS("RubyGems"), - UVI("UVI"), - RUST("crates.io"), - NPM("npm"), - DWF("DWF"), - DEBIAN("Debian"), - GITHUB_ACTIONS("GitHub Actions"); - - private final String value; - - Ecosystem(final String value) { - this.value = value; - } - - public String getValue() { - return value; - } -} diff --git a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java index b479abfe1..22b762469 100644 --- a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java +++ b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java @@ -102,6 +102,7 @@ public void contextInitialized(final ServletContextEvent event) { loadDefaultRepositories(); loadDefaultConfigProperties(); loadDefaultNotificationPublishers(); + loadDefaultOsvEcosystems(); try { new CweImporter().processCweDefinitions(); @@ -289,4 +290,20 @@ private void loadDefaultNotificationPublishers() { } } } + + /** + * Loads the default OSV ecosystems + */ + private void loadDefaultOsvEcosystems() { + try (QueryManager qm = new QueryManager()) { + LOGGER.info("Synchronizing OSV ecosystems to datastore"); + List ecosystems = OsvDownloadTask.getEcosystems(); + if(!ecosystems.isEmpty()) { + ecosystems.forEach(ecosystem -> { + LOGGER.debug("Creating config property: " + OsvDownloadTask.OSV_CONFIG_GROUP + " / " + ecosystem); + qm.createConfigProperty(OsvDownloadTask.OSV_CONFIG_GROUP, ecosystem, "true", IConfigProperty.PropertyType.BOOLEAN, ecosystem); + }); + } + } + } } diff --git a/src/main/java/org/dependencytrack/resources/v1/OsvEcosystemResource b/src/main/java/org/dependencytrack/resources/v1/OsvEcosystemResource new file mode 100644 index 000000000..1433e793e --- /dev/null +++ b/src/main/java/org/dependencytrack/resources/v1/OsvEcosystemResource @@ -0,0 +1,60 @@ +/* + * This file is part of Dependency-Track. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.resources.v1; + +import alpine.common.logging.Logger; +import alpine.server.auth.PermissionRequired; +import alpine.server.resources.AlpineResource; +import io.swagger.annotations.*; +import org.dependencytrack.auth.Permissions; +import org.dependencytrack.persistence.QueryManager; +import org.dependencytrack.tasks.OsvDownloadTask; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.List; + +@Path("/v1/ecosystem") +@Api(value = "ecosystem", authorizations = @Authorization(value = "X-Api-Key")) +public class OsvEcosytemResource extends AlpineResource { + + private static final Logger LOGGER = Logger.getLogger(OsvEcosytemResource.class); + + @GET + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation( + value = "Returns a list of all ecosystems in OSV", + response = alpine.model.Permission.class, + responseContainer = "List" + ) + @ApiResponses(value = { + @ApiResponse(code = 401, message = "Unauthorized") + }) + @PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT) + public Response getAllEcosystems() { + try (QueryManager qm = new QueryManager()) { + OsvDownloadTask osvDownloadTask = new OsvDownloadTask(); + final List ecosystems = osvDownloadTask.getEcosystems(); + return Response.ok(ecosystems).build(); + } + } +} diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index c497e3d93..2327b7379 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -4,6 +4,7 @@ import alpine.event.framework.Event; import alpine.event.framework.LoggableSubscriber; import alpine.model.ConfigProperty; +import alpine.model.IConfigProperty; import com.github.packageurl.MalformedPackageURLException; import com.github.packageurl.PackageURL; import kong.unirest.json.JSONObject; @@ -20,7 +21,6 @@ import org.dependencytrack.model.VulnerableSoftware; import org.dependencytrack.parser.common.resolver.CweResolver; import org.dependencytrack.parser.osv.OsvAdvisoryParser; -import org.dependencytrack.parser.osv.model.Ecosystem; import org.dependencytrack.parser.osv.model.OsvAdvisory; import org.dependencytrack.parser.osv.model.OsvAffectedPackage; import org.dependencytrack.persistence.QueryManager; @@ -39,6 +39,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Scanner; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -51,13 +52,22 @@ public class OsvDownloadTask implements LoggableSubscriber { private static final Logger LOGGER = Logger.getLogger(OsvDownloadTask.class); private static final String OSV_BASE_URL = "https://osv-vulnerabilities.storage.googleapis.com/"; + public static final String OSV_CONFIG_GROUP = "osv-ecosystems"; private final boolean isEnabled; - private HttpUriRequest request; + private final List ecosystems; + private static HttpUriRequest request; public OsvDownloadTask() { try (final QueryManager qm = new QueryManager()) { final ConfigProperty enabled = qm.getConfigProperty(VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getGroupName(), VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getPropertyName()); this.isEnabled = enabled != null && Boolean.valueOf(enabled.getPropertyValue()); + this.ecosystems = new ArrayList<>(); + List ecosystemList = qm.getConfigProperties(OSV_CONFIG_GROUP); + if (ecosystemList != null) { + List enabledList = ecosystemList.stream().filter(ecosystem -> + ecosystem.getPropertyValue().equals("true")).toList(); + enabledList.forEach(ecosystem -> this.ecosystems.add(ecosystem.getPropertyName())); + } } } @@ -66,28 +76,29 @@ public void inform(Event e) { if (e instanceof OsvMirrorEvent && this.isEnabled) { - for (Ecosystem ecosystem : Ecosystem.values()) { - LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem.getValue()); - try { - String url = "https://osv-vulnerabilities.storage.googleapis.com/" - + URLEncoder.encode(ecosystem.getValue(), StandardCharsets.UTF_8.toString()).replace("+", "%20") - + "/all.zip"; - request = new HttpGet(url); - try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { - final StatusLine status = response.getStatusLine(); - if (status.getStatusCode() == 200) { - try (InputStream in = response.getEntity().getContent(); - ZipInputStream zipInput = new ZipInputStream(in)) { - unzipFolder(zipInput); + if(this.ecosystems != null && !this.ecosystems.isEmpty()) { + for (String ecosystem : this.ecosystems) { + LOGGER.info("Updating datasource with Google OSV advisories for ecosystem " + ecosystem); + try { + String url = OSV_BASE_URL + URLEncoder.encode(ecosystem, StandardCharsets.UTF_8.toString()).replace("+", "%20") + + "/all.zip"; + request = new HttpGet(url); + try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { + final StatusLine status = response.getStatusLine(); + if (status.getStatusCode() == 200) { + try (InputStream in = response.getEntity().getContent(); + ZipInputStream zipInput = new ZipInputStream(in)) { + unzipFolder(zipInput); + } + } else { + LOGGER.error("Download failed : " + status.getStatusCode() + ": " + status.getReasonPhrase()); } - } else { - LOGGER.error("Download failed " + status.getStatusCode() + ": " + status.getReasonPhrase() + url); + } catch (Exception ex) { + LOGGER.error("Exception while executing Http client request", ex); } - } catch (Exception ex) { - LOGGER.error("Exception while executing Http client request", ex); + } catch (UnsupportedEncodingException ex) { + LOGGER.error("Exception while encoding URL for ecosystem " + ecosystem); } - } catch (UnsupportedEncodingException ex) { - LOGGER.error("Exception while encoding URL for ecosystem " + ecosystem.getValue()); } } } @@ -297,4 +308,26 @@ private boolean isVulnerabilitySourceClashingWithGithubOrNvd(String source) { return Vulnerability.Source.GITHUB.toString().equals(source) || Vulnerability.Source.NVD.toString().equals(source); } + + public static List getEcosystems() { + ArrayList ecosystems = new ArrayList<>(); + String url = "https://osv-vulnerabilities.storage.googleapis.com/" + "ecosystems.txt"; + request = new HttpGet(url); + try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { + final StatusLine status = response.getStatusLine(); + if (status.getStatusCode() == 200) { + try (InputStream in = response.getEntity().getContent(); + Scanner scanner = new Scanner(in, StandardCharsets.UTF_8.name())) { + while (scanner.hasNextLine()) { + ecosystems.add(scanner.nextLine().trim()); + } + } + } else { + LOGGER.error("Ecosystem download failed : " + status.getStatusCode() + ": " + status.getReasonPhrase()); + } + } catch (Exception ex) { + LOGGER.error("Exception while executing Http request for ecosystems", ex); + } + return ecosystems; + } } \ No newline at end of file From efdba9213a62ddeb8e336c1b1fe5e418ffdc5505 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 1 Sep 2022 18:27:46 +0100 Subject: [PATCH 2/7] fixed imports Signed-off-by: Sahiba Mittal --- .../persistence/DefaultObjectGenerator.java | 10 ++-------- .../{OsvEcosystemResource => OsvEcosytemResource.java} | 0 2 files changed, 2 insertions(+), 8 deletions(-) rename src/main/java/org/dependencytrack/resources/v1/{OsvEcosystemResource => OsvEcosytemResource.java} (100%) diff --git a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java index 22b762469..46819747c 100644 --- a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java +++ b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java @@ -20,19 +20,17 @@ import alpine.common.logging.Logger; import alpine.event.framework.Event; -import alpine.model.ConfigProperty; import alpine.model.ManagedUser; import alpine.model.Permission; import alpine.model.Team; +import alpine.model.IConfigProperty; import alpine.server.auth.PasswordService; -import org.apache.commons.io.FileUtils; import org.dependencytrack.RequirementsVerifier; import org.dependencytrack.auth.Permissions; import org.dependencytrack.event.IndexEvent; import org.dependencytrack.model.Component; import org.dependencytrack.model.ConfigPropertyConstants; import org.dependencytrack.model.License; -import org.dependencytrack.model.NotificationPublisher; import org.dependencytrack.model.Project; import org.dependencytrack.model.RepositoryType; import org.dependencytrack.model.Vulnerability; @@ -41,19 +39,15 @@ import org.dependencytrack.parser.spdx.json.SpdxLicenseDetailParser; import org.dependencytrack.persistence.defaults.DefaultLicenseGroupImporter; import org.dependencytrack.search.IndexManager; +import org.dependencytrack.tasks.OsvDownloadTask; import org.dependencytrack.util.NotificationUtil; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; -import java.io.File; import java.io.IOException; -import java.net.URLDecoder; -import java.nio.file.Path; import java.util.ArrayList; import java.util.List; -import static java.nio.charset.StandardCharsets.UTF_8; - /** * Creates default objects on an empty database. * diff --git a/src/main/java/org/dependencytrack/resources/v1/OsvEcosystemResource b/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java similarity index 100% rename from src/main/java/org/dependencytrack/resources/v1/OsvEcosystemResource rename to src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java From d4b868c253719b1e4b6844cdbbe072e4a7ca5c38 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Thu, 1 Sep 2022 18:46:17 +0100 Subject: [PATCH 3/7] Update OsvEcosytemResource.java Signed-off-by: Sahiba Mittal --- .../resources/v1/OsvEcosytemResource.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java b/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java index 1433e793e..4d057c46a 100644 --- a/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java @@ -21,9 +21,12 @@ import alpine.common.logging.Logger; import alpine.server.auth.PermissionRequired; import alpine.server.resources.AlpineResource; -import io.swagger.annotations.*; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import io.swagger.annotations.ApiResponse; +import io.swagger.annotations.ApiResponses; +import io.swagger.annotations.Authorization; import org.dependencytrack.auth.Permissions; -import org.dependencytrack.persistence.QueryManager; import org.dependencytrack.tasks.OsvDownloadTask; import javax.ws.rs.GET; @@ -51,10 +54,7 @@ public class OsvEcosytemResource extends AlpineResource { }) @PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT) public Response getAllEcosystems() { - try (QueryManager qm = new QueryManager()) { - OsvDownloadTask osvDownloadTask = new OsvDownloadTask(); - final List ecosystems = osvDownloadTask.getEcosystems(); - return Response.ok(ecosystems).build(); - } + final List ecosystems = OsvDownloadTask.getEcosystems(); + return Response.ok(ecosystems).build(); } } From 0e6100022fe3138a1c0233d88c2b1a9a526a1068 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Fri, 2 Sep 2022 15:36:22 +0100 Subject: [PATCH 4/7] set default ecosystem value as false Signed-off-by: Sahiba Mittal --- .../persistence/DefaultObjectGenerator.java | 2 +- .../resources/v1/OsvEcosytemResource.java | 5 +---- .../org/dependencytrack/tasks/OsvDownloadTask.java | 10 +++++----- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java index 46819747c..f067d3ecd 100644 --- a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java +++ b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java @@ -295,7 +295,7 @@ private void loadDefaultOsvEcosystems() { if(!ecosystems.isEmpty()) { ecosystems.forEach(ecosystem -> { LOGGER.debug("Creating config property: " + OsvDownloadTask.OSV_CONFIG_GROUP + " / " + ecosystem); - qm.createConfigProperty(OsvDownloadTask.OSV_CONFIG_GROUP, ecosystem, "true", IConfigProperty.PropertyType.BOOLEAN, ecosystem); + qm.createConfigProperty(OsvDownloadTask.OSV_CONFIG_GROUP, ecosystem, "false", IConfigProperty.PropertyType.BOOLEAN, ecosystem); }); } } diff --git a/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java b/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java index 4d057c46a..f71891aca 100644 --- a/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java @@ -18,7 +18,6 @@ */ package org.dependencytrack.resources.v1; -import alpine.common.logging.Logger; import alpine.server.auth.PermissionRequired; import alpine.server.resources.AlpineResource; import io.swagger.annotations.Api; @@ -40,13 +39,11 @@ @Api(value = "ecosystem", authorizations = @Authorization(value = "X-Api-Key")) public class OsvEcosytemResource extends AlpineResource { - private static final Logger LOGGER = Logger.getLogger(OsvEcosytemResource.class); - @GET @Produces(MediaType.APPLICATION_JSON) @ApiOperation( value = "Returns a list of all ecosystems in OSV", - response = alpine.model.Permission.class, + response = String.class, responseContainer = "List" ) @ApiResponses(value = { diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index 2327b7379..7e2d9d97b 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -4,7 +4,6 @@ import alpine.event.framework.Event; import alpine.event.framework.LoggableSubscriber; import alpine.model.ConfigProperty; -import alpine.model.IConfigProperty; import com.github.packageurl.MalformedPackageURLException; import com.github.packageurl.PackageURL; import kong.unirest.json.JSONObject; @@ -45,7 +44,8 @@ import static org.dependencytrack.model.ConfigPropertyConstants.VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED; import static org.dependencytrack.model.Severity.getSeverityByLevel; -import static org.dependencytrack.util.VulnerabilityUtil.*; +import static org.dependencytrack.util.VulnerabilityUtil.normalizedCvssV3Score; +import static org.dependencytrack.util.VulnerabilityUtil.normalizedCvssV2Score; public class OsvDownloadTask implements LoggableSubscriber { @@ -62,9 +62,9 @@ public OsvDownloadTask() { final ConfigProperty enabled = qm.getConfigProperty(VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getGroupName(), VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getPropertyName()); this.isEnabled = enabled != null && Boolean.valueOf(enabled.getPropertyValue()); this.ecosystems = new ArrayList<>(); - List ecosystemList = qm.getConfigProperties(OSV_CONFIG_GROUP); - if (ecosystemList != null) { - List enabledList = ecosystemList.stream().filter(ecosystem -> + List ecosystemConfig = qm.getConfigProperties(OSV_CONFIG_GROUP); + if (ecosystemConfig != null) { + List enabledList = ecosystemConfig.stream().filter(ecosystem -> ecosystem.getPropertyValue().equals("true")).toList(); enabledList.forEach(ecosystem -> this.ecosystems.add(ecosystem.getPropertyName())); } From 60bf40f6e8ffdf759adde92229e55e3011f21b2e Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Mon, 5 Sep 2022 13:41:03 +0100 Subject: [PATCH 5/7] refactoring Signed-off-by: Sahiba Mittal --- .../persistence/DefaultObjectGenerator.java | 19 ------ .../resources/v1/ConfigPropertyResource.java | 31 ++++++++++ .../resources/v1/OsvEcosytemResource.java | 4 +- .../tasks/OsvDownloadTask.java | 12 ++-- .../org/dependencytrack/ResourceTest.java | 1 + .../v1/ConfigPropertyResourceTest.java | 37 +++++++++++ .../v1/OsvEcosystemResourceTest.java | 62 +++++++++++++++++++ 7 files changed, 139 insertions(+), 27 deletions(-) create mode 100644 src/test/java/org/dependencytrack/resources/v1/OsvEcosystemResourceTest.java diff --git a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java index f067d3ecd..961701209 100644 --- a/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java +++ b/src/main/java/org/dependencytrack/persistence/DefaultObjectGenerator.java @@ -23,7 +23,6 @@ import alpine.model.ManagedUser; import alpine.model.Permission; import alpine.model.Team; -import alpine.model.IConfigProperty; import alpine.server.auth.PasswordService; import org.dependencytrack.RequirementsVerifier; import org.dependencytrack.auth.Permissions; @@ -39,7 +38,6 @@ import org.dependencytrack.parser.spdx.json.SpdxLicenseDetailParser; import org.dependencytrack.persistence.defaults.DefaultLicenseGroupImporter; import org.dependencytrack.search.IndexManager; -import org.dependencytrack.tasks.OsvDownloadTask; import org.dependencytrack.util.NotificationUtil; import javax.servlet.ServletContextEvent; @@ -96,7 +94,6 @@ public void contextInitialized(final ServletContextEvent event) { loadDefaultRepositories(); loadDefaultConfigProperties(); loadDefaultNotificationPublishers(); - loadDefaultOsvEcosystems(); try { new CweImporter().processCweDefinitions(); @@ -284,20 +281,4 @@ private void loadDefaultNotificationPublishers() { } } } - - /** - * Loads the default OSV ecosystems - */ - private void loadDefaultOsvEcosystems() { - try (QueryManager qm = new QueryManager()) { - LOGGER.info("Synchronizing OSV ecosystems to datastore"); - List ecosystems = OsvDownloadTask.getEcosystems(); - if(!ecosystems.isEmpty()) { - ecosystems.forEach(ecosystem -> { - LOGGER.debug("Creating config property: " + OsvDownloadTask.OSV_CONFIG_GROUP + " / " + ecosystem); - qm.createConfigProperty(OsvDownloadTask.OSV_CONFIG_GROUP, ecosystem, "false", IConfigProperty.PropertyType.BOOLEAN, ecosystem); - }); - } - } - } } diff --git a/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java b/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java index ebd45b329..924a2e852 100644 --- a/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java @@ -20,6 +20,7 @@ import alpine.common.logging.Logger; import alpine.model.ConfigProperty; +import alpine.model.IConfigProperty; import alpine.server.auth.PermissionRequired; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -137,4 +138,34 @@ public Response updateConfigProperty(List list) { } return Response.ok(returnList).build(); } + + @POST + @Path("upsert") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + @ApiOperation( + value = "Creates/updates a config property", + response = ConfigProperty.class + ) + @ApiResponses(value = { + @ApiResponse(code = 401, message = "Unauthorized") + }) + @PermissionRequired(Permissions.Constants.SYSTEM_CONFIGURATION) + public Response upsertsConfigProperty(ConfigProperty json) { + final Validator validator = super.getValidator(); + failOnValidationError( + validator.validateProperty(json, "groupName"), + validator.validateProperty(json, "propertyName"), + validator.validateProperty(json, "propertyValue") + ); + try (QueryManager qm = new QueryManager()) { + final ConfigProperty property = qm.getConfigProperty(json.getGroupName(), json.getPropertyName()); + if (property == null) { + ConfigProperty configProperty = qm.createConfigProperty(json.getGroupName(), json.getPropertyName(), json.getPropertyValue(), IConfigProperty.PropertyType.BOOLEAN, json.getPropertyName()); + return Response.ok(configProperty).build(); + } else { + return updatePropertyValue(qm, json, property); + } + } + } } diff --git a/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java b/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java index f71891aca..6c5b321e4 100644 --- a/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/OsvEcosytemResource.java @@ -35,7 +35,7 @@ import javax.ws.rs.core.Response; import java.util.List; -@Path("/v1/ecosystem") +@Path("/v1/integration/osv/ecosystem") @Api(value = "ecosystem", authorizations = @Authorization(value = "X-Api-Key")) public class OsvEcosytemResource extends AlpineResource { @@ -49,7 +49,7 @@ public class OsvEcosytemResource extends AlpineResource { @ApiResponses(value = { @ApiResponse(code = 401, message = "Unauthorized") }) - @PermissionRequired(Permissions.Constants.ACCESS_MANAGEMENT) + @PermissionRequired(Permissions.Constants.SYSTEM_CONFIGURATION) public Response getAllEcosystems() { final List ecosystems = OsvDownloadTask.getEcosystems(); return Response.ok(ecosystems).build(); diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index 7e2d9d97b..4c9fdb809 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -50,12 +50,10 @@ public class OsvDownloadTask implements LoggableSubscriber { private static final Logger LOGGER = Logger.getLogger(OsvDownloadTask.class); - private static final String OSV_BASE_URL = "https://osv-vulnerabilities.storage.googleapis.com/"; public static final String OSV_CONFIG_GROUP = "osv-ecosystems"; private final boolean isEnabled; private final List ecosystems; - private static HttpUriRequest request; public OsvDownloadTask() { try (final QueryManager qm = new QueryManager()) { @@ -82,7 +80,7 @@ public void inform(Event e) { try { String url = OSV_BASE_URL + URLEncoder.encode(ecosystem, StandardCharsets.UTF_8.toString()).replace("+", "%20") + "/all.zip"; - request = new HttpGet(url); + HttpUriRequest request = new HttpGet(url); try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { final StatusLine status = response.getStatusLine(); if (status.getStatusCode() == 200) { @@ -311,15 +309,17 @@ private boolean isVulnerabilitySourceClashingWithGithubOrNvd(String source) { public static List getEcosystems() { ArrayList ecosystems = new ArrayList<>(); - String url = "https://osv-vulnerabilities.storage.googleapis.com/" + "ecosystems.txt"; - request = new HttpGet(url); + String url = OSV_BASE_URL + "ecosystems.txt"; + HttpUriRequest request = new HttpGet(url); try (final CloseableHttpResponse response = HttpClientPool.getClient().execute(request)) { final StatusLine status = response.getStatusLine(); if (status.getStatusCode() == 200) { try (InputStream in = response.getEntity().getContent(); Scanner scanner = new Scanner(in, StandardCharsets.UTF_8.name())) { while (scanner.hasNextLine()) { - ecosystems.add(scanner.nextLine().trim()); + if(!scanner.nextLine().isBlank()) { + ecosystems.add(scanner.nextLine().trim()); + } } } } else { diff --git a/src/test/java/org/dependencytrack/ResourceTest.java b/src/test/java/org/dependencytrack/ResourceTest.java index 46533c649..6670bc493 100644 --- a/src/test/java/org/dependencytrack/ResourceTest.java +++ b/src/test/java/org/dependencytrack/ResourceTest.java @@ -63,6 +63,7 @@ public abstract class ResourceTest extends JerseyTest { protected final String V1_NOTIFICATION_RULE = "/v1/notification/rule"; protected final String V1_OIDC = "/v1/oidc"; protected final String V1_PERMISSION = "/v1/permission"; + protected final String V1_OSV_ECOSYSTEM = "/v1/integration/osv/ecosystem"; protected final String V1_POLICY = "/v1/policy"; protected final String V1_POLICY_VIOLATION = "/v1/violation"; protected final String V1_PROJECT = "/v1/project"; diff --git a/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java index ab4417e8b..5e6cb222d 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java @@ -226,4 +226,41 @@ public void updateConfigPropertiesAggregateTest() { String body = json.getString(3); Assert.assertEquals("A Task scheduler cadence ("+prop4.getPropertyName()+") cannot be inferior to one hour.A value of -2 was provided.", body); } + + @Test + public void upsertConfigPropertyUpdateTest() { + ConfigProperty property = qm.createConfigProperty("my.group", "updateBoolean", "false", IConfigProperty.PropertyType.BOOLEAN, "A boolean"); + ConfigProperty request = qm.detach(ConfigProperty.class, property.getId()); + request.setPropertyValue("true"); + Response response = target(V1_CONFIG_PROPERTY+"/upsert").request() + .header(X_API_KEY, apiKey) + .post(Entity.entity(request, MediaType.APPLICATION_JSON)); + Assert.assertEquals(200, response.getStatus(), 0); + JsonObject json = parseJsonObject(response); + Assert.assertNotNull(json); + Assert.assertEquals("my.group", json.getString("groupName")); + Assert.assertEquals("updateBoolean", json.getString("propertyName")); + Assert.assertEquals("true", json.getString("propertyValue")); + Assert.assertEquals("BOOLEAN", json.getString("propertyType")); + Assert.assertEquals("A boolean", json.getString("description")); + } + + @Test + public void upsertConfigPropertyCreateTest() { + ConfigProperty configProperty = new ConfigProperty(); + configProperty.setGroupName("my.group"); + configProperty.setPropertyName("createBoolean"); + configProperty.setPropertyValue("false"); + Response response = target(V1_CONFIG_PROPERTY+"/upsert").request() + .header(X_API_KEY, apiKey) + .post(Entity.entity(configProperty, MediaType.APPLICATION_JSON)); + Assert.assertEquals(200, response.getStatus(), 0); + JsonObject json = parseJsonObject(response); + Assert.assertNotNull(json); + Assert.assertEquals("my.group", json.getString("groupName")); + Assert.assertEquals("createBoolean", json.getString("propertyName")); + Assert.assertEquals("false", json.getString("propertyValue")); + Assert.assertEquals("BOOLEAN", json.getString("propertyType")); + Assert.assertEquals("createBoolean", json.getString("description")); + } } diff --git a/src/test/java/org/dependencytrack/resources/v1/OsvEcosystemResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/OsvEcosystemResourceTest.java new file mode 100644 index 000000000..e39da9451 --- /dev/null +++ b/src/test/java/org/dependencytrack/resources/v1/OsvEcosystemResourceTest.java @@ -0,0 +1,62 @@ +/* + * This file is part of Dependency-Track. + * + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * Copyright (c) Steve Springett. All Rights Reserved. + */ +package org.dependencytrack.resources.v1; + +import alpine.server.filters.ApiFilter; +import alpine.server.filters.AuthenticationFilter; +import org.dependencytrack.ResourceTest; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.servlet.ServletContainer; +import org.glassfish.jersey.test.DeploymentContext; +import org.glassfish.jersey.test.ServletDeploymentContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import javax.json.JsonArray; +import javax.ws.rs.core.Response; + +public class OsvEcosystemResourceTest extends ResourceTest { + + @Override + protected DeploymentContext configureDeployment() { + return ServletDeploymentContext.forServlet(new ServletContainer( + new ResourceConfig(OsvEcosytemResource.class) + .register(ApiFilter.class) + .register(AuthenticationFilter.class))) + .build(); + } + + @Before + public void before() throws Exception { + super.before(); + } + + @Test + public void getAllEcosystemsTest() { + Response response = target(V1_OSV_ECOSYSTEM).request() + .header(X_API_KEY, apiKey) + .get(Response.class); + Assert.assertEquals(200, response.getStatus(), 0); + Assert.assertNull(response.getHeaderString(TOTAL_COUNT_HEADER)); + JsonArray json = parseJsonArray(response); + Assert.assertNotNull(json); + Assert.assertFalse(json.isEmpty()); + } +} From 667bddd891dce11ffc7b055f5498733961703046 Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 6 Sep 2022 11:43:27 +0100 Subject: [PATCH 6/7] changed ecosystem config structure Signed-off-by: Sahiba Mittal --- .../model/ConfigPropertyConstants.java | 2 +- .../resources/v1/ConfigPropertyResource.java | 31 ---------------- .../tasks/OsvDownloadTask.java | 26 +++++++------ .../v1/ConfigPropertyResourceTest.java | 37 ------------------- .../task/OsvDownloadTaskTest.java | 26 ++++++++++++- 5 files changed, 41 insertions(+), 81 deletions(-) diff --git a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java index 48b5a86ff..841b77c90 100644 --- a/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java +++ b/src/main/java/org/dependencytrack/model/ConfigPropertyConstants.java @@ -51,7 +51,7 @@ public enum ConfigPropertyConstants { VULNERABILITY_SOURCE_NVD_FEEDS_URL("vuln-source", "nvd.feeds.url", "https://nvd.nist.gov/feeds", PropertyType.URL, "A base URL pointing to the hostname and path of the NVD feeds"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ENABLED("vuln-source", "github.advisories.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable GitHub Advisories"), VULNERABILITY_SOURCE_GITHUB_ADVISORIES_ACCESS_TOKEN("vuln-source", "github.advisories.access.token", null, PropertyType.STRING, "The access token used for GitHub API authentication"), - VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED("vuln-source", "google.osv.enabled", "false", PropertyType.BOOLEAN, "Flag to enable/disable Google OSV"), + VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED("vuln-source", "google.osv.enabled", null, PropertyType.STRING, "List of enabled ecosystems to mirror OSV"), VULNERABILITY_SOURCE_EPSS_ENABLED("vuln-source", "epss.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable Exploit Prediction Scoring System"), VULNERABILITY_SOURCE_EPSS_FEEDS_URL("vuln-source", "epss.feeds.url", "https://epss.cyentia.com", PropertyType.URL, "A base URL pointing to the hostname and path of the EPSS feeds"), ACCEPT_ARTIFACT_CYCLONEDX("artifact", "cyclonedx.enabled", "true", PropertyType.BOOLEAN, "Flag to enable/disable the systems ability to accept CycloneDX uploads"), diff --git a/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java b/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java index 924a2e852..ebd45b329 100644 --- a/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java +++ b/src/main/java/org/dependencytrack/resources/v1/ConfigPropertyResource.java @@ -20,7 +20,6 @@ import alpine.common.logging.Logger; import alpine.model.ConfigProperty; -import alpine.model.IConfigProperty; import alpine.server.auth.PermissionRequired; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -138,34 +137,4 @@ public Response updateConfigProperty(List list) { } return Response.ok(returnList).build(); } - - @POST - @Path("upsert") - @Consumes(MediaType.APPLICATION_JSON) - @Produces(MediaType.APPLICATION_JSON) - @ApiOperation( - value = "Creates/updates a config property", - response = ConfigProperty.class - ) - @ApiResponses(value = { - @ApiResponse(code = 401, message = "Unauthorized") - }) - @PermissionRequired(Permissions.Constants.SYSTEM_CONFIGURATION) - public Response upsertsConfigProperty(ConfigProperty json) { - final Validator validator = super.getValidator(); - failOnValidationError( - validator.validateProperty(json, "groupName"), - validator.validateProperty(json, "propertyName"), - validator.validateProperty(json, "propertyValue") - ); - try (QueryManager qm = new QueryManager()) { - final ConfigProperty property = qm.getConfigProperty(json.getGroupName(), json.getPropertyName()); - if (property == null) { - ConfigProperty configProperty = qm.createConfigProperty(json.getGroupName(), json.getPropertyName(), json.getPropertyValue(), IConfigProperty.PropertyType.BOOLEAN, json.getPropertyName()); - return Response.ok(configProperty).build(); - } else { - return updatePropertyValue(qm, json, property); - } - } - } } diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index 4c9fdb809..23e52ad08 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -39,6 +39,7 @@ import java.util.Date; import java.util.List; import java.util.Scanner; +import java.util.Arrays; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; @@ -51,20 +52,21 @@ public class OsvDownloadTask implements LoggableSubscriber { private static final Logger LOGGER = Logger.getLogger(OsvDownloadTask.class); private static final String OSV_BASE_URL = "https://osv-vulnerabilities.storage.googleapis.com/"; - public static final String OSV_CONFIG_GROUP = "osv-ecosystems"; - private final boolean isEnabled; - private final List ecosystems; + private String ecosystemConfig; + private List ecosystems; + + public List getEnabledEcosystems() { + return this.ecosystems; + } public OsvDownloadTask() { try (final QueryManager qm = new QueryManager()) { final ConfigProperty enabled = qm.getConfigProperty(VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getGroupName(), VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getPropertyName()); - this.isEnabled = enabled != null && Boolean.valueOf(enabled.getPropertyValue()); - this.ecosystems = new ArrayList<>(); - List ecosystemConfig = qm.getConfigProperties(OSV_CONFIG_GROUP); - if (ecosystemConfig != null) { - List enabledList = ecosystemConfig.stream().filter(ecosystem -> - ecosystem.getPropertyValue().equals("true")).toList(); - enabledList.forEach(ecosystem -> this.ecosystems.add(ecosystem.getPropertyName())); + if (enabled != null) { + this.ecosystemConfig = enabled.getPropertyValue(); + if (this.ecosystemConfig != null) { + ecosystems = Arrays.stream(this.ecosystemConfig.split(";")).map(String::trim).toList(); + } } } } @@ -72,7 +74,7 @@ public OsvDownloadTask() { @Override public void inform(Event e) { - if (e instanceof OsvMirrorEvent && this.isEnabled) { + if (e instanceof OsvMirrorEvent) { if(this.ecosystems != null && !this.ecosystems.isEmpty()) { for (String ecosystem : this.ecosystems) { @@ -98,6 +100,8 @@ public void inform(Event e) { LOGGER.error("Exception while encoding URL for ecosystem " + ecosystem); } } + } else { + LOGGER.info("Google OSV mirroring is disabled. No ecosystem selected."); } } } diff --git a/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java b/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java index 5e6cb222d..ab4417e8b 100644 --- a/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java +++ b/src/test/java/org/dependencytrack/resources/v1/ConfigPropertyResourceTest.java @@ -226,41 +226,4 @@ public void updateConfigPropertiesAggregateTest() { String body = json.getString(3); Assert.assertEquals("A Task scheduler cadence ("+prop4.getPropertyName()+") cannot be inferior to one hour.A value of -2 was provided.", body); } - - @Test - public void upsertConfigPropertyUpdateTest() { - ConfigProperty property = qm.createConfigProperty("my.group", "updateBoolean", "false", IConfigProperty.PropertyType.BOOLEAN, "A boolean"); - ConfigProperty request = qm.detach(ConfigProperty.class, property.getId()); - request.setPropertyValue("true"); - Response response = target(V1_CONFIG_PROPERTY+"/upsert").request() - .header(X_API_KEY, apiKey) - .post(Entity.entity(request, MediaType.APPLICATION_JSON)); - Assert.assertEquals(200, response.getStatus(), 0); - JsonObject json = parseJsonObject(response); - Assert.assertNotNull(json); - Assert.assertEquals("my.group", json.getString("groupName")); - Assert.assertEquals("updateBoolean", json.getString("propertyName")); - Assert.assertEquals("true", json.getString("propertyValue")); - Assert.assertEquals("BOOLEAN", json.getString("propertyType")); - Assert.assertEquals("A boolean", json.getString("description")); - } - - @Test - public void upsertConfigPropertyCreateTest() { - ConfigProperty configProperty = new ConfigProperty(); - configProperty.setGroupName("my.group"); - configProperty.setPropertyName("createBoolean"); - configProperty.setPropertyValue("false"); - Response response = target(V1_CONFIG_PROPERTY+"/upsert").request() - .header(X_API_KEY, apiKey) - .post(Entity.entity(configProperty, MediaType.APPLICATION_JSON)); - Assert.assertEquals(200, response.getStatus(), 0); - JsonObject json = parseJsonObject(response); - Assert.assertNotNull(json); - Assert.assertEquals("my.group", json.getString("groupName")); - Assert.assertEquals("createBoolean", json.getString("propertyName")); - Assert.assertEquals("false", json.getString("propertyValue")); - Assert.assertEquals("BOOLEAN", json.getString("propertyType")); - Assert.assertEquals("createBoolean", json.getString("description")); - } } diff --git a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java index b0f0a2257..d94de5d9b 100644 --- a/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java +++ b/src/test/java/org/dependencytrack/task/OsvDownloadTaskTest.java @@ -15,6 +15,7 @@ */ package org.dependencytrack.task; +import alpine.model.IConfigProperty; import com.github.packageurl.PackageURL; import kong.unirest.json.JSONObject; import org.apache.commons.lang3.StringUtils; @@ -27,6 +28,7 @@ import org.dependencytrack.persistence.CweImporter; import org.dependencytrack.tasks.OsvDownloadTask; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import java.io.IOException; @@ -37,10 +39,24 @@ import java.util.List; import java.util.function.Consumer; +import static org.dependencytrack.model.ConfigPropertyConstants.VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED; + public class OsvDownloadTaskTest extends PersistenceCapableTest { private JSONObject jsonObject; private final OsvAdvisoryParser parser = new OsvAdvisoryParser(); - private final OsvDownloadTask task = new OsvDownloadTask(); + private OsvDownloadTask task; + + @Before + public void setUp() { + qm.createConfigProperty(VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getGroupName(), + VULNERABILITY_SOURCE_GOOGLE_OSV_ENABLED.getPropertyName(), + "Maven;DWF", + IConfigProperty.PropertyType.STRING, + "List of ecosystems"); + task = new OsvDownloadTask(); + Assert.assertNotNull(task.getEnabledEcosystems()); + Assert.assertEquals(2, task.getEnabledEcosystems().size()); + } @Test public void testParseOSVJsonToAdvisoryAndSave() throws Exception { @@ -223,6 +239,14 @@ public void testCommitHashRangesAndVersions() throws IOException { Assert.assertEquals(Severity.MEDIUM, vulnerability.getSeverity()); } + @Test + public void testGetEcosystems() { + + List ecosystems = task.getEcosystems(); + Assert.assertNotNull(ecosystems); + Assert.assertTrue(ecosystems.contains("Maven")); + } + private void prepareJsonObject(String filePath) throws IOException { // parse OSV json file to Advisory object String jsonString = new String(Files.readAllBytes(Paths.get(filePath))); From 984f29a066d4593dad44c6e42a6bb788264bb3ff Mon Sep 17 00:00:00 2001 From: Sahiba Mittal Date: Tue, 6 Sep 2022 19:01:11 +0100 Subject: [PATCH 7/7] Update src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java Co-authored-by: Niklas Signed-off-by: Sahiba Mittal --- src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java index 23e52ad08..090fbbd40 100644 --- a/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java +++ b/src/main/java/org/dependencytrack/tasks/OsvDownloadTask.java @@ -321,8 +321,9 @@ public static List getEcosystems() { try (InputStream in = response.getEntity().getContent(); Scanner scanner = new Scanner(in, StandardCharsets.UTF_8.name())) { while (scanner.hasNextLine()) { - if(!scanner.nextLine().isBlank()) { - ecosystems.add(scanner.nextLine().trim()); + final String line = scanner.nextLine(); + if(!line.isBlank()) { + ecosystems.add(line.trim()); } } }