diff --git a/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java b/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java index d0aabcda81..7bd22f8854 100644 --- a/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java +++ b/src/main/java/org/dependencytrack/model/RepositoryMetaComponent.java @@ -18,19 +18,21 @@ */ package org.dependencytrack.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import io.swagger.v3.oas.annotations.media.Schema; +import java.io.Serializable; +import java.util.Date; -import jakarta.validation.constraints.NotNull; import javax.jdo.annotations.Column; import javax.jdo.annotations.IdGeneratorStrategy; import javax.jdo.annotations.Index; import javax.jdo.annotations.PersistenceCapable; import javax.jdo.annotations.Persistent; import javax.jdo.annotations.PrimaryKey; -import java.io.Serializable; -import java.util.Date; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; /** * Tracks third-party metadata about component groups from external repositories @@ -81,6 +83,21 @@ public class RepositoryMetaComponent implements Serializable { @NotNull private String latestVersion; + /** + * Whether the component is deprecated or not. + */ + @Persistent + @Column(name = "IS_DEPRECATED", allowsNull = "false", defaultValue="false") + @NotNull + private boolean isDeprecated; // Added in 4.13.0 + + /** + * Whether the component is deprecated or not. + */ + @Persistent + @Column(name = "DEPRECATION_MESSAGE", allowsNull = "true") + private String deprecationMessage; // Added in 4.13.0 + /** * The optional date when the component was last published. */ @@ -140,6 +157,22 @@ public void setLatestVersion(String latestVersion) { this.latestVersion = latestVersion; } + public boolean isDeprecated() { + return isDeprecated; + } + + public void setDeprecated(boolean deprecated) { + isDeprecated = deprecated; + } + + public String getDeprecationMessage() { + return deprecationMessage; + } + + public void setDeprecationMessage(String deprecationMessage) { + this.deprecationMessage = deprecationMessage; + } + public Date getPublished() { return published; } diff --git a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java index 9d5b542a4e..ea9f7046dc 100644 --- a/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/ComponentQueryManager.java @@ -167,7 +167,7 @@ public PaginatedResult getComponents(final Project project, final boolean includ " SELECT FROM org.dependencytrack.model.RepositoryMetaComponent m " + " WHERE m.name == this.name " + " && (m.namespace == this.group || (m.namespace == null && this.group == null)) " + - " && m.latestVersion != this.version " + + " && (m.latestVersion != this.version || m.isDeprecated)" + " && this.purl.matches('pkg:' + m.repositoryType.toString().toLowerCase() + '/%') " + " ).isEmpty()"; } @@ -579,6 +579,8 @@ public Map getDependencyGraphForComponents(Project project, L if (repoMetaComponent != null) { RepositoryMetaComponent transientRepoMetaComponent = new RepositoryMetaComponent(); transientRepoMetaComponent.setLatestVersion(repoMetaComponent.getLatestVersion()); + transientRepoMetaComponent.setDeprecated(repoMetaComponent.isDeprecated()); + transientRepoMetaComponent.setDeprecationMessage(repoMetaComponent.getDeprecationMessage()); transientComponent.setRepositoryMeta(transientRepoMetaComponent); } } diff --git a/src/main/java/org/dependencytrack/persistence/FindingsQueryManager.java b/src/main/java/org/dependencytrack/persistence/FindingsQueryManager.java index f2d604ccc0..d7f9235ba8 100644 --- a/src/main/java/org/dependencytrack/persistence/FindingsQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/FindingsQueryManager.java @@ -18,8 +18,16 @@ */ package org.dependencytrack.persistence; -import alpine.resources.AlpineRequest; -import com.github.packageurl.PackageURL; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.jdo.PersistenceManager; +import javax.jdo.Query; + import org.dependencytrack.model.Analysis; import org.dependencytrack.model.AnalysisComment; import org.dependencytrack.model.AnalysisJustification; @@ -35,14 +43,9 @@ import org.dependencytrack.persistence.RepositoryQueryManager.RepositoryMetaComponentSearch; import org.dependencytrack.util.PurlUtil; -import javax.jdo.PersistenceManager; -import javax.jdo.Query; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; +import com.github.packageurl.PackageURL; + +import alpine.resources.AlpineRequest; public class FindingsQueryManager extends QueryManager implements IQueryManager { @@ -331,6 +334,8 @@ public List getFindings(Project project, boolean includeSuppressed) { if (affectedFindings != null) { for (final Finding finding : affectedFindings) { finding.getComponent().put("latestVersion", metaComponent.getLatestVersion()); + finding.getComponent().put("isDeprecated", metaComponent.isDeprecated()); + finding.getComponent().put("deprecationMessage", metaComponent.getDeprecationMessage()); } } }); diff --git a/src/main/java/org/dependencytrack/persistence/FindingsSearchQueryManager.java b/src/main/java/org/dependencytrack/persistence/FindingsSearchQueryManager.java index b03be7a40d..197b086886 100644 --- a/src/main/java/org/dependencytrack/persistence/FindingsSearchQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/FindingsSearchQueryManager.java @@ -141,6 +141,7 @@ public PaginatedResult getAllFindings(final Map filters, final b final RepositoryMetaComponent repoMetaComponent = getRepositoryMetaComponent(type, purl.getNamespace(), purl.getName()); if (repoMetaComponent != null) { finding.getComponent().put("latestVersion", repoMetaComponent.getLatestVersion()); + finding.getComponent().put("isDeprecated", repoMetaComponent.isDeprecated()); } } diff --git a/src/main/java/org/dependencytrack/persistence/RepositoryQueryManager.java b/src/main/java/org/dependencytrack/persistence/RepositoryQueryManager.java index 59094731f3..80db3ddd60 100644 --- a/src/main/java/org/dependencytrack/persistence/RepositoryQueryManager.java +++ b/src/main/java/org/dependencytrack/persistence/RepositoryQueryManager.java @@ -18,17 +18,6 @@ */ package org.dependencytrack.persistence; -import alpine.common.logging.Logger; -import alpine.persistence.PaginatedResult; -import alpine.resources.AlpineRequest; -import alpine.security.crypto.DataEncryption; -import org.apache.commons.lang3.StringUtils; -import org.dependencytrack.model.Repository; -import org.dependencytrack.model.RepositoryMetaComponent; -import org.dependencytrack.model.RepositoryType; - -import javax.jdo.PersistenceManager; -import javax.jdo.Query; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -37,6 +26,19 @@ import java.util.Map; import java.util.UUID; +import javax.jdo.PersistenceManager; +import javax.jdo.Query; + +import org.apache.commons.lang3.StringUtils; +import org.dependencytrack.model.Repository; +import org.dependencytrack.model.RepositoryMetaComponent; +import org.dependencytrack.model.RepositoryType; + +import alpine.common.logging.Logger; +import alpine.persistence.PaginatedResult; +import alpine.resources.AlpineRequest; +import alpine.security.crypto.DataEncryption; + public class RepositoryQueryManager extends QueryManager implements IQueryManager { private static final Logger LOGGER = Logger.getLogger(RepositoryQueryManager.class); @@ -241,6 +243,8 @@ public synchronized RepositoryMetaComponent synchronizeRepositoryMetaComponent( metaComponent.setNamespace(transientRepositoryMetaComponent.getNamespace()); metaComponent.setLastCheck(transientRepositoryMetaComponent.getLastCheck()); metaComponent.setLatestVersion(transientRepositoryMetaComponent.getLatestVersion()); + metaComponent.setDeprecated(transientRepositoryMetaComponent.isDeprecated()); + metaComponent.setDeprecationMessage(transientRepositoryMetaComponent.getDeprecationMessage()); metaComponent.setName(transientRepositoryMetaComponent.getName()); metaComponent.setPublished(transientRepositoryMetaComponent.getPublished()); return persist(metaComponent); diff --git a/src/main/java/org/dependencytrack/tasks/repositories/MetaModel.java b/src/main/java/org/dependencytrack/tasks/repositories/MetaModel.java index e40a95381e..5637acf94d 100644 --- a/src/main/java/org/dependencytrack/tasks/repositories/MetaModel.java +++ b/src/main/java/org/dependencytrack/tasks/repositories/MetaModel.java @@ -18,14 +18,16 @@ */ package org.dependencytrack.tasks.repositories; -import org.dependencytrack.model.Component; - import java.util.Date; +import org.dependencytrack.model.Component; + public class MetaModel { private final Component component; private String latestVersion; + private boolean isDeprecated; + private String deprecationMessage; private Date publishedTimestamp; public MetaModel(final Component component) { @@ -44,6 +46,22 @@ public void setLatestVersion(final String latestVersion) { this.latestVersion = latestVersion; } + public boolean isDeprecated() { + return isDeprecated; + } + + public void setDeprecated(boolean isDeprecated) { + this.isDeprecated = isDeprecated; + } + + public String getDeprecationMessage() { + return deprecationMessage; + } + + public void setDeprecationMessage(final String deprecationMessage) { + this.deprecationMessage = deprecationMessage; + } + public Date getPublishedTimestamp() { return publishedTimestamp; } diff --git a/src/main/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzer.java b/src/main/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzer.java index 02977df68c..063cc1bb61 100644 --- a/src/main/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzer.java +++ b/src/main/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzer.java @@ -18,8 +18,8 @@ */ package org.dependencytrack.tasks.repositories; -import alpine.common.logging.Logger; -import com.github.packageurl.PackageURL; +import java.io.IOException; + import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.util.EntityUtils; @@ -28,7 +28,9 @@ import org.dependencytrack.model.RepositoryType; import org.json.JSONObject; -import java.io.IOException; +import com.github.packageurl.PackageURL; + +import alpine.common.logging.Logger; /** * An IMetaAnalyzer implementation that supports NPM. @@ -40,7 +42,8 @@ public class NpmMetaAnalyzer extends AbstractMetaAnalyzer { private static final Logger LOGGER = Logger.getLogger(NpmMetaAnalyzer.class); private static final String DEFAULT_BASE_URL = "https://registry.npmjs.org"; - private static final String API_URL = "/-/package/%s/dist-tags"; + private static final String VERSION_URL = "/-/package/%s/dist-tags"; + private static final String PACKAGE_URL = "/%s/%s"; NpmMetaAnalyzer() { this.baseUrl = DEFAULT_BASE_URL; @@ -74,8 +77,9 @@ public MetaModel analyze(final Component component) { packageName = component.getPurl().getName(); } - final String url = String.format(baseUrl + API_URL, urlEncode(packageName)); - try (final CloseableHttpResponse response = processHttpRequest(url)) { + // Get the latest version + final String versionUrl = String.format(baseUrl + VERSION_URL, urlEncode(packageName)); + try (final CloseableHttpResponse response = processHttpRequest(versionUrl)) { if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { if (response.getEntity()!=null) { String responseString = EntityUtils.toString(response.getEntity()); @@ -86,13 +90,37 @@ public MetaModel analyze(final Component component) { } } } else { - handleUnexpectedHttpResponse(LOGGER, url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), component); + handleUnexpectedHttpResponse(LOGGER, versionUrl, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), component); } } catch (IOException e) { handleRequestException(LOGGER, e); } catch (Exception ex) { throw new MetaAnalyzerException(ex); } + + // Get deprecation information + if (meta.getLatestVersion() != null && !meta.getLatestVersion().isEmpty()) { + final String packageUrl = String.format(baseUrl + PACKAGE_URL, urlEncode(packageName), urlEncode(meta.getLatestVersion())); + try (final CloseableHttpResponse response = processHttpRequest(packageUrl)) { + if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + if (response.getEntity()!=null) { + String responseString = EntityUtils.toString(response.getEntity()); + var jsonObject = new JSONObject(responseString); + final String deprecated = jsonObject.optString("deprecated"); + if (deprecated != null && deprecated.length() > 0) { + meta.setDeprecated(true); + meta.setDeprecationMessage(deprecated); + } + } + } else { + handleUnexpectedHttpResponse(LOGGER, packageUrl, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), component); + } + } catch (IOException e) { + handleRequestException(LOGGER, e); + } catch (Exception ex) { + throw new MetaAnalyzerException(ex); + } + } } return meta; } diff --git a/src/main/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzer.java b/src/main/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzer.java index 06da55a9c8..a9b15279c6 100644 --- a/src/main/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzer.java +++ b/src/main/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzer.java @@ -18,8 +18,12 @@ */ package org.dependencytrack.tasks.repositories; -import alpine.common.logging.Logger; -import com.github.packageurl.PackageURL; +import java.io.IOException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + import org.apache.http.HttpStatus; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.util.EntityUtils; @@ -30,11 +34,9 @@ import org.json.JSONArray; import org.json.JSONObject; -import java.io.IOException; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; +import com.github.packageurl.PackageURL; + +import alpine.common.logging.Logger; /** * An IMetaAnalyzer implementation that supports Nuget. @@ -164,11 +166,32 @@ private boolean performLastPublishedCheck(final MetaModel meta, final Component if (response.getEntity() != null) { String stringResponse = EntityUtils.toString(response.getEntity()); if (!stringResponse.equalsIgnoreCase("") && !stringResponse.equalsIgnoreCase("{}")) { - JSONObject jsonResponse = new JSONObject(stringResponse); + final JSONObject jsonResponse = new JSONObject(stringResponse); final String updateTime = jsonResponse.optString("published", null); if (updateTime != null) { meta.setPublishedTimestamp(parseUpdateTime(updateTime)); } + + final String catalogEntry = jsonResponse.optString("catalogEntry", null); + + try (final CloseableHttpResponse catalogResponse = processHttpRequest(catalogEntry)) { + if (catalogResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + if (catalogResponse.getEntity() != null) { + final String responseString = EntityUtils.toString(catalogResponse.getEntity()); + final JSONObject responseJson = new JSONObject(responseString); + final JSONObject deprecationObject = responseJson.optJSONObject("deprecation"); + if (deprecationObject != null) { + meta.setDeprecated(deprecationObject.optJSONArray("reasons") != null); + meta.setDeprecationMessage(deprecationObject.optString("message")); + } + } + } + } catch (IOException e) { + handleRequestException(LOGGER, e); + } catch (Exception ex) { + throw new MetaAnalyzerException(ex); + } + return true; } } diff --git a/src/main/java/org/dependencytrack/tasks/repositories/RepositoryMetaAnalyzerTask.java b/src/main/java/org/dependencytrack/tasks/repositories/RepositoryMetaAnalyzerTask.java index dcb9548fcb..5a6372e60f 100644 --- a/src/main/java/org/dependencytrack/tasks/repositories/RepositoryMetaAnalyzerTask.java +++ b/src/main/java/org/dependencytrack/tasks/repositories/RepositoryMetaAnalyzerTask.java @@ -61,6 +61,8 @@ public class RepositoryMetaAnalyzerTask implements Subscriber { private static final Logger LOGGER = Logger.getLogger(RepositoryMetaAnalyzerTask.class); private static final String LATEST_VERSION = "latestVersion"; private static final String PUBLISHED_TIMESTAMP = "publishedTimestamp"; + private static final String IS_DEPRECATED = "isDeprecated"; + private static final String DEPRECATION_MESSAGE = "deprecationMessage"; private static final CacheStampedeBlocker cacheStampedeBlocker; private long cacheValidityPeriod; @@ -175,6 +177,8 @@ private void analyze(final QueryManager qm, final Component component, final IMe if (cac != null && isCacheCurrent(cac, component.getPurl().toString())) { LOGGER.debug("Building repository Metamodel from cache for " + purl); model.setLatestVersion(StringUtils.trimToNull(cac.getResult().getString(LATEST_VERSION))); + model.setDeprecated(cac.getResult().getInt(IS_DEPRECATED) == 1); + model.setDeprecationMessage(StringUtils.trimToNull(cac.getResult().getString(DEPRECATION_MESSAGE))); model.setPublishedTimestamp(Date.from(Instant.ofEpochMilli(cac.getResult().getJsonNumber(PUBLISHED_TIMESTAMP).longValue()))); } else { LOGGER.debug("Analyzing component: " + component.getUuid() + " using repository: " @@ -221,6 +225,8 @@ private void analyze(final QueryManager qm, final Component component, final IMe metaComponent.setName(component.getPurl().getName()); metaComponent.setPublished(model.getPublishedTimestamp()); metaComponent.setLatestVersion(model.getLatestVersion()); + metaComponent.setDeprecated(model.isDeprecated()); + metaComponent.setDeprecationMessage(model.getDeprecationMessage()); metaComponent.setLastCheck(new Date()); try { qm.synchronizeRepositoryMetaComponent(metaComponent); @@ -253,9 +259,13 @@ private void analyze(final QueryManager qm, final Component component, final IMe private JsonObject buildRepositoryComponentAnalysisCacheResult(MetaModel model) { JsonObjectBuilder builder = Json.createObjectBuilder(); String latestVersion = model.getLatestVersion() != null ? model.getLatestVersion() : ""; + Integer isDeprecated = model.isDeprecated() ? 1 : 0; + String deprecationMessage = model.getDeprecationMessage() != null ? model.getDeprecationMessage() : ""; Long published = model.getPublishedTimestamp() != null ? model.getPublishedTimestamp().getTime() : 0L; builder.add(LATEST_VERSION, Json.createValue(latestVersion)); builder.add(PUBLISHED_TIMESTAMP, Json.createValue(published)); + builder.add(IS_DEPRECATED, Json.createValue(isDeprecated)); + builder.add(DEPRECATION_MESSAGE, Json.createValue(deprecationMessage)); return builder.build(); } diff --git a/src/test/java/org/dependencytrack/model/RepositoryMetaComponentTest.java b/src/test/java/org/dependencytrack/model/RepositoryMetaComponentTest.java index 966ec668c7..40936b1666 100644 --- a/src/test/java/org/dependencytrack/model/RepositoryMetaComponentTest.java +++ b/src/test/java/org/dependencytrack/model/RepositoryMetaComponentTest.java @@ -18,11 +18,11 @@ */ package org.dependencytrack.model; +import java.util.Date; + import org.junit.Assert; import org.junit.Test; -import java.util.Date; - public class RepositoryMetaComponentTest { @Test @@ -60,6 +60,20 @@ public void testLatestVersion() { Assert.assertEquals("2.0.0", rmc.getLatestVersion()); } + @Test + public void testIsDeprecated() { + RepositoryMetaComponent rmc = new RepositoryMetaComponent(); + rmc.setDeprecated(true); + Assert.assertTrue(rmc.isDeprecated()); + } + + @Test + public void testDeprecationMessage() { + RepositoryMetaComponent rmc = new RepositoryMetaComponent(); + rmc.setDeprecationMessage("Deprecated component."); + Assert.assertEquals("Deprecated component.", rmc.getDeprecationMessage()); + } + @Test public void testPublished() { Date date = new Date(); diff --git a/src/test/java/org/dependencytrack/tasks/RepoMetaAnalysisTaskTest.java b/src/test/java/org/dependencytrack/tasks/RepoMetaAnalysisTaskTest.java index 02455e160b..0b5d9ffc70 100644 --- a/src/test/java/org/dependencytrack/tasks/RepoMetaAnalysisTaskTest.java +++ b/src/test/java/org/dependencytrack/tasks/RepoMetaAnalysisTaskTest.java @@ -90,6 +90,8 @@ public void informTestNullPassword() throws Exception { RepositoryMetaComponent metaComponent = qm.getRepositoryMetaComponent(RepositoryType.MAVEN, "junit", "junit"); qm.getPersistenceManager().refresh(metaComponent); assertThat(metaComponent.getLatestVersion()).isEqualTo("4.13.2"); + assertThat(metaComponent.isDeprecated()).isFalse(); + assertThat(metaComponent.getDeprecationMessage()).isNull(); } @Test @@ -136,6 +138,8 @@ public void informTestNullUserName() throws Exception { RepositoryMetaComponent metaComponent = qm.getRepositoryMetaComponent(RepositoryType.MAVEN, "test1", "test1"); qm.getPersistenceManager().refresh(metaComponent); assertThat(metaComponent.getLatestVersion()).isEqualTo("1.7.0"); + assertThat(metaComponent.isDeprecated()).isFalse(); + assertThat(metaComponent.getDeprecationMessage()).isNull(); } @Test @@ -182,6 +186,8 @@ public void informTestNullUserNameAndPassword() throws Exception { RepositoryMetaComponent metaComponent = qm.getRepositoryMetaComponent(RepositoryType.MAVEN, "test2", "test2"); qm.getPersistenceManager().refresh(metaComponent); assertThat(metaComponent.getLatestVersion()).isEqualTo("4.13.2"); + assertThat(metaComponent.isDeprecated()).isFalse(); + assertThat(metaComponent.getDeprecationMessage()).isNull(); } @Test @@ -228,5 +234,7 @@ public void informTestUserNameAndPassword() throws Exception { RepositoryMetaComponent metaComponent = qm.getRepositoryMetaComponent(RepositoryType.MAVEN, "test3", "test3"); qm.getPersistenceManager().refresh(metaComponent); assertThat(metaComponent.getLatestVersion()).isEqualTo("4.13.2"); + assertThat(metaComponent.isDeprecated()).isFalse(); + assertThat(metaComponent.getDeprecationMessage()).isNull(); } } diff --git a/src/test/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzerTest.java b/src/test/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzerTest.java index ddfe3a254d..9e25ddbee8 100644 --- a/src/test/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzerTest.java +++ b/src/test/java/org/dependencytrack/tasks/repositories/NpmMetaAnalyzerTest.java @@ -18,20 +18,20 @@ */ package org.dependencytrack.tasks.repositories; -import com.github.packageurl.PackageURL; -import com.github.tomakehurst.wiremock.junit.WireMockRule; +import static org.assertj.core.api.Assertions.assertThat; import org.dependencytrack.model.Component; import org.dependencytrack.model.RepositoryType; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; +import com.github.packageurl.PackageURL; import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; -import static org.assertj.core.api.Assertions.assertThat; +import com.github.tomakehurst.wiremock.junit.WireMockRule; public class NpmMetaAnalyzerTest { @@ -48,9 +48,39 @@ public void testAnalyzer() throws Exception { Assert.assertEquals(RepositoryType.NPM, analyzer.supportedRepositoryType()); MetaModel metaModel = analyzer.analyze(component); Assert.assertNotNull(metaModel.getLatestVersion()); + Assert.assertFalse(metaModel.isDeprecated()); + Assert.assertNull(metaModel.getDeprecationMessage()); //Assert.assertNotNull(metaModel.getPublishedTimestamp()); // todo: not yet supported } + @Test + public void testAnalyzerPackageDeprecated() throws Exception { + Component component = new Component(); + component.setPurl(new PackageURL("pkg:npm/har-validator@5.1.5")); + + NpmMetaAnalyzer analyzer = new NpmMetaAnalyzer(); + Assert.assertTrue(analyzer.isApplicable(component)); + Assert.assertEquals(RepositoryType.NPM, analyzer.supportedRepositoryType()); + MetaModel metaModel = analyzer.analyze(component); + Assert.assertNotNull(metaModel.getLatestVersion()); + Assert.assertTrue(metaModel.isDeprecated()); + Assert.assertNotNull(metaModel.getDeprecationMessage()); + } + + @Test + public void testAnalyzerPackageVersionDeprecatedButNewerVersionFound() throws Exception { + Component component = new Component(); + component.setPurl(new PackageURL("pkg:npm/uuid@0.0.1")); + + NpmMetaAnalyzer analyzer = new NpmMetaAnalyzer(); + Assert.assertTrue(analyzer.isApplicable(component)); + Assert.assertEquals(RepositoryType.NPM, analyzer.supportedRepositoryType()); + MetaModel metaModel = analyzer.analyze(component); + Assert.assertNotNull(metaModel.getLatestVersion()); + Assert.assertFalse(metaModel.isDeprecated()); + Assert.assertNull(metaModel.getDeprecationMessage()); + } + @Test public void testWithScopedPackage() { stubFor(get(urlPathEqualTo("/-/package/%40angular%2Fcli/dist-tags")) @@ -74,6 +104,18 @@ public void testWithScopedPackage() { } """))); + stubFor(get(urlPathEqualTo("/%40angular%2Fcli/17.1.2")) + .willReturn(aResponse() + .withStatus(200) + .withBody(""" + { + "name": "@angular/cli", + "version": "17.1.1", + "description": "CLI tool for Angular", + "main": "index.js", + } + """))); + final var component = new Component(); component.setPurl("pkg:npm/%40angular/cli@17.1.1"); @@ -96,6 +138,14 @@ public void testWithSpecialCharactersInPackageName() { "Not Found" """))); + stubFor(get(urlPathEqualTo("/jquery%20joyride%20plugin%20/2.1")) + .willReturn(aResponse() + .withStatus(404) + .withBody(""" + "Not Found" + """))); + + final var component = new Component(); component.setPurl("pkg:npm/jquery%20joyride%20plugin%20@2.1"); diff --git a/src/test/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzerTest.java b/src/test/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzerTest.java index 0cf52204f1..9a667dba66 100644 --- a/src/test/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzerTest.java +++ b/src/test/java/org/dependencytrack/tasks/repositories/NugetMetaAnalyzerTest.java @@ -18,28 +18,28 @@ */ package org.dependencytrack.tasks.repositories; -import com.github.packageurl.PackageURL; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + import org.apache.http.HttpHeaders; import org.dependencytrack.model.Component; import org.dependencytrack.model.RepositoryType; +import static org.dependencytrack.tasks.repositories.NugetMetaAnalyzer.SUPPORTED_DATE_FORMATS; import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import org.mockserver.client.MockServerClient; import org.mockserver.integration.ClientAndServer; - -import java.nio.file.Files; -import java.nio.file.Paths; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.Date; - -import static org.dependencytrack.tasks.repositories.NugetMetaAnalyzer.SUPPORTED_DATE_FORMATS; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; +import com.github.packageurl.PackageURL; + public class NugetMetaAnalyzerTest { private static ClientAndServer mockServer; @@ -66,6 +66,45 @@ public void testAnalyzer() throws Exception { Assert.assertTrue(analyzer.isApplicable(component)); Assert.assertEquals(RepositoryType.NUGET, analyzer.supportedRepositoryType()); Assert.assertNotNull(metaModel.getLatestVersion()); + Assert.assertFalse(metaModel.isDeprecated()); + Assert.assertNotNull(metaModel.getPublishedTimestamp()); + } + + + + @Test + public void testAnalyzerPackageDeprecated() throws Exception { + Component component = new Component(); + component.setPurl(new PackageURL("pkg:nuget/Microsoft.AspNetCore@2.2.0")); + NugetMetaAnalyzer analyzer = new NugetMetaAnalyzer(); + + analyzer.setRepositoryBaseUrl("https://api.nuget.org"); + MetaModel metaModel = analyzer.analyze(component); + + Assert.assertTrue(analyzer.isApplicable(component)); + Assert.assertEquals(RepositoryType.NUGET, analyzer.supportedRepositoryType()); + Assert.assertNotNull(metaModel.getLatestVersion()); + Assert.assertTrue(metaModel.isDeprecated()); + Assert.assertNotNull(metaModel.getDeprecationMessage()); + Assert.assertNotNull(metaModel.getPublishedTimestamp()); + } + + + + @Test + public void testAnalyzerPackageVersionDeprecatedButNewerVersionFound() throws Exception { + Component component = new Component(); + component.setPurl(new PackageURL("pkg:nuget/Microsoft.Extensions.Configuration@5.0.0")); + NugetMetaAnalyzer analyzer = new NugetMetaAnalyzer(); + + analyzer.setRepositoryBaseUrl("https://api.nuget.org"); + MetaModel metaModel = analyzer.analyze(component); + + Assert.assertTrue(analyzer.isApplicable(component)); + Assert.assertEquals(RepositoryType.NUGET, analyzer.supportedRepositoryType()); + Assert.assertNotEquals("5.0.0", metaModel.getLatestVersion()); + Assert.assertFalse(metaModel.isDeprecated()); + Assert.assertNull(metaModel.getDeprecationMessage()); Assert.assertNotNull(metaModel.getPublishedTimestamp()); }