Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backport: Compose Metadata Analyzer: Use v2 URL #4485

Draft
wants to merge 1 commit into
base: 4.12.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
package org.dependencytrack.tasks.repositories;

import alpine.common.logging.Logger;

import com.github.packageurl.PackageURL;
import org.dependencytrack.exception.MetaAnalyzerException;
import org.json.JSONArray;
import org.json.JSONObject;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
Expand All @@ -46,9 +48,10 @@ public class ComposerMetaAnalyzer extends AbstractMetaAnalyzer {
private static final String DEFAULT_BASE_URL = "https://repo.packagist.org";

/**
* @see <a href="https://packagist.org/apidoc#get-package-metadata-v1">Packagist's API doc for "Getting package data - Using the Composer v1 metadata (DEPRECATED)"</a>
* @see <a href="https://packagist.org/apidoc#get-package-data">Packagist's API doc for "Getting package data - Using the Composer v2 metadata"</a>
* Example: https://repo.packagist.org/p2/monolog/monolog.json
*/
private static final String API_URL = "/p/%s/%s.json";
private static final String PACKAGE_META_DATA_URL = "/p2/%s/%s.json";

ComposerMetaAnalyzer() {
this.baseUrl = DEFAULT_BASE_URL;
Expand Down Expand Up @@ -77,7 +80,7 @@ public MetaModel analyze(final Component component) {
return meta;
}

final String url = String.format(baseUrl + API_URL, urlEncode(component.getPurl().getNamespace()), urlEncode(component.getPurl().getName()));
final String url = String.format(baseUrl + PACKAGE_META_DATA_URL, urlEncode(component.getPurl().getNamespace()), urlEncode(component.getPurl().getName()));
try (final CloseableHttpResponse response = processHttpRequest(url)) {
if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
handleUnexpectedHttpResponse(LOGGER, url, response.getStatusLine().getStatusCode(), response.getStatusLine().getReasonPhrase(), component);
Expand All @@ -98,33 +101,34 @@ public MetaModel analyze(final Component component) {
final JSONObject responsePackages = jsonObject
.getJSONObject("packages");
if (!responsePackages.has(expectedResponsePackage)) {
// the package no longer exists - like this one: https://repo.packagist.org/p/magento/adobe-ims.json
// the package no longer exists - for v2 there's no example (yet), v1 example https://repo.packagist.org/p/magento/adobe-ims.json
return meta;
}
final JSONObject composerPackage = responsePackages.getJSONObject(expectedResponsePackage);
final JSONArray composerPackageVersions = responsePackages.getJSONArray(expectedResponsePackage);

final ComparableVersion latestVersion = new ComparableVersion(stripLeadingV(component.getPurl().getVersion()));
final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX");

composerPackage.names().forEach(key_ -> {
String key = (String) key_;
if (key.startsWith("dev-") || key.endsWith("-dev")) {
composerPackageVersions.forEach(item -> {
JSONObject composerPackage = (JSONObject) item;
String version = composerPackage.getString("version");
if (version.startsWith("dev-") || version.endsWith("-dev")) {
// dev versions are excluded, since they are not pinned but a VCS-branch.
// this case doesn't seem to happen anymore with V2, as dev (untagged) releases are not part of the response anymore
return;
}

final String version_normalized = composerPackage.getJSONObject(key).getString("version_normalized");
final String version_normalized = composerPackage.getString("version_normalized");
ComparableVersion currentComparableVersion = new ComparableVersion(version_normalized);
if (currentComparableVersion.compareTo(latestVersion) < 0) {
// smaller version can be skipped
return;
}

final String version = composerPackage.getJSONObject(key).getString("version");
latestVersion.parseVersion(stripLeadingV(version_normalized));
meta.setLatestVersion(version);

final String published = composerPackage.getJSONObject(key).getString("time");
final String published = composerPackage.getString("time");
try {
meta.setPublishedTimestamp(dateFormat.parse(published));
} catch (ParseException e) {
Expand All @@ -141,7 +145,7 @@ public MetaModel analyze(final Component component) {
}

private static String stripLeadingV(String s) {
return s.startsWith("v")
return s.startsWith("v") || s.startsWith("V")
? s.substring(1)
: s;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public void testAnalyzerFindsVersionWithLeadingV() throws Exception {
.when(
request()
.withMethod("GET")
.withPath("/p/typo3/class-alias-loader.json")
.withPath("/p2/typo3/class-alias-loader.json")
)
.respond(
response()
Expand All @@ -89,13 +89,21 @@ public void testAnalyzerFindsVersionWithLeadingV() throws Exception {

MetaModel metaModel = analyzer.analyze(component);

Assert.assertEquals("v1.1.3", metaModel.getLatestVersion());
Assert.assertEquals("v1.2.0", metaModel.getLatestVersion());
Assert.assertEquals(
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss XXX").parse("2020-05-24 13:03:22 Z"),
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss XXX").parse("2024-10-11 08:11:39 Z"),
metaModel.getPublishedTimestamp()
);
}

/*
* This case no longer happens in the composer v2 repositories. It now returns a 404 for all examples from #2134
* - adobe-ims.json
* - adobe-stock-integration.json
* - composter-root-update-plugin.json
* - module-aws-s3.json
* Leaving it here in case we find a different package triggering this behaviour.

@Test
public void testAnalyzerGetsUnexpectedResponseContentCausingLatestVersionBeingNull() throws Exception {
Component component = new Component();
Expand All @@ -109,7 +117,7 @@ public void testAnalyzerGetsUnexpectedResponseContentCausingLatestVersionBeingNu
.when(
request()
.withMethod("GET")
.withPath("/p/magento/adobe-ims.json")
.withPath("/p2/magento/adobe-ims.json")
)
.respond(
response()
Expand All @@ -123,12 +131,40 @@ public void testAnalyzerGetsUnexpectedResponseContentCausingLatestVersionBeingNu

Assert.assertNull(metaModel.getLatestVersion());
}
*/

@Test
public void testAnalyzerGetsUnexpectedResponseContent404() throws Exception {
Component component = new Component();
ComposerMetaAnalyzer analyzer = new ComposerMetaAnalyzer();

component.setPurl(new PackageURL("pkg:composer/magento/[email protected]"));

analyzer.setRepositoryBaseUrl(String.format("http://localhost:%d", mockServer.getPort()));
new MockServerClient("localhost", mockServer.getPort())
.when(
request()
.withMethod("GET")
.withPath("/p2/magento/adobe-ims.json")
)
.respond(
response()
.withStatusCode(404)
.withHeader(HttpHeaders.CONTENT_TYPE, "application/json")
);

analyzer.analyze(component);
MetaModel metaModel = analyzer.analyze(component);

Assert.assertNull(metaModel.getLatestVersion());
}


private static File getResourceFile(String namespace, String name) throws Exception{
return new File(
Thread.currentThread().getContextClassLoader()
.getResource(String.format(
"unit/tasks/repositories/https---repo.packagist.org-p-%s-%s.json",
"unit/tasks/repositories/https---repo.packagist.org-p2-%s-%s.json",
namespace,
name
))
Expand Down

This file was deleted.

This file was deleted.

Loading
Loading