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