1919
2020import java .io .FileNotFoundException ;
2121import java .io .IOException ;
22- import java .io .InputStreamReader ;
23- import java .net .HttpURLConnection ;
2422import java .net .MalformedURLException ;
23+ import java .net .URISyntaxException ;
2524import java .net .URL ;
2625import java .nio .charset .StandardCharsets ;
27- import java .util .ArrayList ;
28- import java .util .Base64 ;
2926import java .util .List ;
3027import java .util .UUID ;
31- import java .util .regex .Matcher ;
32- import java .util .regex .Pattern ;
3328
3429import javax .annotation .concurrent .ThreadSafe ;
3530
31+ import org .apache .hc .core5 .http .message .BasicHeader ;
3632import org .owasp .dependencycheck .data .nexus .MavenArtifact ;
3733import org .owasp .dependencycheck .dependency .Dependency ;
3834import org .owasp .dependencycheck .utils .Checksum ;
39- import org .owasp .dependencycheck .utils .InvalidSettingException ;
35+ import org .owasp .dependencycheck .utils .Downloader ;
36+ import org .owasp .dependencycheck .utils .ResourceNotFoundException ;
4037import org .owasp .dependencycheck .utils .Settings ;
41- import org .owasp .dependencycheck .utils .URLConnectionFactory ;
38+ import org .owasp .dependencycheck .utils .TooManyRequestsException ;
4239import org .slf4j .Logger ;
4340import org .slf4j .LoggerFactory ;
4441
45- import com .fasterxml .jackson .core .JsonParser ;
46- import com .fasterxml .jackson .databind .DeserializationFeature ;
47- import com .fasterxml .jackson .databind .ObjectMapper ;
48- import com .fasterxml .jackson .databind .ObjectReader ;
4942
5043/**
5144 * Class of methods to search Artifactory for hashes and determine Maven GAV
5649 * @author nhenneaux
5750 */
5851@ ThreadSafe
59- @ SuppressWarnings ("squid:S2647" )
6052public class ArtifactorySearch {
6153
6254 /**
6355 * Used for logging.
6456 */
6557 private static final Logger LOGGER = LoggerFactory .getLogger (ArtifactorySearch .class );
6658
67- /**
68- * Pattern to match the path returned by the Artifactory AQL API.
69- */
70- private static final Pattern PATH_PATTERN = Pattern .compile ("^/(?<groupId>.+)/(?<artifactId>[^/]+)/(?<version>[^/]+)/[^/]+$" );
71- /**
72- * Extracted duplicateArtifactorySearchIT.java comment.
73- */
74- private static final String WHILE_ACTUAL_IS = " while actual is " ;
7559 /**
7660 * The URL for the Central service.
7761 */
@@ -80,46 +64,29 @@ public class ArtifactorySearch {
8064 /**
8165 * Whether to use the Proxy when making requests.
8266 */
83- private final boolean useProxy ;
84-
85- /**
86- * The configured settings.
87- */
88- private final Settings settings ;
67+ private final boolean allowUsingProxy ;
8968
90- /**
91- * Search result reader
92- */
93- private final ObjectReader objectReader ;
9469
9570 /**
96- * Creates a NexusSearch for the given repository URL.
71+ * Creates a ArtifactorySearch for the given repository URL.
9772 *
9873 * @param settings the configured settings
9974 */
10075 public ArtifactorySearch (Settings settings ) {
101- this .settings = settings ;
10276
10377 final String searchUrl = settings .getString (Settings .KEYS .ANALYZER_ARTIFACTORY_URL );
10478
10579 this .rootURL = searchUrl ;
10680 LOGGER .debug ("Artifactory Search URL {}" , searchUrl );
10781
10882 if (null != settings .getString (Settings .KEYS .PROXY_SERVER )) {
109- boolean useProxySettings = false ;
110- try {
111- useProxySettings = settings .getBoolean (Settings .KEYS .ANALYZER_ARTIFACTORY_USES_PROXY );
112- } catch (InvalidSettingException e ) {
113- LOGGER .error ("Settings {} is invalid, only, true/false is valid" , Settings .KEYS .ANALYZER_ARTIFACTORY_USES_PROXY , e );
114- }
115- this .useProxy = useProxySettings ;
116- LOGGER .debug ("Using proxy? {}" , useProxy );
83+ this .allowUsingProxy = settings .getBoolean (Settings .KEYS .ANALYZER_ARTIFACTORY_USES_PROXY , false );
84+ LOGGER .debug ("Using proxy configuration? {}" , allowUsingProxy );
11785 } else {
118- useProxy = false ;
119- LOGGER .debug ("Not using proxy" );
86+ this . allowUsingProxy = settings . getBoolean ( Settings . KEYS . ANALYZER_ARTIFACTORY_USES_PROXY , true ) ;
87+ LOGGER .debug ("Using default non-legacy proxy configuration " );
12088 }
12189
122- objectReader = new ObjectMapper ().configure (DeserializationFeature .FAIL_ON_UNKNOWN_PROPERTIES , false ).readerFor (FileImpl .class );
12390 }
12491
12592 /**
@@ -137,50 +104,19 @@ public List<MavenArtifact> search(Dependency dependency) throws IOException {
137104
138105 final String sha1sum = dependency .getSha1sum ();
139106 final URL url = buildUrl (sha1sum );
140- final HttpURLConnection conn = connect (url );
141- final int responseCode = conn .getResponseCode ();
142- if (responseCode == 200 ) {
143- return processResponse (dependency , conn );
144- }
145- throw new IOException ("Could not connect to Artifactory " + url + " (" + responseCode + "): " + conn .getResponseMessage ());
146-
147- }
148-
149- /**
150- * Makes an connection to the given URL.
151- *
152- * @param url the URL to connect to
153- * @return the HTTP URL Connection
154- * @throws IOException thrown if there is an error making the connection
155- */
156- private HttpURLConnection connect (URL url ) throws IOException {
157- LOGGER .debug ("Searching Artifactory url {}" , url );
158-
159- // Determine if we need to use a proxy. The rules:
160- // 1) If the proxy is set, AND the setting is set to true, use the proxy
161- // 2) Otherwise, don't use the proxy (either the proxy isn't configured,
162- // or proxy is specifically set to false)
163- final URLConnectionFactory factory = new URLConnectionFactory (settings );
164- final HttpURLConnection conn = factory .createHttpURLConnection (url , useProxy );
165- conn .setDoOutput (true );
166-
167- conn .addRequestProperty ("X-Result-Detail" , "info" );
168-
169- final String username = settings .getString (Settings .KEYS .ANALYZER_ARTIFACTORY_API_USERNAME );
170- final String apiToken = settings .getString (Settings .KEYS .ANALYZER_ARTIFACTORY_API_TOKEN );
171- if (username != null && apiToken != null ) {
172- final String userpassword = username + ":" + apiToken ;
173- final String encodedAuthorization = Base64 .getEncoder ().encodeToString (userpassword .getBytes (StandardCharsets .UTF_8 ));
174- conn .addRequestProperty ("Authorization" , "Basic " + encodedAuthorization );
175- } else {
176- final String bearerToken = settings .getString (Settings .KEYS .ANALYZER_ARTIFACTORY_BEARER_TOKEN );
177- if (bearerToken != null ) {
178- conn .addRequestProperty ("Authorization" , "Bearer " + bearerToken );
179- }
107+ final StringBuilder msg = new StringBuilder ("Could not connect to Artifactory at" )
108+ .append (url );
109+ try {
110+ final BasicHeader artifactoryResultDetail = new BasicHeader ("X-Result-Detail" , "info" );
111+ return Downloader .getInstance ().fetchAndHandle (url , new ArtifactorySearchResponseHandler (dependency ), List .of (artifactoryResultDetail ),
112+ allowUsingProxy );
113+ } catch (TooManyRequestsException e ) {
114+ throw new IOException (msg .append (" (429): Too manu requests" ).toString (), e );
115+ } catch (URISyntaxException e ) {
116+ throw new IOException (msg .append (" (400): Invalid URL" ).toString (), e );
117+ } catch (ResourceNotFoundException e ) {
118+ throw new IOException (msg .append (" (404): Not found" ).toString (), e );
180119 }
181-
182- conn .connect ();
183- return conn ;
184120 }
185121
186122 /**
@@ -196,99 +132,6 @@ private URL buildUrl(String sha1sum) throws MalformedURLException {
196132 return new URL (rootURL + "/api/search/checksum?sha1=" + sha1sum );
197133 }
198134
199- /**
200- * Process the Artifactory response.
201- *
202- * @param dependency the dependency
203- * @param conn the HTTP URL Connection
204- * @return a list of the Maven Artifact information
205- * @throws IOException thrown if there is an I/O error
206- */
207- protected List <MavenArtifact > processResponse (Dependency dependency , HttpURLConnection conn ) throws IOException {
208- final List <MavenArtifact > result = new ArrayList <>();
209-
210- try (InputStreamReader streamReader = new InputStreamReader (conn .getInputStream (), StandardCharsets .UTF_8 );
211- JsonParser parser = objectReader .getFactory ().createParser (streamReader )) {
212-
213- if (init (parser ) && parser .nextToken () == com .fasterxml .jackson .core .JsonToken .START_OBJECT ) {
214- // at least one result
215- do {
216- final FileImpl file = objectReader .readValue (parser );
217-
218- checkHashes (dependency , file .getChecksums ());
219-
220- final Matcher pathMatcher = PATH_PATTERN .matcher (file .getPath ());
221- if (!pathMatcher .matches ()) {
222- throw new IllegalStateException ("Cannot extract the Maven information from the path "
223- + "retrieved in Artifactory " + file .getPath ());
224- }
225-
226- final String groupId = pathMatcher .group ("groupId" ).replace ('/' , '.' );
227- final String artifactId = pathMatcher .group ("artifactId" );
228- final String version = pathMatcher .group ("version" );
229-
230- result .add (new MavenArtifact (groupId , artifactId , version , file .getDownloadUri (),
231- MavenArtifact .derivePomUrl (artifactId , version , file .getDownloadUri ())));
232-
233- } while (parser .nextToken () == com .fasterxml .jackson .core .JsonToken .START_OBJECT );
234- } else {
235- throw new FileNotFoundException ("Artifact " + dependency + " not found in Artifactory" );
236- }
237- }
238-
239- return result ;
240- }
241-
242- protected boolean init (JsonParser parser ) throws IOException {
243- com .fasterxml .jackson .core .JsonToken nextToken = parser .nextToken ();
244- if (nextToken != com .fasterxml .jackson .core .JsonToken .START_OBJECT ) {
245- throw new IOException ("Expected " + com .fasterxml .jackson .core .JsonToken .START_OBJECT + ", got " + nextToken );
246- }
247-
248- do {
249- nextToken = parser .nextToken ();
250- if (nextToken == null ) {
251- break ;
252- }
253-
254- if (nextToken .isStructStart ()) {
255- if (nextToken == com .fasterxml .jackson .core .JsonToken .START_ARRAY && "results" .equals (parser .currentName ())) {
256- return true ;
257- } else {
258- parser .skipChildren ();
259- }
260- }
261- } while (true );
262-
263- return false ;
264- }
265-
266- /**
267- * Validates the hashes of the dependency.
268- *
269- * @param dependency the dependency
270- * @param checksums the collection of checksums (md5, sha1, sha256)
271- * @throws FileNotFoundException thrown if one of the checksums does not
272- * match
273- */
274- private void checkHashes (Dependency dependency , ChecksumsImpl checksums ) throws FileNotFoundException {
275- final String md5sum = dependency .getMd5sum ();
276- if (!checksums .getMd5 ().equals (md5sum )) {
277- throw new FileNotFoundException ("Artifact found by API is not matching the md5 "
278- + "of the artifact (repository hash is " + checksums .getMd5 () + WHILE_ACTUAL_IS + md5sum + ") !" );
279- }
280- final String sha1sum = dependency .getSha1sum ();
281- if (!checksums .getSha1 ().equals (sha1sum )) {
282- throw new FileNotFoundException ("Artifact found by API is not matching the SHA1 "
283- + "of the artifact (repository hash is " + checksums .getSha1 () + WHILE_ACTUAL_IS + sha1sum + ") !" );
284- }
285- final String sha256sum = dependency .getSha256sum ();
286- if (checksums .getSha256 () != null && !checksums .getSha256 ().equals (sha256sum )) {
287- throw new FileNotFoundException ("Artifact found by API is not matching the SHA-256 "
288- + "of the artifact (repository hash is " + checksums .getSha256 () + WHILE_ACTUAL_IS + sha256sum + ") !" );
289- }
290- }
291-
292135 /**
293136 * Performs a pre-flight request to ensure the Artifactory service is
294137 * reachable.
@@ -297,17 +140,20 @@ private void checkHashes(Dependency dependency, ChecksumsImpl checksums) throws
297140 * <code>false</code>.
298141 */
299142 public boolean preflightRequest () {
143+ URL url = null ;
300144 try {
301- final URL url = buildUrl (Checksum .getSHA1Checksum (UUID .randomUUID ().toString ()));
302- final HttpURLConnection connection = connect (url );
303- if (connection .getResponseCode () != 200 ) {
304- LOGGER .warn ("Expected 200 result from Artifactory ({}), got {}" , url , connection .getResponseCode ());
305- return false ;
306- }
145+ url = buildUrl (Checksum .getSHA1Checksum (UUID .randomUUID ().toString ()));
146+ Downloader .getInstance ().fetchContent (url , StandardCharsets .UTF_8 );
307147 return true ;
308148 } catch (IOException e ) {
309149 LOGGER .error ("Cannot connect to Artifactory" , e );
310150 return false ;
151+ } catch (TooManyRequestsException e ) {
152+ LOGGER .warn ("Expected 200 result from Artifactory ({}), got 429" , url );
153+ return false ;
154+ } catch (ResourceNotFoundException e ) {
155+ LOGGER .warn ("Expected 200 result from Artifactory ({}), got 404" , url );
156+ return false ;
311157 }
312158
313159 }
0 commit comments