1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.maven.impl;
20
21 import javax.xml.stream.Location;
22 import javax.xml.stream.XMLStreamException;
23
24 import java.io.File;
25 import java.io.IOException;
26 import java.io.InputStream;
27 import java.nio.file.Path;
28 import java.nio.file.Paths;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.concurrent.atomic.AtomicInteger;
32 import java.util.function.Supplier;
33 import java.util.function.UnaryOperator;
34
35 import org.apache.maven.api.Constants;
36 import org.apache.maven.api.ProtoSession;
37 import org.apache.maven.api.di.Inject;
38 import org.apache.maven.api.di.Named;
39 import org.apache.maven.api.services.BuilderProblem;
40 import org.apache.maven.api.services.Interpolator;
41 import org.apache.maven.api.services.ProblemCollector;
42 import org.apache.maven.api.services.SettingsBuilder;
43 import org.apache.maven.api.services.SettingsBuilderException;
44 import org.apache.maven.api.services.SettingsBuilderRequest;
45 import org.apache.maven.api.services.SettingsBuilderResult;
46 import org.apache.maven.api.services.Source;
47 import org.apache.maven.api.services.xml.SettingsXmlFactory;
48 import org.apache.maven.api.services.xml.XmlReaderException;
49 import org.apache.maven.api.services.xml.XmlReaderRequest;
50 import org.apache.maven.api.settings.Activation;
51 import org.apache.maven.api.settings.IdentifiableBase;
52 import org.apache.maven.api.settings.Profile;
53 import org.apache.maven.api.settings.Repository;
54 import org.apache.maven.api.settings.RepositoryPolicy;
55 import org.apache.maven.api.settings.Server;
56 import org.apache.maven.api.settings.Settings;
57 import org.apache.maven.settings.v4.SettingsMerger;
58 import org.apache.maven.settings.v4.SettingsTransformer;
59 import org.codehaus.plexus.components.secdispatcher.Dispatcher;
60 import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
61 import org.codehaus.plexus.components.secdispatcher.internal.DefaultSecDispatcher;
62
63
64
65
66
67 @Named
68 public class DefaultSettingsBuilder implements SettingsBuilder {
69
70 private final DefaultSettingsValidator settingsValidator = new DefaultSettingsValidator();
71
72 private final SettingsMerger settingsMerger = new SettingsMerger() {
73 @Override
74 protected KeyComputer<Repository> getRepositoryKey() {
75 return IdentifiableBase::getId;
76 }
77 };
78
79 private final SettingsXmlFactory settingsXmlFactory;
80
81 private final Interpolator interpolator;
82
83 private final Map<String, Dispatcher> dispatchers;
84
85
86
87
88 @Inject
89 public DefaultSettingsBuilder(
90 SettingsXmlFactory settingsXmlFactory, Interpolator interpolator, Map<String, Dispatcher> dispatchers) {
91 this.settingsXmlFactory = settingsXmlFactory;
92 this.interpolator = interpolator;
93 this.dispatchers = dispatchers;
94 }
95
96 @Override
97 public SettingsBuilderResult build(SettingsBuilderRequest request) throws SettingsBuilderException {
98 ProblemCollector<BuilderProblem> problems = ProblemCollector.create(request.getSession());
99
100 Source installationSource = request.getInstallationSettingsSource().orElse(null);
101 Settings installation = readSettings(installationSource, false, request, problems);
102
103 Source projectSource = request.getProjectSettingsSource().orElse(null);
104 Settings project = readSettings(projectSource, true, request, problems);
105
106 Source userSource = request.getUserSettingsSource().orElse(null);
107 Settings user = readSettings(userSource, false, request, problems);
108
109 Settings effective =
110 settingsMerger.merge(user, settingsMerger.merge(project, installation, false, null), false, null);
111
112
113
114
115 if (effective.getRepositories().isEmpty()
116 && effective.getPluginRepositories().isEmpty()) {
117 Repository central = Repository.newBuilder()
118 .id("central")
119 .name("Central Repository")
120 .url("https://repo.maven.apache.org/maven2")
121 .snapshots(RepositoryPolicy.newBuilder().enabled(false).build())
122 .build();
123 Repository centralWithNoUpdate = central.withReleases(
124 RepositoryPolicy.newBuilder().updatePolicy("never").build());
125 effective = Settings.newBuilder(effective)
126 .repositories(List.of(central))
127 .pluginRepositories(List.of(centralWithNoUpdate))
128 .build();
129 }
130
131
132 String localRepository = effective.getLocalRepository();
133 if (localRepository != null && !localRepository.isEmpty()) {
134 Path file = Paths.get(localRepository);
135 if (!file.isAbsolute() && file.toString().startsWith(File.separator)) {
136 effective = effective.withLocalRepository(file.toAbsolutePath().toString());
137 }
138 }
139
140 if (problems.hasErrorProblems()) {
141 throw new SettingsBuilderException("Error building settings", problems);
142 }
143
144 return new DefaultSettingsBuilderResult(request, effective, problems);
145 }
146
147 private Settings readSettings(
148 Source settingsSource,
149 boolean isProjectSettings,
150 SettingsBuilderRequest request,
151 ProblemCollector<BuilderProblem> problems) {
152 if (settingsSource == null) {
153 return Settings.newInstance();
154 }
155
156 Settings settings;
157
158 try {
159 try (InputStream is = settingsSource.openStream()) {
160 settings = settingsXmlFactory.read(XmlReaderRequest.builder()
161 .inputStream(is)
162 .location(settingsSource.getLocation())
163 .strict(true)
164 .build());
165 } catch (XmlReaderException e) {
166 try (InputStream is = settingsSource.openStream()) {
167 settings = settingsXmlFactory.read(XmlReaderRequest.builder()
168 .inputStream(is)
169 .location(settingsSource.getLocation())
170 .strict(false)
171 .build());
172 Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
173 problems.reportProblem(new DefaultBuilderProblem(
174 settingsSource.getLocation(),
175 loc != null ? loc.getLineNumber() : -1,
176 loc != null ? loc.getColumnNumber() : -1,
177 e,
178 e.getMessage(),
179 BuilderProblem.Severity.WARNING));
180 }
181 }
182 } catch (XmlReaderException e) {
183 Location loc = e.getCause() instanceof XMLStreamException xe ? xe.getLocation() : null;
184 problems.reportProblem(new DefaultBuilderProblem(
185 settingsSource.getLocation(),
186 loc != null ? loc.getLineNumber() : -1,
187 loc != null ? loc.getColumnNumber() : -1,
188 e,
189 "Non-parseable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
190 BuilderProblem.Severity.FATAL));
191 return Settings.newInstance();
192 } catch (IOException e) {
193 problems.reportProblem(new DefaultBuilderProblem(
194 settingsSource.getLocation(),
195 -1,
196 -1,
197 e,
198 "Non-readable settings " + settingsSource.getLocation() + ": " + e.getMessage(),
199 BuilderProblem.Severity.FATAL));
200 return Settings.newInstance();
201 }
202
203 settings = interpolate(settings, request, problems);
204 settings = decrypt(settingsSource, settings, request, problems);
205
206 settingsValidator.validate(settings, isProjectSettings, problems);
207
208 if (isProjectSettings) {
209 settings = Settings.newBuilder(settings, true)
210 .localRepository(null)
211 .interactiveMode(false)
212 .offline(false)
213 .proxies(List.of())
214 .usePluginRegistry(false)
215 .servers(settings.getServers().stream()
216 .map(s -> Server.newBuilder(s, true)
217 .username(null)
218 .passphrase(null)
219 .privateKey(null)
220 .password(null)
221 .filePermissions(null)
222 .directoryPermissions(null)
223 .build())
224 .toList())
225 .build();
226 }
227
228 return settings;
229 }
230
231 private Settings interpolate(
232 Settings settings, SettingsBuilderRequest request, ProblemCollector<BuilderProblem> problems) {
233 UnaryOperator<String> src;
234 if (request.getInterpolationSource().isPresent()) {
235 src = request.getInterpolationSource().get();
236 } else {
237 Map<String, String> userProperties = request.getSession().getUserProperties();
238 Map<String, String> systemProperties = request.getSession().getSystemProperties();
239 src = Interpolator.chain(userProperties::get, systemProperties::get);
240 }
241 return new DefSettingsTransformer(value -> value != null ? interpolator.interpolate(value, src) : null)
242 .visit(settings);
243 }
244
245 static class DefSettingsTransformer extends SettingsTransformer {
246 DefSettingsTransformer(UnaryOperator<String> transformer) {
247 super(transformer);
248 }
249
250 @Override
251 protected Activation.Builder transformActivation_Condition(
252 Supplier<? extends Activation.Builder> creator, Activation.Builder builder, Activation target) {
253
254 return builder;
255 }
256 }
257
258 private Settings decrypt(
259 Source settingsSource,
260 Settings settings,
261 SettingsBuilderRequest request,
262 ProblemCollector<BuilderProblem> problems) {
263 if (dispatchers.isEmpty()) {
264 return settings;
265 }
266 SecDispatcher secDispatcher = new DefaultSecDispatcher(dispatchers, getSecuritySettings(request.getSession()));
267 final AtomicInteger preMaven4Passwords = new AtomicInteger(0);
268 UnaryOperator<String> decryptFunction = str -> {
269 if (str != null && !str.isEmpty() && !str.contains("${") && secDispatcher.isAnyEncryptedString(str)) {
270 if (secDispatcher.isLegacyEncryptedString(str)) {
271
272 preMaven4Passwords.incrementAndGet();
273 }
274 try {
275 return secDispatcher.decrypt(str);
276 } catch (Exception e) {
277 problems.reportProblem(new DefaultBuilderProblem(
278 settingsSource.getLocation(),
279 -1,
280 -1,
281 e,
282 "Could not decrypt password (fix the corrupted password or remove it, if unused) " + str,
283 BuilderProblem.Severity.FATAL));
284 }
285 }
286 return str;
287 };
288 Settings result = new SettingsTransformer(decryptFunction).visit(settings);
289 if (preMaven4Passwords.get() > 0) {
290 problems.reportProblem(new DefaultBuilderProblem(
291 settingsSource.getLocation(),
292 -1,
293 -1,
294 null,
295 "Detected " + preMaven4Passwords.get() + " pre-Maven 4 legacy encrypted password(s) "
296 + "- configure password encryption with the help of mvnenc for increased security.",
297 BuilderProblem.Severity.WARNING));
298 }
299 return result;
300 }
301
302 private Path getSecuritySettings(ProtoSession session) {
303 Map<String, String> properties = session.getUserProperties();
304 String settingsSecurity = properties.get(Constants.MAVEN_SETTINGS_SECURITY);
305 if (settingsSecurity != null) {
306 return Paths.get(settingsSecurity);
307 }
308 String mavenUserConf = properties.get(Constants.MAVEN_USER_CONF);
309 if (mavenUserConf != null) {
310 return Paths.get(mavenUserConf, Constants.MAVEN_SETTINGS_SECURITY_FILE_NAME);
311 }
312 return Paths.get(properties.get("user.home"), ".m2", Constants.MAVEN_SETTINGS_SECURITY_FILE_NAME);
313 }
314
315 @Override
316 public ProblemCollector<BuilderProblem> validate(Settings settings, boolean isProjectSettings) {
317
318 ProblemCollector<BuilderProblem> problems = ProblemCollector.create(null);
319 settingsValidator.validate(settings, isProjectSettings, problems);
320 return problems;
321 }
322
323 @Override
324 public Profile convert(org.apache.maven.api.model.Profile profile) {
325 return SettingsUtilsV4.convertToSettingsProfile(profile);
326 }
327
328 @Override
329 public org.apache.maven.api.model.Profile convert(Profile profile) {
330 return SettingsUtilsV4.convertFromSettingsProfile(profile);
331 }
332
333
334
335
336
337 static class DefaultSettingsBuilderResult implements SettingsBuilderResult {
338
339 private final SettingsBuilderRequest request;
340
341 private final Settings effectiveSettings;
342
343 private final ProblemCollector<BuilderProblem> problems;
344
345 DefaultSettingsBuilderResult(
346 SettingsBuilderRequest request, Settings effectiveSettings, ProblemCollector<BuilderProblem> problems) {
347 this.request = request;
348 this.effectiveSettings = effectiveSettings;
349 this.problems = (problems != null) ? problems : ProblemCollector.empty();
350 }
351
352 @Override
353 public SettingsBuilderRequest getRequest() {
354 return request;
355 }
356
357 @Override
358 public Settings getEffectiveSettings() {
359 return effectiveSettings;
360 }
361
362 @Override
363 public ProblemCollector<BuilderProblem> getProblems() {
364 return problems;
365 }
366 }
367 }