Skip to content

Commit 406ea7f

Browse files
authored
feat: ArtifactoryAnalyzer updated to use the HTTPClient5-based Downloader and skip unusable results (#7293)
1 parent 4e13760 commit 406ea7f

File tree

5 files changed

+788
-496
lines changed

5 files changed

+788
-496
lines changed

core/src/main/java/org/owasp/dependencycheck/data/artifactory/ArtifactorySearch.java

Lines changed: 32 additions & 186 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,26 @@
1919

2020
import java.io.FileNotFoundException;
2121
import java.io.IOException;
22-
import java.io.InputStreamReader;
23-
import java.net.HttpURLConnection;
2422
import java.net.MalformedURLException;
23+
import java.net.URISyntaxException;
2524
import java.net.URL;
2625
import java.nio.charset.StandardCharsets;
27-
import java.util.ArrayList;
28-
import java.util.Base64;
2926
import java.util.List;
3027
import java.util.UUID;
31-
import java.util.regex.Matcher;
32-
import java.util.regex.Pattern;
3328

3429
import javax.annotation.concurrent.ThreadSafe;
3530

31+
import org.apache.hc.core5.http.message.BasicHeader;
3632
import org.owasp.dependencycheck.data.nexus.MavenArtifact;
3733
import org.owasp.dependencycheck.dependency.Dependency;
3834
import 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;
4037
import org.owasp.dependencycheck.utils.Settings;
41-
import org.owasp.dependencycheck.utils.URLConnectionFactory;
38+
import org.owasp.dependencycheck.utils.TooManyRequestsException;
4239
import org.slf4j.Logger;
4340
import 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
@@ -56,22 +49,13 @@
5649
* @author nhenneaux
5750
*/
5851
@ThreadSafe
59-
@SuppressWarnings("squid:S2647")
6052
public 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

Comments
 (0)