1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.aether.internal.impl.resolution;
20
21 import javax.inject.Inject;
22 import javax.inject.Named;
23 import javax.inject.Singleton;
24
25 import java.io.IOException;
26 import java.io.UncheckedIOException;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Objects;
31 import java.util.Set;
32
33 import org.eclipse.aether.RepositorySystemSession;
34 import org.eclipse.aether.artifact.Artifact;
35 import org.eclipse.aether.repository.ArtifactRepository;
36 import org.eclipse.aether.resolution.ArtifactResult;
37 import org.eclipse.aether.spi.checksums.TrustedChecksumsSource;
38 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactory;
39 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmFactorySelector;
40 import org.eclipse.aether.spi.connector.checksum.ChecksumAlgorithmHelper;
41 import org.eclipse.aether.transfer.ChecksumFailureException;
42 import org.eclipse.aether.util.ConfigUtils;
43 import org.eclipse.aether.util.artifact.ArtifactIdUtils;
44
45 import static java.util.Objects.requireNonNull;
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 @Singleton
76 @Named(TrustedChecksumsArtifactResolverPostProcessor.NAME)
77 public final class TrustedChecksumsArtifactResolverPostProcessor extends ArtifactResolverPostProcessorSupport {
78 public static final String NAME = "trustedChecksums";
79
80 private static final String CONFIG_PROPS_PREFIX =
81 ArtifactResolverPostProcessorSupport.CONFIG_PROPS_PREFIX + NAME + ".";
82
83
84
85
86
87
88
89
90 public static final String CONFIG_PROP_ENABLED = ArtifactResolverPostProcessorSupport.CONFIG_PROPS_PREFIX + NAME;
91
92
93
94
95
96
97
98
99 public static final String CONFIG_PROP_CHECKSUM_ALGORITHMS = CONFIG_PROPS_PREFIX + "checksumAlgorithms";
100
101 public static final String DEFAULT_CHECKSUM_ALGORITHMS = "SHA-1";
102
103
104
105
106
107
108
109
110
111
112
113 public static final String CONFIG_PROP_SCOPE = CONFIG_PROPS_PREFIX + "scope";
114
115 public static final String ALL_SCOPE = "all";
116
117 public static final String PROJECT_SCOPE = "project";
118
119 public static final String DEFAULT_SCOPE = ALL_SCOPE;
120
121
122
123
124
125
126
127
128 public static final String CONFIG_PROP_FAIL_IF_MISSING = CONFIG_PROPS_PREFIX + "failIfMissing";
129
130
131
132
133
134
135
136
137 public static final String CONFIG_PROP_SNAPSHOTS = CONFIG_PROPS_PREFIX + "snapshots";
138
139
140
141
142
143
144
145
146 public static final String CONFIG_PROP_RECORD = CONFIG_PROPS_PREFIX + "record";
147
148 private static final String CHECKSUM_ALGORITHMS_CACHE_KEY =
149 TrustedChecksumsArtifactResolverPostProcessor.class.getName() + ".checksumAlgorithms";
150
151 private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
152
153 private final Map<String, TrustedChecksumsSource> trustedChecksumsSources;
154
155 @Inject
156 public TrustedChecksumsArtifactResolverPostProcessor(
157 ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector,
158 Map<String, TrustedChecksumsSource> trustedChecksumsSources) {
159 this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
160 this.trustedChecksumsSources = requireNonNull(trustedChecksumsSources);
161 }
162
163 @Override
164 protected boolean isEnabled(RepositorySystemSession session) {
165 return ConfigUtils.getBoolean(session, false, CONFIG_PROP_ENABLED);
166 }
167
168 private boolean inScope(RepositorySystemSession session, ArtifactResult artifactResult) {
169 String scope = ConfigUtils.getString(session, DEFAULT_SCOPE, CONFIG_PROP_SCOPE);
170 if (ALL_SCOPE.equals(scope)) {
171 return artifactResult.isResolved();
172 } else if (PROJECT_SCOPE.equals(scope)) {
173 return artifactResult.isResolved()
174 && artifactResult.getRequest().getRequestContext().startsWith("project");
175 } else {
176 throw new IllegalArgumentException("Unknown value for configuration " + CONFIG_PROP_SCOPE + ": " + scope);
177 }
178 }
179
180 @SuppressWarnings("unchecked")
181 @Override
182 protected void doPostProcess(RepositorySystemSession session, List<ArtifactResult> artifactResults) {
183 final List<ChecksumAlgorithmFactory> checksumAlgorithms = (List<ChecksumAlgorithmFactory>) session.getData()
184 .computeIfAbsent(
185 CHECKSUM_ALGORITHMS_CACHE_KEY,
186 () -> checksumAlgorithmFactorySelector.selectList(
187 ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString(
188 session, DEFAULT_CHECKSUM_ALGORITHMS, CONFIG_PROP_CHECKSUM_ALGORITHMS))));
189
190 final boolean failIfMissing = ConfigUtils.getBoolean(session, false, CONFIG_PROP_FAIL_IF_MISSING);
191 final boolean record = ConfigUtils.getBoolean(session, false, CONFIG_PROP_RECORD);
192 final boolean snapshots = ConfigUtils.getBoolean(session, false, CONFIG_PROP_SNAPSHOTS);
193
194 for (ArtifactResult artifactResult : artifactResults) {
195 if (artifactResult.getRequest().getArtifact().isSnapshot() && !snapshots) {
196 continue;
197 }
198 if (inScope(session, artifactResult)) {
199 if (record) {
200 recordArtifactChecksums(session, artifactResult, checksumAlgorithms);
201 } else if (!validateArtifactChecksums(session, artifactResult, checksumAlgorithms, failIfMissing)) {
202 artifactResult.setArtifact(artifactResult.getArtifact().setPath(null));
203 }
204 }
205 }
206 }
207
208
209
210
211 private void recordArtifactChecksums(
212 RepositorySystemSession session,
213 ArtifactResult artifactResult,
214 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) {
215 Artifact artifact = artifactResult.getArtifact();
216 ArtifactRepository artifactRepository = artifactResult.getRepository();
217 try {
218 final Map<String, String> calculatedChecksums =
219 ChecksumAlgorithmHelper.calculate(artifact.getPath(), checksumAlgorithmFactories);
220
221 for (TrustedChecksumsSource trustedChecksumsSource : trustedChecksumsSources.values()) {
222 TrustedChecksumsSource.Writer writer =
223 trustedChecksumsSource.getTrustedArtifactChecksumsWriter(session);
224 if (writer != null) {
225 try {
226 writer.addTrustedArtifactChecksums(
227 artifact, artifactRepository, checksumAlgorithmFactories, calculatedChecksums);
228 } catch (IOException e) {
229 throw new UncheckedIOException(
230 "Could not write required checksums for " + artifact.getPath(), e);
231 }
232 }
233 }
234 } catch (IOException e) {
235 throw new UncheckedIOException("Could not calculate required checksums for " + artifact.getPath(), e);
236 }
237 }
238
239
240
241
242
243 private boolean validateArtifactChecksums(
244 RepositorySystemSession session,
245 ArtifactResult artifactResult,
246 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
247 boolean failIfMissing) {
248 Artifact artifact = artifactResult.getArtifact();
249 ArtifactRepository artifactRepository = artifactResult.getRepository();
250 boolean valid = true;
251 boolean validated = false;
252 try {
253
254 final Map<String, String> calculatedChecksums =
255 ChecksumAlgorithmHelper.calculate(artifact.getPath(), checksumAlgorithmFactories);
256
257 for (Map.Entry<String, TrustedChecksumsSource> entry : trustedChecksumsSources.entrySet()) {
258 final String trustedSourceName = entry.getKey();
259 final TrustedChecksumsSource trustedChecksumsSource = entry.getValue();
260
261
262 Map<String, String> trustedChecksums = trustedChecksumsSource.getTrustedArtifactChecksums(
263 session, artifact, artifactRepository, checksumAlgorithmFactories);
264
265 if (trustedChecksums == null) {
266 continue;
267 }
268 validated = true;
269
270 if (!calculatedChecksums.equals(trustedChecksums)) {
271 Set<String> missingTrustedAlg = new HashSet<>(calculatedChecksums.keySet());
272 missingTrustedAlg.removeAll(trustedChecksums.keySet());
273
274 if (!missingTrustedAlg.isEmpty() && failIfMissing) {
275 artifactResult.addException(
276 artifactRepository,
277 new ChecksumFailureException("Missing from " + trustedSourceName
278 + " trusted checksum(s) " + missingTrustedAlg + " for artifact "
279 + ArtifactIdUtils.toId(artifact)));
280 valid = false;
281 }
282
283
284
285 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) {
286 String calculatedChecksum = calculatedChecksums.get(checksumAlgorithmFactory.getName());
287 String trustedChecksum = trustedChecksums.get(checksumAlgorithmFactory.getName());
288 if (trustedChecksum != null && !Objects.equals(calculatedChecksum, trustedChecksum)) {
289 artifactResult.addException(
290 artifactRepository,
291 new ChecksumFailureException("Artifact "
292 + ArtifactIdUtils.toId(artifact) + " trusted checksum mismatch: "
293 + trustedSourceName + "=" + trustedChecksum + "; calculated="
294 + calculatedChecksum));
295 valid = false;
296 }
297 }
298 }
299 }
300
301 if (!validated && failIfMissing) {
302 artifactResult.addException(
303 artifactRepository,
304 new ChecksumFailureException(
305 "There are no enabled trusted checksums" + " source(s) to validate against."));
306 valid = false;
307 }
308 } catch (IOException e) {
309 throw new UncheckedIOException(e);
310 }
311 return valid;
312 }
313 }