Skip to content

Commit 8bd8caf

Browse files
authored
feat: Improve Artifactory handler log message (#7838)
2 parents 326eb6b + 7f92ad9 commit 8bd8caf

File tree

3 files changed

+73
-34
lines changed

3 files changed

+73
-34
lines changed

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

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@
5252
@ThreadSafe
5353
public class ArtifactorySearch {
5454

55+
/**
56+
* Required to add all extra information of the found artifact.
57+
* Source: https://jfrog.com/help/r/jfrog-rest-apis/property-search
58+
*/
59+
@SuppressWarnings("JavadocLinkAsPlainText")
60+
public static final String X_RESULT_DETAIL_HEADER = "X-Result-Detail";
61+
5562
/**
5663
* Used for logging.
5764
*/
@@ -108,9 +115,13 @@ public List<MavenArtifact> search(Dependency dependency) throws IOException {
108115
final StringBuilder msg = new StringBuilder("Could not connect to Artifactory at")
109116
.append(url);
110117
try {
111-
final BasicHeader artifactoryResultDetail = new BasicHeader("X-Result-Detail", "info");
112-
return Downloader.getInstance().fetchAndHandle(url, new ArtifactorySearchResponseHandler(dependency), List.of(artifactoryResultDetail),
113-
allowUsingProxy);
118+
final BasicHeader artifactoryResultDetail = new BasicHeader(X_RESULT_DETAIL_HEADER, "info");
119+
return Downloader.getInstance().fetchAndHandle(
120+
url,
121+
new ArtifactorySearchResponseHandler(url, dependency),
122+
List.of(artifactoryResultDetail),
123+
allowUsingProxy
124+
);
114125
} catch (TooManyRequestsException e) {
115126
throw new IOException(msg.append(" (429): Too manu requests").toString(), e);
116127
} catch (URISyntaxException e) {

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

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,16 @@
3232
import java.io.FileNotFoundException;
3333
import java.io.IOException;
3434
import java.io.InputStreamReader;
35+
import java.net.URL;
3536
import java.nio.charset.StandardCharsets;
3637
import java.util.ArrayList;
3738
import java.util.List;
3839
import java.util.Optional;
3940
import java.util.regex.Matcher;
4041
import java.util.regex.Pattern;
4142

43+
import static org.owasp.dependencycheck.data.artifactory.ArtifactorySearch.X_RESULT_DETAIL_HEADER;
44+
4245
class ArtifactorySearchResponseHandler implements HttpClientResponseHandler<List<MavenArtifact>> {
4346
/**
4447
* Pattern to match the path returned by the Artifactory AQL API.
@@ -61,13 +64,20 @@ class ArtifactorySearchResponseHandler implements HttpClientResponseHandler<List
6164
private final Dependency expectedDependency;
6265

6366
/**
64-
* Creates a responsehandler for the response on a single dependency-search attempt.
67+
* The search request URL i.e., the location at which to search artifacts with checksum information
68+
*/
69+
private final URL sourceUrl;
70+
71+
/**
72+
* Creates a response handler for the response on a single dependency-search attempt.
6573
*
74+
* @param sourceUrl The search request URL
6675
* @param dependency The dependency that is expected to be in the response when found (for validating the FileItem(s) in the response)
6776
*/
68-
ArtifactorySearchResponseHandler(Dependency dependency) {
77+
ArtifactorySearchResponseHandler(URL sourceUrl, Dependency dependency) {
6978
this.fileImplReader = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readerFor(FileImpl.class);
7079
this.expectedDependency = dependency;
80+
this.sourceUrl = sourceUrl;
7181
}
7282

7383
protected boolean init(JsonParser parser) throws IOException {
@@ -114,7 +124,7 @@ private boolean checkHashes(ChecksumsImpl checksums) {
114124
match = false;
115125
}
116126
final String sha256sum = expectedDependency.getSha256sum();
117-
/* For sha256 we need to validate that the checksum is non-null in the artifactory response.
127+
/* For SHA-256, we need to validate that the checksum is non-null in the artifactory response.
118128
* Extract from Artifactory documentation:
119129
* New artifacts that are uploaded to Artifactory 5.5 and later will automatically have their SHA-256 checksum calculated.
120130
* However, artifacts that were already hosted in Artifactory before the upgrade will not have their SHA-256 checksum in the database yet.
@@ -132,7 +142,7 @@ private boolean checkHashes(ChecksumsImpl checksums) {
132142
* Process the Artifactory response.
133143
*
134144
* @param response the HTTP response
135-
* @return a list of the Maven Artifact informations that match the searched dependency hash
145+
* @return a list of the Maven Artifact information that matches the searched dependency hash
136146
* @throws FileNotFoundException When a matching artifact is not found
137147
* @throws IOException thrown if there is an I/O error
138148
*/
@@ -149,8 +159,11 @@ public List<MavenArtifact> handleResponse(ClassicHttpResponse response) throws I
149159
final FileImpl file = fileImplReader.readValue(parser);
150160

151161
if (file.getChecksums() == null) {
152-
LOGGER.warn("No checksums found in artifactory search result of uri {}. Please make sure that header X-Result-Detail is retained on any (reverse)-proxy, loadbalancer or WebApplicationFirewall in the network path to your Artifactory Server",
153-
file.getUri());
162+
LOGGER.warn(
163+
"No checksums found in Artifactory search result for '{}'. " +
164+
"Specifically, the result set contains URI '{}' but it is missing the 'checksums' property. " +
165+
"Please make sure that the '{}' header is retained on any (reverse-)proxy, load-balancer or Web Application Firewall in the network path to your Artifactory server.",
166+
sourceUrl, file.getUri(), X_RESULT_DETAIL_HEADER);
154167
continue;
155168
}
156169

@@ -174,18 +187,18 @@ public List<MavenArtifact> handleResponse(ClassicHttpResponse response) throws I
174187
}
175188
if (result.isEmpty()) {
176189
throw new FileNotFoundException("Artifact " + expectedDependency
177-
+ " not found in Artifactory; discovered sha1 hits not recognized as matching maven artifacts");
190+
+ " not found in Artifactory; discovered SHA1 hits not recognized as matching Maven artifacts");
178191
}
179192
return result;
180193
}
181194

182195
/**
183196
* Validate the FileImpl result for usability as a dependency.
184197
* <br/>
185-
* Checks that the actually matches all known hashes and the path appears to match a maven repository G/A/V pattern.
198+
* Checks that the file actually matches all known hashes and the path appears to match a maven repository G/A/V pattern.
186199
*
187200
* @param file The FileImpl from an Artifactory search response
188-
* @return An Optional with the Matcher for the file path to retrieve the Maven G/A/V coordinates in case result is usable for further
201+
* @return An Optional with the Matcher for the file path to retrieve the Maven G/A/V coordinates in case the result is usable for further
189202
* processing, otherwise an empty Optional.
190203
*/
191204
private Optional<Matcher> validateUsability(FileImpl file) {

core/src/test/java/org/owasp/dependencycheck/data/artifactory/ArtifactorySearchResponseHandlerTest.java

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@
3333
import java.io.ByteArrayInputStream;
3434
import java.io.FileNotFoundException;
3535
import java.io.IOException;
36+
import java.net.MalformedURLException;
37+
import java.net.URL;
3638
import java.nio.charset.StandardCharsets;
3739
import java.util.List;
3840

@@ -41,8 +43,20 @@
4143
import static org.mockito.Mockito.mock;
4244
import static org.mockito.Mockito.when;
4345

46+
@SuppressWarnings("resource")
4447
class ArtifactorySearchResponseHandlerTest extends BaseTest {
4548

49+
private static final URL TEST_URL;
50+
private static final String EXCEPTION_MESSAGE = "Artifact Dependency{ fileName='null', actualFilePath='null', filePath='null', packagePath='null'} not found in Artifactory; discovered SHA1 hits not recognized as matching Maven artifacts";
51+
52+
static {
53+
try {
54+
TEST_URL = new URL("https://example.com/artifactory/api/search/checksum?sha1=43515aa4b2f4bce7c431145e8c0a7bcc441e0532");
55+
} catch (MalformedURLException e) {
56+
throw new RuntimeException(e);
57+
}
58+
}
59+
4660
@BeforeEach
4761
@Override
4862
public void setUp() throws Exception {
@@ -88,7 +102,7 @@ void shouldProcessCorrectlyArtifactoryAnswerWithoutSha256() throws IOException {
88102

89103

90104
// When
91-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
105+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
92106
final List<MavenArtifact> mavenArtifacts = handler.handleResponse(response);
93107

94108
// Then
@@ -118,7 +132,7 @@ void shouldProcessCorrectlyArtifactoryAnswerWithMultipleMatches() throws IOExcep
118132

119133

120134
// When
121-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
135+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
122136
final List<MavenArtifact> mavenArtifacts = handler.handleResponse(response);
123137

124138
// Then
@@ -152,6 +166,7 @@ void shouldProcessCorrectlyForMissingXResultDetailHeader() throws IOException {
152166
final ListAppender<ILoggingEvent> listAppender = new ListAppender<>();
153167
listAppender.start();
154168
sutLogger.addAppender(listAppender);
169+
final String logMessage = "No checksums found in Artifactory search result for '{}'. Specifically, the result set contains URI '{}' but it is missing the 'checksums' property. Please make sure that the '{}' header is retained on any (reverse-)proxy, load-balancer or Web Application Firewall in the network path to your Artifactory server.";
155170

156171
// Given
157172
final Dependency dependency = new Dependency();
@@ -167,32 +182,32 @@ void shouldProcessCorrectlyForMissingXResultDetailHeader() throws IOException {
167182

168183

169184
// When
170-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
185+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
171186

172187
FileNotFoundException e = assertThrows(FileNotFoundException.class, () -> handler.handleResponse(response),
173188
"Result with no details due to missing X-Result-Detail header, should throw an exception!");
174189

175190
// Then
176-
assertEquals("Artifact Dependency{ fileName='freemarker-2.3.33.jar', actualFilePath='null', filePath='null', packagePath='null'} not found in Artifactory; discovered sha1 hits not recognized as matching maven artifacts",
191+
assertEquals("Artifact Dependency{ fileName='freemarker-2.3.33.jar', actualFilePath='null', filePath='null', packagePath='null'} not found in Artifactory; discovered SHA1 hits not recognized as matching Maven artifacts",
177192
e.getMessage());
178193

179-
// There should be a WARN-log for for each of the results regarding the absence of X-Result-Detail header driven attributes
194+
// There should be a WARN-log for each of the results regarding the absence of X-Result-Detail header driven attributes
180195
final List<ILoggingEvent> logsList = listAppender.list;
181196
assertEquals(2, logsList.size(), "Number of log entries for the ArtifactorySearchResponseHandler");
182197

183198
ILoggingEvent logEvent = logsList.get(0);
184199
assertEquals(Level.WARN, logEvent.getLevel());
185-
assertEquals("No checksums found in artifactory search result of uri {}. Please make sure that header X-Result-Detail is retained on any (reverse)-proxy, loadbalancer or WebApplicationFirewall in the network path to your Artifactory Server", logEvent.getMessage());
200+
assertEquals(logMessage, logEvent.getMessage());
186201
Object[] args = logEvent.getArgumentArray();
187-
assertEquals(1, args.length);
188-
assertEquals("https://artifactory.example.com:443/artifactory/api/storage/maven-central-cache/org/freemarker/freemarker/2.3.33/freemarker-2.3.33.jar", args[0]);
202+
assertEquals(3, args.length);
203+
assertEquals("https://artifactory.example.com:443/artifactory/api/storage/maven-central-cache/org/freemarker/freemarker/2.3.33/freemarker-2.3.33.jar", args[1]);
189204

190205
logEvent = logsList.get(1);
191206
assertEquals(Level.WARN, logEvent.getLevel());
192-
assertEquals("No checksums found in artifactory search result of uri {}. Please make sure that header X-Result-Detail is retained on any (reverse)-proxy, loadbalancer or WebApplicationFirewall in the network path to your Artifactory Server", logEvent.getMessage());
207+
assertEquals(logMessage, logEvent.getMessage());
193208
args = logEvent.getArgumentArray();
194-
assertEquals(1, args.length);
195-
assertEquals("https://artifactory.example.com:443/artifactory/api/storage/gradle-plugins-extended-cache/org/freemarker/freemarker/2.3.33/freemarker-2.3.33.jar", args[0]);
209+
assertEquals(3, args.length);
210+
assertEquals("https://artifactory.example.com:443/artifactory/api/storage/gradle-plugins-extended-cache/org/freemarker/freemarker/2.3.33/freemarker-2.3.33.jar", args[1]);
196211

197212
// Remove our manually injected additional appender
198213
sutLogger.detachAppender(listAppender);
@@ -212,7 +227,7 @@ void shouldHandleNoMatches() throws IOException {
212227
when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(payload));
213228

214229
// When
215-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
230+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
216231
FileNotFoundException e = assertThrows(FileNotFoundException.class, () -> handler.handleResponse(response),
217232
"No Match found, should throw an exception!");
218233
// Then
@@ -298,7 +313,7 @@ void shouldProcessCorrectlyArtifactoryAnswer() throws IOException {
298313
when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(payload));
299314

300315
// When
301-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
316+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
302317
final List<MavenArtifact> mavenArtifacts = handler.handleResponse(response);
303318

304319
// Then
@@ -419,13 +434,13 @@ void shouldProcessCorrectlyArtifactoryAnswerMisMatchMd5() throws IOException {
419434
when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(payload));
420435

421436
// When
422-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
437+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
423438
FileNotFoundException e = assertThrows(FileNotFoundException.class, () -> handler.handleResponse(response),
424439
"MD5 mismatching should throw an exception!");
425440

426441
// Then
427442
assertEquals("Artifact " + dependency
428-
+ " not found in Artifactory; discovered sha1 hits not recognized as matching maven artifacts", e.getMessage());
443+
+ " not found in Artifactory; discovered SHA1 hits not recognized as matching Maven artifacts", e.getMessage());
429444
}
430445

431446
@Test
@@ -442,12 +457,12 @@ void shouldProcessCorrectlyArtifactoryAnswerMisMatchSha1() throws IOException {
442457
when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(payload));
443458

444459
// When
445-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
460+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
446461
FileNotFoundException e = assertThrows(FileNotFoundException.class, () -> handler.handleResponse(response),
447462
"SHA1 mismatching should throw an exception!");
448463

449464
// Then
450-
assertEquals("Artifact Dependency{ fileName='null', actualFilePath='null', filePath='null', packagePath='null'} not found in Artifactory; discovered sha1 hits not recognized as matching maven artifacts", e.getMessage());
465+
assertEquals(EXCEPTION_MESSAGE, e.getMessage());
451466
}
452467

453468
@Test
@@ -464,12 +479,12 @@ void shouldProcessCorrectlyArtifactoryAnswerMisMatchSha256() throws IOException
464479
when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(payload));
465480

466481
// When
467-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
482+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
468483
FileNotFoundException e = assertThrows(FileNotFoundException.class, () -> handler.handleResponse(response),
469484
"SHA256 mismatching should throw an exception!");
470485

471486
// Then
472-
assertEquals("Artifact Dependency{ fileName='null', actualFilePath='null', filePath='null', packagePath='null'} not found in Artifactory; discovered sha1 hits not recognized as matching maven artifacts", e.getMessage());
487+
assertEquals(EXCEPTION_MESSAGE, e.getMessage());
473488
}
474489

475490
@Test
@@ -487,12 +502,12 @@ void shouldThrowNotFoundWhenPatternCannotBeParsed() throws IOException {
487502
when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(payload));
488503

489504
// When
490-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
505+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
491506
FileNotFoundException e = assertThrows(FileNotFoundException.class, () -> handler.handleResponse(response),
492507
"Maven GAV pattern mismatch for filepath should throw a not found exception!");
493508

494509
// Then
495-
assertEquals("Artifact Dependency{ fileName='null', actualFilePath='null', filePath='null', packagePath='null'} not found in Artifactory; discovered sha1 hits not recognized as matching maven artifacts", e.getMessage());
510+
assertEquals(EXCEPTION_MESSAGE, e.getMessage());
496511
}
497512

498513
@Test
@@ -509,7 +524,7 @@ void shouldSkipResultsWherePatternCannotBeParsed() throws IOException {
509524
when(responseEntity.getContent()).thenReturn(new ByteArrayInputStream(payload));
510525

511526
// When
512-
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(dependency);
527+
final ArtifactorySearchResponseHandler handler = new ArtifactorySearchResponseHandler(TEST_URL, dependency);
513528
List<MavenArtifact> result = handler.handleResponse(response);
514529
// Then
515530
assertEquals(1, result.size());

0 commit comments

Comments
 (0)