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 CONF_NAME_CHECKSUM_ALGORITHMS = "checksumAlgorithms";
81
82 private static final String DEFAULT_CHECKSUM_ALGORITHMS = "SHA-1";
83
84 private static final String CONF_NAME_FAIL_IF_MISSING = "failIfMissing";
85
86 private static final String CONF_NAME_SNAPSHOTS = "snapshots";
87
88
89
90
91
92
93
94 public static final String CONFIG_PROP_SCOPE = "scope";
95
96 public static final String ALL_SCOPE = "all";
97
98 public static final String PROJECT_SCOPE = "project";
99
100 public static final String DEFAULT_SCOPE = ALL_SCOPE;
101
102 private static final String CONF_NAME_RECORD = "record";
103
104 private static final String CHECKSUM_ALGORITHMS_CACHE_KEY =
105 TrustedChecksumsArtifactResolverPostProcessor.class.getName() + ".checksumAlgorithms";
106
107 private final ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector;
108
109 private final Map<String, TrustedChecksumsSource> trustedChecksumsSources;
110
111 @Inject
112 public TrustedChecksumsArtifactResolverPostProcessor(
113 ChecksumAlgorithmFactorySelector checksumAlgorithmFactorySelector,
114 Map<String, TrustedChecksumsSource> trustedChecksumsSources) {
115 super(NAME);
116 this.checksumAlgorithmFactorySelector = requireNonNull(checksumAlgorithmFactorySelector);
117 this.trustedChecksumsSources = requireNonNull(trustedChecksumsSources);
118 }
119
120 private boolean inScope(RepositorySystemSession session, ArtifactResult artifactResult) {
121 String scope = ConfigUtils.getString(session, DEFAULT_SCOPE, configPropKey(CONFIG_PROP_SCOPE));
122 if (ALL_SCOPE.equals(scope)) {
123 return artifactResult.isResolved();
124 } else if (PROJECT_SCOPE.equals(scope)) {
125 return artifactResult.isResolved()
126 && artifactResult.getRequest().getRequestContext().startsWith("project");
127 } else {
128 throw new IllegalArgumentException("Unknown value for configuration " + CONFIG_PROP_SCOPE + ": " + scope);
129 }
130 }
131
132 @SuppressWarnings("unchecked")
133 @Override
134 protected void doPostProcess(RepositorySystemSession session, List<ArtifactResult> artifactResults) {
135 final List<ChecksumAlgorithmFactory> checksumAlgorithms = (List<ChecksumAlgorithmFactory>) session.getData()
136 .computeIfAbsent(
137 CHECKSUM_ALGORITHMS_CACHE_KEY,
138 () -> checksumAlgorithmFactorySelector.selectList(
139 ConfigUtils.parseCommaSeparatedUniqueNames(ConfigUtils.getString(
140 session,
141 DEFAULT_CHECKSUM_ALGORITHMS,
142 configPropKey(CONF_NAME_CHECKSUM_ALGORITHMS)))));
143
144 final boolean failIfMissing = ConfigUtils.getBoolean(session, false, configPropKey(CONF_NAME_FAIL_IF_MISSING));
145 final boolean record = ConfigUtils.getBoolean(session, false, configPropKey(CONF_NAME_RECORD));
146 final boolean snapshots = ConfigUtils.getBoolean(session, false, configPropKey(CONF_NAME_SNAPSHOTS));
147
148 for (ArtifactResult artifactResult : artifactResults) {
149 if (artifactResult.getRequest().getArtifact().isSnapshot() && !snapshots) {
150 continue;
151 }
152 if (inScope(session, artifactResult)) {
153 if (record) {
154 recordArtifactChecksums(session, artifactResult, checksumAlgorithms);
155 } else if (!validateArtifactChecksums(session, artifactResult, checksumAlgorithms, failIfMissing)) {
156 artifactResult.setArtifact(artifactResult.getArtifact().setFile(null));
157 }
158 }
159 }
160 }
161
162
163
164
165 private void recordArtifactChecksums(
166 RepositorySystemSession session,
167 ArtifactResult artifactResult,
168 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories) {
169 Artifact artifact = artifactResult.getArtifact();
170 ArtifactRepository artifactRepository = artifactResult.getRepository();
171 try {
172 final Map<String, String> calculatedChecksums =
173 ChecksumAlgorithmHelper.calculate(artifact.getFile(), checksumAlgorithmFactories);
174
175 for (TrustedChecksumsSource trustedChecksumsSource : trustedChecksumsSources.values()) {
176 TrustedChecksumsSource.Writer writer =
177 trustedChecksumsSource.getTrustedArtifactChecksumsWriter(session);
178 if (writer != null) {
179 try {
180 writer.addTrustedArtifactChecksums(
181 artifact, artifactRepository, checksumAlgorithmFactories, calculatedChecksums);
182 } catch (IOException e) {
183 throw new UncheckedIOException(
184 "Could not write required checksums for " + artifact.getFile(), e);
185 }
186 }
187 }
188 } catch (IOException e) {
189 throw new UncheckedIOException("Could not calculate required checksums for " + artifact.getFile(), e);
190 }
191 }
192
193
194
195
196
197 private boolean validateArtifactChecksums(
198 RepositorySystemSession session,
199 ArtifactResult artifactResult,
200 List<ChecksumAlgorithmFactory> checksumAlgorithmFactories,
201 boolean failIfMissing) {
202 Artifact artifact = artifactResult.getArtifact();
203 ArtifactRepository artifactRepository = artifactResult.getRepository();
204 boolean valid = true;
205 boolean validated = false;
206 try {
207
208 final Map<String, String> calculatedChecksums =
209 ChecksumAlgorithmHelper.calculate(artifact.getFile(), checksumAlgorithmFactories);
210
211 for (Map.Entry<String, TrustedChecksumsSource> entry : trustedChecksumsSources.entrySet()) {
212 final String trustedSourceName = entry.getKey();
213 final TrustedChecksumsSource trustedChecksumsSource = entry.getValue();
214
215
216 Map<String, String> trustedChecksums = trustedChecksumsSource.getTrustedArtifactChecksums(
217 session, artifact, artifactRepository, checksumAlgorithmFactories);
218
219 if (trustedChecksums == null) {
220 continue;
221 }
222 validated = true;
223
224 if (!calculatedChecksums.equals(trustedChecksums)) {
225 Set<String> missingTrustedAlg = new HashSet<>(calculatedChecksums.keySet());
226 missingTrustedAlg.removeAll(trustedChecksums.keySet());
227
228 if (!missingTrustedAlg.isEmpty() && failIfMissing) {
229 artifactResult.addException(new ChecksumFailureException("Missing from " + trustedSourceName
230 + " trusted checksum(s) " + missingTrustedAlg + " for artifact "
231 + ArtifactIdUtils.toId(artifact)));
232 valid = false;
233 }
234
235
236
237 for (ChecksumAlgorithmFactory checksumAlgorithmFactory : checksumAlgorithmFactories) {
238 String calculatedChecksum = calculatedChecksums.get(checksumAlgorithmFactory.getName());
239 String trustedChecksum = trustedChecksums.get(checksumAlgorithmFactory.getName());
240 if (trustedChecksum != null && !Objects.equals(calculatedChecksum, trustedChecksum)) {
241 artifactResult.addException(new ChecksumFailureException("Artifact "
242 + ArtifactIdUtils.toId(artifact) + " trusted checksum mismatch: "
243 + trustedSourceName + "=" + trustedChecksum + "; calculated="
244 + calculatedChecksum));
245 valid = false;
246 }
247 }
248 }
249 }
250
251 if (!validated && failIfMissing) {
252 artifactResult.addException(new ChecksumFailureException(
253 "There are no enabled trusted checksums" + " source(s) to validate against."));
254 valid = false;
255 }
256 } catch (IOException e) {
257 throw new UncheckedIOException(e);
258 }
259 return valid;
260 }
261 }