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