Fixes issue 75

Changed getConnection to public and made one which makes using the proxy optional, even if configured

Added a preflight request and proxy logic


Former-commit-id: 19fdfcf4edacacfa3724c8969c7da74a593f9a7c
This commit is contained in:
Will Stranathan
2014-02-26 22:08:18 -05:00
parent 594aa03c5a
commit 9349e9cd99
7 changed files with 144 additions and 27 deletions

View File

@@ -93,6 +93,10 @@ public class NexusAnalyzer extends AbstractAnalyzer {
LOGGER.fine(String.format("Nexus Analyzer URL: %s", searchUrl)); LOGGER.fine(String.format("Nexus Analyzer URL: %s", searchUrl));
try { try {
searcher = new NexusSearch(new URL(searchUrl)); searcher = new NexusSearch(new URL(searchUrl));
if (! searcher.preflightRequest()) {
LOGGER.warning("There was an issue getting Nexus status. Disabling analyzer.");
enabled = false;
}
} catch (MalformedURLException mue) { } catch (MalformedURLException mue) {
// I know that initialize can throw an exception, but we'll // I know that initialize can throw an exception, but we'll
// just disable the analyzer if the URL isn't valid // just disable the analyzer if the URL isn't valid

View File

@@ -19,13 +19,19 @@ package org.owasp.dependencycheck.data.nexus;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath; import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathFactory;
import org.owasp.dependencycheck.utils.Downloader;
import org.owasp.dependencycheck.utils.InvalidSettingException;
import org.owasp.dependencycheck.utils.Settings;
import org.w3c.dom.Document; import org.w3c.dom.Document;
/** /**
@@ -40,40 +46,71 @@ public class NexusSearch {
*/ */
private final URL rootURL; private final URL rootURL;
/**
* Whether to use the Proxy when making requests
*/
private boolean useProxy;
/** /**
* Used for logging. * Used for logging.
*/ */
private static final Logger LOGGER = Logger.getLogger(NexusSearch.class.getName()); private static final Logger LOGGER = Logger.getLogger(NexusSearch.class
.getName());
/** /**
* Creates a NexusSearch for the given repository URL. * Creates a NexusSearch for the given repository URL.
* *
* @param rootURL the root URL of the repository on which searches should execute. full URL's are calculated * @param rootURL
* relative to this URL, so it should end with a / * the root URL of the repository on which searches should
* execute. full URL's are calculated relative to this URL, so it
* should end with a /
*/ */
public NexusSearch(URL rootURL) { public NexusSearch(URL rootURL) {
this.rootURL = rootURL; this.rootURL = rootURL;
try {
if (null != Settings.getString(Settings.KEYS.PROXY_URL)
&& Settings.getBoolean(Settings.KEYS.ANALYZER_NEXUS_PROXY)) {
useProxy = true;
LOGGER.fine("Using proxy");
} else {
useProxy = false;
LOGGER.fine("Not using proxy");
}
} catch (InvalidSettingException ise) {
useProxy = false;
}
} }
/** /**
* Searches the configured Nexus repository for the given sha1 hash. If the artifact is found, a * Searches the configured Nexus repository for the given sha1 hash. If the
* <code>MavenArtifact</code> is populated with the coordinate information. * artifact is found, a <code>MavenArtifact</code> is populated with the
* coordinate information.
* *
* @param sha1 The SHA-1 hash string for which to search * @param sha1
* The SHA-1 hash string for which to search
* @return the populated Maven coordinates * @return the populated Maven coordinates
* @throws IOException if it's unable to connect to the specified repositor or if the specified artifact is not * @throws IOException
* found. * if it's unable to connect to the specified repositor or if
* the specified artifact is not found.
*/ */
public MavenArtifact searchSha1(String sha1) throws IOException { public MavenArtifact searchSha1(String sha1) throws IOException {
if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) { if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) {
throw new IllegalArgumentException("Invalid SHA1 format"); throw new IllegalArgumentException("Invalid SHA1 format");
} }
final URL url = new URL(rootURL, String.format("identify/sha1/%s", sha1.toLowerCase())); final URL url = new URL(rootURL, String.format("identify/sha1/%s",
sha1.toLowerCase()));
LOGGER.fine(String.format("Searching Nexus url %s", url.toString())); LOGGER.fine(String.format("Searching Nexus url %s", url.toString()));
final URLConnection conn = url.openConnection(); // 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
URLConnection conn = null;
conn = Downloader.getConnection(url, useProxy);
conn.setDoOutput(true); conn.setDoOutput(true);
// JSON would be more elegant, but there's not currently a dependency // JSON would be more elegant, but there's not currently a dependency
@@ -82,23 +119,65 @@ public class NexusSearch {
conn.connect(); conn.connect();
try { try {
final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); final DocumentBuilder builder = DocumentBuilderFactory
.newInstance().newDocumentBuilder();
final Document doc = builder.parse(conn.getInputStream()); final Document doc = builder.parse(conn.getInputStream());
final XPath xpath = XPathFactory.newInstance().newXPath(); final XPath xpath = XPathFactory.newInstance().newXPath();
final String groupId = xpath.evaluate("/org.sonatype.nexus.rest.model.NexusArtifact/groupId", doc); final String groupId = xpath
final String artifactId = xpath.evaluate("/org.sonatype.nexus.rest.model.NexusArtifact/artifactId", doc); .evaluate(
final String version = xpath.evaluate("/org.sonatype.nexus.rest.model.NexusArtifact/version", doc); "/org.sonatype.nexus.rest.model.NexusArtifact/groupId",
final String link = xpath.evaluate("/org.sonatype.nexus.rest.model.NexusArtifact/artifactLink", doc); doc);
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);
return new MavenArtifact(groupId, artifactId, version, link); return new MavenArtifact(groupId, artifactId, version, link);
} catch (FileNotFoundException fnfe) { } catch (FileNotFoundException fnfe) {
// This is what we get when the SHA1 they sent doesn't exist in Nexus. This // This is what we get when the SHA1 they sent doesn't exist in
// Nexus. This
// is useful upstream for recovery, so we just re-throw it // is useful upstream for recovery, so we just re-throw it
throw fnfe; throw fnfe;
} catch (Exception e) { } catch (Exception e) {
// Anything else is jacked-up XML stuff that we really can't recover from well // Anything else is jacked-up XML stuff that we really can't recover
// from well
throw new IOException(e.getMessage(), e); throw new IOException(e.getMessage(), e);
} }
} }
/**
* Do a preflight request to see if the repository is actually working.
*
* @return whether the repository is listening and returns the /status URL
* correctly
*/
public boolean preflightRequest() {
try {
HttpURLConnection conn = Downloader.getConnection(new URL(rootURL, "status"));
conn.addRequestProperty("Accept", "application/xml");
conn.connect();
if (conn.getResponseCode() != 200) {
LOGGER.warning("Expected 200 result from Nexus, got " + conn.getResponseCode());
return false;
}
final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
final Document doc = builder.parse(conn.getInputStream());
if (doc.getDocumentElement().getNodeName() != "status") {
LOGGER.warning("Expected root node name of status, got " + doc.getDocumentElement().getNodeName());
return false;
}
} catch (Exception e) {
return false;
}
return true;
}
} }
// vim: cc=120:sw=4:ts=4:sts=4 // vim: cc=120:sw=4:ts=4:sts=4

View File

@@ -176,7 +176,7 @@ public final class Downloader {
* @return an HttpURLConnection * @return an HttpURLConnection
* @throws DownloadFailedException thrown if there is an exception * @throws DownloadFailedException thrown if there is an exception
*/ */
private static HttpURLConnection getConnection(URL url) throws DownloadFailedException { public static HttpURLConnection getConnection(URL url) throws DownloadFailedException {
HttpURLConnection conn = null; HttpURLConnection conn = null;
Proxy proxy = null; Proxy proxy = null;
final String proxyUrl = Settings.getString(Settings.KEYS.PROXY_URL); final String proxyUrl = Settings.getString(Settings.KEYS.PROXY_URL);
@@ -219,4 +219,28 @@ public final class Downloader {
} }
return conn; return conn;
} }
/**
* Utility method to get an HttpURLConnection. The use of a proxy here is optional as there
* may be cases where a proxy is configured but we don't want to use it (for example, if there's
* an internal repository configured)
*
* @param url the url to connect to
* @parem proxy whether to use the proxy (if configured)
* @throws DownloadFailedException thrown if there is an exception
*/
public static HttpURLConnection getConnection(URL url, boolean proxy) throws DownloadFailedException {
if (proxy) {
return getConnection(url);
}
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection)url.openConnection();
final int timeout = Settings.getInt(Settings.KEYS.CONNECTION_TIMEOUT, 60000);
conn.setConnectTimeout(timeout);
} catch (IOException ioe) {
throw new DownloadFailedException("Error getting connection.", ioe);
}
return conn;
}
} }

View File

@@ -145,6 +145,10 @@ public final class Settings {
* The properties key for the Nexus search URL. * The properties key for the Nexus search URL.
*/ */
public static final String ANALYZER_NEXUS_URL = "analyzer.nexus.url"; public static final String ANALYZER_NEXUS_URL = "analyzer.nexus.url";
/**
* The properties key for using the proxy to reach Nexus
*/
public static final String ANALYZER_NEXUS_PROXY = "analyzer.nexus.proxy";
/** /**
* The path to mono, if available. * The path to mono, if available.
*/ */

View File

@@ -45,4 +45,6 @@ cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml
# the URL for searching Nexus for SHA-1 hashes and whether it's enabled # the URL for searching Nexus for SHA-1 hashes and whether it's enabled
analyzer.nexus.enabled=true analyzer.nexus.enabled=true
analyzer.nexus.url=http://repository.sonatype.org/service/local/ analyzer.nexus.url=http://repository.sonatype.org/service/local/
# If set to true, the proxy will still ONLY be used if the proxy properties (proxy.url, proxy.port)
# are configured
analyzer.nexus.proxy=true

View File

@@ -17,13 +17,15 @@
*/ */
package org.owasp.dependencycheck.data.nexus; package org.owasp.dependencycheck.data.nexus;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.net.URL; import java.net.URL;
import java.util.logging.Logger; import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.owasp.dependencycheck.utils.Settings; import org.owasp.dependencycheck.utils.Settings;
@@ -37,6 +39,7 @@ public class NexusSearchTest {
String nexusUrl = Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL); String nexusUrl = Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL);
LOGGER.fine(nexusUrl); LOGGER.fine(nexusUrl);
searcher = new NexusSearch(new URL(nexusUrl)); searcher = new NexusSearch(new URL(nexusUrl));
Assume.assumeTrue(searcher.preflightRequest());
} }
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
@@ -52,7 +55,6 @@ public class NexusSearchTest {
// This test does generate network traffic and communicates with a host // This test does generate network traffic and communicates with a host
// you may not be able to reach. Remove the @Ignore annotation if you want to // you may not be able to reach. Remove the @Ignore annotation if you want to
// test it anyway // test it anyway
@Ignore
@Test @Test
public void testValidSha1() throws Exception { public void testValidSha1() throws Exception {
MavenArtifact ma = searcher.searchSha1("9977a8d04e75609cf01badc4eb6a9c7198c4c5ea"); MavenArtifact ma = searcher.searchSha1("9977a8d04e75609cf01badc4eb6a9c7198c4c5ea");
@@ -65,7 +67,6 @@ public class NexusSearchTest {
// This test does generate network traffic and communicates with a host // This test does generate network traffic and communicates with a host
// you may not be able to reach. Remove the @Ignore annotation if you want to // you may not be able to reach. Remove the @Ignore annotation if you want to
// test it anyway // test it anyway
@Ignore
@Test(expected = FileNotFoundException.class) @Test(expected = FileNotFoundException.class)
public void testMissingSha1() throws Exception { public void testMissingSha1() throws Exception {
searcher.searchSha1("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); searcher.searchSha1("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");

View File

@@ -50,3 +50,6 @@ cve.url-1.2.base=http://nvd.nist.gov/download/nvdcve-%d.xml
# the URL for searching Nexus for SHA-1 hashes and whether it's enabled # the URL for searching Nexus for SHA-1 hashes and whether it's enabled
analyzer.nexus.enabled=true analyzer.nexus.enabled=true
analyzer.nexus.url=http://repository.sonatype.org/service/local/ analyzer.nexus.url=http://repository.sonatype.org/service/local/
# If set to true, the proxy will still ONLY be used if the proxy properties (proxy.url, proxy.port)
# are configured
analyzer.nexus.proxy=true