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

fix: Make the HTTP-Client use pre-emptive authentication #7255

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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 @@ -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
Loading