Skip to content

Commit

Permalink
fix: Make the HTTP-Client use pre-emptive authentication for configur…
Browse files Browse the repository at this point in the history
…ed server credentials and extend HTTPClient usage to Nexus search

Fixes #7108
Fixes #7253
  • Loading branch information
aikebah committed Dec 15, 2024
1 parent a7f4f54 commit f303c34
Show file tree
Hide file tree
Showing 12 changed files with 475 additions and 335 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -321,8 +321,7 @@ public void analyzeDependency(Dependency dependency, Engine engine) throws Analy
*/
public boolean useProxy() {
try {
return getSettings().getString(Settings.KEYS.PROXY_SERVER) != null
&& getSettings().getBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY);
return getSettings().getBoolean(Settings.KEYS.ANALYZER_NEXUS_USES_PROXY);
} catch (InvalidSettingException ise) {
LOGGER.warn("Failed to parse proxy settings.", ise);
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,19 @@
package org.owasp.dependencycheck.data.central;

import org.apache.hc.client5.http.impl.classic.AbstractHttpClientResponseHandler;
import org.apache.hc.core5.http.HttpEntity;
import org.apache.hc.core5.http.message.BasicHeader;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.ResourceNotFoundException;
import org.owasp.dependencycheck.utils.TooManyRequestsException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
Expand All @@ -44,12 +40,11 @@
import org.owasp.dependencycheck.data.cache.DataCacheFactory;
import org.owasp.dependencycheck.data.nexus.MavenArtifact;
import org.owasp.dependencycheck.utils.Settings;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.owasp.dependencycheck.utils.ToXMLDocumentResponseHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
* Class of methods to search Maven Central via Central.
Expand Down Expand Up @@ -162,19 +157,7 @@ public List<MavenArtifact> searchSha1(String sha1) throws IOException, TooManyRe
// JSON would be more elegant, but there's not currently a dependency
// on JSON, so don't want to add one just for this
final BasicHeader acceptHeader = new BasicHeader("Accept", "application/xml");
final AbstractHttpClientResponseHandler<Document> handler = new AbstractHttpClientResponseHandler<>() {
@Override
public Document handleEntity(HttpEntity entity) throws IOException {
try (InputStream in = entity.getContent()) {
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
return builder.parse(in);
} catch (ParserConfigurationException | SAXException | IOException e) {
// Anything else is jacked up XML stuff that we really can't recover from well
final String errorMessage = "Failed to parse MavenCentral XML Response: " + e.getMessage();
throw new IOException(errorMessage, e);
}
}
};
final AbstractHttpClientResponseHandler<Document> handler = new ToXMLDocumentResponseHandler();
try {
final Document doc = Downloader.getInstance().fetchAndHandle(url, handler, List.of(acceptHeader), useProxy);
final boolean missing = addMavenArtifacts(doc, result);
Expand All @@ -194,6 +177,9 @@ public Document handleEntity(HttpEntity entity) throws IOException {
} catch (ResourceNotFoundException | DownloadFailedException e) {
final String errorMessage = "Could not connect to MavenCentral " + e.getMessage();
throw new IOException(errorMessage, e);
} catch (URISyntaxException e) {
final String errorMessage = "Could not convert central search URL to a URI " + e.getMessage();
throw new IOException(errorMessage, e);
}
if (cache != null) {
cache.put(sha1, result);
Expand Down Expand Up @@ -267,4 +253,5 @@ private boolean isInvalidURL(String url) {
}
return false;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,29 @@

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import javax.annotation.concurrent.ThreadSafe;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.hc.client5.http.HttpResponseException;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpHeaders;
import org.apache.hc.core5.http.message.BasicHeader;
import org.owasp.dependencycheck.utils.DownloadFailedException;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.ResourceNotFoundException;
import org.owasp.dependencycheck.utils.Settings;

import org.owasp.dependencycheck.utils.URLConnectionFactory;
import org.owasp.dependencycheck.utils.XmlUtils;
import org.owasp.dependencycheck.utils.ToXMLDocumentResponseHandler;
import org.owasp.dependencycheck.utils.TooManyRequestsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

/**
* Class of methods to search Nexus repositories.
Expand Down Expand Up @@ -94,118 +98,76 @@ public MavenArtifact searchSha1(String sha1) throws IOException {

LOGGER.debug("Searching Nexus url {}", url);

// Determine if we need to use a proxy. The rules:
// 1) If the proxy is set, AND the setting is set to true, use the proxy
// 2) Otherwise, don't use the proxy (either the proxy isn't configured,
// or proxy is specifically set to false
final HttpURLConnection conn;
final URLConnectionFactory factory = new URLConnectionFactory(settings);
conn = factory.createHttpURLConnection(url, useProxy);
conn.setDoOutput(true);
final String authHeader = buildHttpAuthHeaderValue();
if (!authHeader.isEmpty()) {
conn.addRequestProperty("Authorization", authHeader);
}

// JSON would be more elegant, but there's not currently a dependency
// on JSON, so don't want to add one just for this
conn.addRequestProperty("Accept", "application/xml");
conn.connect();

switch (conn.getResponseCode()) {
case 200:
try {
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();
final Document doc = builder.parse(conn.getInputStream());
final XPath xpath = XPathFactory.newInstance().newXPath();
final String groupId = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/groupId",
doc);
final String artifactId = xpath.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactId",
try {
// JSON would be more elegant, but there's not currently a dependency
// on JSON, so don't want to add one just for this
final ToXMLDocumentResponseHandler handler = new ToXMLDocumentResponseHandler();
final Document doc = Downloader.getInstance().fetchAndHandle(url, handler, List.of(new BasicHeader(HttpHeaders.ACCEPT,
ContentType.APPLICATION_XML)));
final XPath xpath = XPathFactory.newInstance().newXPath();
final String groupId = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/groupId",
doc);
final String version = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/version",
doc);
final String link = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactLink",
doc);
final String pomLink = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/pomLink",
doc);
final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
if (link != null && !link.isEmpty()) {
ma.setArtifactUrl(link);
}
if (pomLink != null && !pomLink.isEmpty()) {
ma.setPomUrl(pomLink);
final String artifactId = xpath.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactId",
doc);
final String version = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/version",
doc);
final String link = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/artifactLink",
doc);
final String pomLink = xpath
.evaluate(
"/org.sonatype.nexus.rest.model.NexusArtifact/pomLink",
doc);
final MavenArtifact ma = new MavenArtifact(groupId, artifactId, version);
if (link != null && !link.isEmpty()) {
ma.setArtifactUrl(link);
}
if (pomLink != null && !pomLink.isEmpty()) {
ma.setPomUrl(pomLink);
}
return ma;
} catch (DownloadFailedException | TooManyRequestsException e) {
if (LOGGER.isDebugEnabled()) {
int responseCode = -1;
String responseMessage = "";
if (e.getCause() instanceof HttpResponseException) {
final HttpResponseException cause = (HttpResponseException) e.getCause();
responseCode = cause.getStatusCode();
responseMessage = cause.getReasonPhrase();
}
return ma;
} catch (ParserConfigurationException | IOException | SAXException | XPathExpressionException e) {
// Anything else is jacked-up XML stuff that we really can't recover
// from well
throw new IOException(e.getMessage(), e);
LOGGER.debug("Could not connect to Nexus received response code: {} {}",
responseCode, responseMessage);
}
case 404:
throw new FileNotFoundException("Artifact not found in Nexus");
default:
LOGGER.debug("Could not connect to Nexus received response code: {} {}",
conn.getResponseCode(), conn.getResponseMessage());
throw new IOException("Could not connect to Nexus");
} catch (ResourceNotFoundException e) {
throw new FileNotFoundException("Artifact not found in Nexus");
} catch (XPathExpressionException | URISyntaxException e) {
throw new IOException(e.getMessage(), e);
}
}

@Override
public boolean preflightRequest() {
final HttpURLConnection conn;
try {
final URL url = new URL(rootURL, "status");
final URLConnectionFactory factory = new URLConnectionFactory(settings);
conn = factory.createHttpURLConnection(url, useProxy);
conn.addRequestProperty("Accept", "application/xml");
final String authHeader = buildHttpAuthHeaderValue();
if (!authHeader.isEmpty()) {
conn.addRequestProperty("Authorization", authHeader);
}
conn.connect();
if (conn.getResponseCode() != 200) {
LOGGER.warn("Expected 200 result from Nexus, got {}", conn.getResponseCode());
return false;
}
final DocumentBuilder builder = XmlUtils.buildSecureDocumentBuilder();

final Document doc = builder.parse(conn.getInputStream());
final ToXMLDocumentResponseHandler handler = new ToXMLDocumentResponseHandler();
final Document doc = Downloader.getInstance().fetchAndHandle(url, handler, List.of(new BasicHeader(HttpHeaders.ACCEPT,
ContentType.APPLICATION_XML)));
if (!"status".equals(doc.getDocumentElement().getNodeName())) {
LOGGER.warn("Expected root node name of status, got {}", doc.getDocumentElement().getNodeName());
LOGGER.warn("Pre-flight request to Nexus failed; expected root node name of status, got {}", doc.getDocumentElement().getNodeName());
return false;
}
} catch (IOException | ParserConfigurationException | SAXException e) {
} catch (IOException | TooManyRequestsException | ResourceNotFoundException | URISyntaxException e) {
LOGGER.warn("Pre-flight request to Nexus failed: ", e);
return false;
}
return true;
}

/**
* Constructs the base64 encoded basic authentication header value.
*
* @return the base64 encoded basic authentication header value
*/
private String buildHttpAuthHeaderValue() {
final String user = settings.getString(Settings.KEYS.ANALYZER_NEXUS_USER, "");
final String pass = settings.getString(Settings.KEYS.ANALYZER_NEXUS_PASSWORD, "");
String result = "";
if (user.isEmpty() || pass.isEmpty()) {
LOGGER.debug("Skip authentication as user and/or password for nexus is empty");
} else {
final String auth = user + ':' + pass;
final String base64Auth = Base64.getEncoder().encodeToString(auth.getBytes(StandardCharsets.UTF_8));
result = "Basic " + base64Auth;
}
return result;
}
}
Loading

0 comments on commit f303c34

Please sign in to comment.