diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java index 8990af8f7..4d15c708a 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/analyzer/NexusAnalyzer.java @@ -93,6 +93,10 @@ public class NexusAnalyzer extends AbstractAnalyzer { LOGGER.fine(String.format("Nexus Analyzer URL: %s", searchUrl)); try { 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) { // I know that initialize can throw an exception, but we'll // just disable the analyzer if the URL isn't valid diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java index 5e8e67139..3580d0dc2 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/data/nexus/NexusSearch.java @@ -19,18 +19,24 @@ package org.owasp.dependencycheck.data.nexus; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.logging.Logger; + import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; 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; /** * Class of methods to search Nexus repositories. - * + * * @author colezlaw */ public class NexusSearch { @@ -40,40 +46,71 @@ public class NexusSearch { */ private final URL rootURL; + /** + * Whether to use the Proxy when making requests + */ + private boolean useProxy; + /** * 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. - * - * @param rootURL 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 / + * + * @param rootURL + * 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) { 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 - * MavenArtifact is populated with the coordinate information. - * - * @param sha1 The SHA-1 hash string for which to search + * Searches the configured Nexus repository for the given sha1 hash. If the + * artifact is found, a MavenArtifact is populated with the + * coordinate information. + * + * @param sha1 + * The SHA-1 hash string for which to search * @return the populated Maven coordinates - * @throws IOException if it's unable to connect to the specified repositor or if the specified artifact is not - * found. + * @throws IOException + * 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 { if (null == sha1 || !sha1.matches("^[0-9A-Fa-f]{40}$")) { 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())); - 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); // JSON would be more elegant, but there's not currently a dependency @@ -82,23 +119,65 @@ public class NexusSearch { conn.connect(); try { - final DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + final DocumentBuilder builder = DocumentBuilderFactory + .newInstance().newDocumentBuilder(); 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", 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 groupId = xpath + .evaluate( + "/org.sonatype.nexus.rest.model.NexusArtifact/groupId", + 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); } 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 throw fnfe; } 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); } } + + /** + * 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 diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Downloader.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Downloader.java index e8cbc0466..c7816b124 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Downloader.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Downloader.java @@ -176,7 +176,7 @@ public final class Downloader { * @return an HttpURLConnection * @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; Proxy proxy = null; final String proxyUrl = Settings.getString(Settings.KEYS.PROXY_URL); @@ -219,4 +219,28 @@ public final class Downloader { } 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; + } } diff --git a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Settings.java b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Settings.java index 0a54a5ed9..01d876fb5 100644 --- a/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Settings.java +++ b/dependency-check-core/src/main/java/org/owasp/dependencycheck/utils/Settings.java @@ -145,6 +145,10 @@ public final class Settings { * The properties key for the Nexus search 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. */ diff --git a/dependency-check-core/src/main/resources/dependencycheck.properties b/dependency-check-core/src/main/resources/dependencycheck.properties index b4a09d5d1..837c7c91f 100644 --- a/dependency-check-core/src/main/resources/dependencycheck.properties +++ b/dependency-check-core/src/main/resources/dependencycheck.properties @@ -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 analyzer.nexus.enabled=true 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 diff --git a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java index c83d06701..9904cc847 100644 --- a/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java +++ b/dependency-check-core/src/test/java/org/owasp/dependencycheck/data/nexus/NexusSearchTest.java @@ -17,13 +17,15 @@ */ package org.owasp.dependencycheck.data.nexus; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + import java.io.FileNotFoundException; import java.net.URL; 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.Ignore; import org.junit.Test; import org.owasp.dependencycheck.utils.Settings; @@ -37,6 +39,7 @@ public class NexusSearchTest { String nexusUrl = Settings.getString(Settings.KEYS.ANALYZER_NEXUS_URL); LOGGER.fine(nexusUrl); searcher = new NexusSearch(new URL(nexusUrl)); + Assume.assumeTrue(searcher.preflightRequest()); } @Test(expected = IllegalArgumentException.class) @@ -52,7 +55,6 @@ public class NexusSearchTest { // 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 // test it anyway - @Ignore @Test public void testValidSha1() throws Exception { MavenArtifact ma = searcher.searchSha1("9977a8d04e75609cf01badc4eb6a9c7198c4c5ea"); @@ -65,7 +67,6 @@ public class NexusSearchTest { // 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 // test it anyway - @Ignore @Test(expected = FileNotFoundException.class) public void testMissingSha1() throws Exception { searcher.searchSha1("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"); diff --git a/dependency-check-core/src/test/resources/dependencycheck.properties b/dependency-check-core/src/test/resources/dependencycheck.properties index f7a395ed4..6db3dc947 100644 --- a/dependency-check-core/src/test/resources/dependencycheck.properties +++ b/dependency-check-core/src/test/resources/dependencycheck.properties @@ -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 analyzer.nexus.enabled=true 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