1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.owasp.dependencycheck.data.artifactory;
19
20 import com.fasterxml.jackson.core.JsonParser;
21 import com.fasterxml.jackson.core.JsonToken;
22 import com.fasterxml.jackson.databind.DeserializationFeature;
23 import com.fasterxml.jackson.databind.ObjectMapper;
24 import com.fasterxml.jackson.databind.ObjectReader;
25 import org.apache.hc.core5.http.ClassicHttpResponse;
26 import org.apache.hc.core5.http.io.HttpClientResponseHandler;
27 import org.owasp.dependencycheck.data.nexus.MavenArtifact;
28 import org.owasp.dependencycheck.dependency.Dependency;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import java.io.FileNotFoundException;
33 import java.io.IOException;
34 import java.io.InputStreamReader;
35 import java.nio.charset.StandardCharsets;
36 import java.util.ArrayList;
37 import java.util.List;
38 import java.util.Optional;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42 class ArtifactorySearchResponseHandler implements HttpClientResponseHandler<List<MavenArtifact>> {
43
44
45
46 private static final Pattern PATH_PATTERN = Pattern.compile("^/(?<groupId>.+)/(?<artifactId>[^/]+)/(?<version>[^/]+)/[^/]+$");
47
48
49
50
51 private static final Logger LOGGER = LoggerFactory.getLogger(ArtifactorySearchResponseHandler.class);
52
53
54
55
56 private final ObjectReader fileImplReader;
57
58
59
60
61 private final Dependency expectedDependency;
62
63
64
65
66
67
68 ArtifactorySearchResponseHandler(Dependency dependency) {
69 this.fileImplReader = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).readerFor(FileImpl.class);
70 this.expectedDependency = dependency;
71 }
72
73 protected boolean init(JsonParser parser) throws IOException {
74 com.fasterxml.jackson.core.JsonToken nextToken = parser.nextToken();
75 if (nextToken != com.fasterxml.jackson.core.JsonToken.START_OBJECT) {
76 throw new IOException("Expected " + com.fasterxml.jackson.core.JsonToken.START_OBJECT + ", got " + nextToken);
77 }
78
79 do {
80 nextToken = parser.nextToken();
81 if (nextToken == null) {
82 break;
83 }
84
85 if (nextToken.isStructStart()) {
86 if (nextToken == com.fasterxml.jackson.core.JsonToken.START_ARRAY && "results".equals(parser.currentName())) {
87 return true;
88 } else {
89 parser.skipChildren();
90 }
91 }
92 } while (true);
93
94 return false;
95 }
96
97
98
99
100
101
102
103 private boolean checkHashes(ChecksumsImpl checksums) {
104 final String md5sum = expectedDependency.getMd5sum();
105 final String hashMismatchFormat = "Artifact found by API is not matching the {} of the artifact (repository hash is {} while actual is {}) !";
106 boolean match = true;
107 if (!checksums.getMd5().equals(md5sum)) {
108 LOGGER.warn(hashMismatchFormat, "md5", md5sum, checksums.getMd5());
109 match = false;
110 }
111 final String sha1sum = expectedDependency.getSha1sum();
112 if (!checksums.getSha1().equals(sha1sum)) {
113 LOGGER.warn(hashMismatchFormat, "sha1", sha1sum, checksums.getSha1());
114 match = false;
115 }
116 final String sha256sum = expectedDependency.getSha256sum();
117
118
119
120
121
122
123
124 if (checksums.getSha256() != null && !checksums.getSha256().equals(sha256sum)) {
125 LOGGER.warn(hashMismatchFormat, "sha256", sha256sum, checksums.getSha256());
126 match = false;
127 }
128 return match;
129 }
130
131
132
133
134
135
136
137
138
139 @Override
140 public List<MavenArtifact> handleResponse(ClassicHttpResponse response) throws IOException {
141 final List<MavenArtifact> result = new ArrayList<>();
142
143 try (InputStreamReader streamReader = new InputStreamReader(response.getEntity().getContent(), StandardCharsets.UTF_8);
144 JsonParser parser = fileImplReader.getFactory().createParser(streamReader)) {
145
146 if (init(parser) && parser.nextToken() == JsonToken.START_OBJECT) {
147
148 do {
149 final FileImpl file = fileImplReader.readValue(parser);
150
151 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());
154 continue;
155 }
156
157 final Optional<Matcher> validationResult = validateUsability(file);
158 if (validationResult.isEmpty()) {
159 continue;
160 }
161 final Matcher pathMatcher = validationResult.get();
162
163 final String groupId = pathMatcher.group("groupId").replace('/', '.');
164 final String artifactId = pathMatcher.group("artifactId");
165 final String version = pathMatcher.group("version");
166
167 result.add(new MavenArtifact(groupId, artifactId, version, file.getDownloadUri(),
168 MavenArtifact.derivePomUrl(artifactId, version, file.getDownloadUri())));
169
170 } while (parser.nextToken() == JsonToken.START_OBJECT);
171 } else {
172 throw new FileNotFoundException("Artifact " + expectedDependency + " not found in Artifactory");
173 }
174 }
175 if (result.isEmpty()) {
176 throw new FileNotFoundException("Artifact " + expectedDependency
177 + " not found in Artifactory; discovered sha1 hits not recognized as matching maven artifacts");
178 }
179 return result;
180 }
181
182
183
184
185
186
187
188
189
190
191 private Optional<Matcher> validateUsability(FileImpl file) {
192 final Optional<Matcher> result;
193 if (!checkHashes(file.getChecksums())) {
194 result = Optional.empty();
195 } else {
196 final Matcher pathMatcher = PATH_PATTERN.matcher(file.getPath());
197 if (!pathMatcher.matches()) {
198 LOGGER.debug("Cannot extract the Maven information from the path retrieved in Artifactory {}", file.getPath());
199 result = Optional.empty();
200 } else {
201 result = Optional.of(pathMatcher);
202 }
203 }
204 return result;
205 }
206 }