Skip to content

Commit b8858bd

Browse files
committed
Enable support for use of encryption in Maven's settings.xml
This commit updates the CLI so that it will decrypt any encrypted passwords in a user's Maven settings.xml file. The code that performs the decrytion has a transitive dependency on three types in Plexus' logging API. There are tens of different artifacts containing this API available in Maven Central. Rather than bloating the API with a dependency on a complete Plexus container, which could perhaps be considered the primary source, a dependency on a considerably smaller artifact has been introduced. Closes #574
1 parent c5820d8 commit b8858bd

File tree

7 files changed

+210
-11
lines changed

7 files changed

+210
-11
lines changed

spring-boot-cli/pom.xml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@
6767
<groupId>org.apache.maven</groupId>
6868
<artifactId>maven-settings-builder</artifactId>
6969
</dependency>
70+
<dependency>
71+
<groupId>org.codehaus.plexus</groupId>
72+
<artifactId>plexus-component-api</artifactId>
73+
<exclusions>
74+
<exclusion>
75+
<groupId>*</groupId>
76+
<artifactId>*</artifactId>
77+
</exclusion>
78+
</exclusions>
79+
</dependency>
7080
<dependency>
7181
<groupId>org.eclipse.aether</groupId>
7282
<artifactId>aether-api</artifactId>

spring-boot-cli/src/main/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfiguration.java

Lines changed: 74 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package org.springframework.boot.cli.compiler.grape;
1818

1919
import java.io.File;
20+
import java.lang.reflect.Field;
21+
import java.util.List;
2022

2123
import org.apache.maven.settings.Mirror;
2224
import org.apache.maven.settings.Proxy;
@@ -26,6 +28,10 @@
2628
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
2729
import org.apache.maven.settings.building.SettingsBuildingException;
2830
import org.apache.maven.settings.building.SettingsBuildingRequest;
31+
import org.apache.maven.settings.crypto.DefaultSettingsDecrypter;
32+
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
33+
import org.apache.maven.settings.crypto.SettingsDecrypter;
34+
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
2935
import org.eclipse.aether.DefaultRepositorySystemSession;
3036
import org.eclipse.aether.RepositorySystem;
3137
import org.eclipse.aether.repository.Authentication;
@@ -38,6 +44,9 @@
3844
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
3945
import org.eclipse.aether.util.repository.DefaultMirrorSelector;
4046
import org.eclipse.aether.util.repository.DefaultProxySelector;
47+
import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
48+
import org.sonatype.plexus.components.cipher.PlexusCipherException;
49+
import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
4150

4251
/**
4352
* Auto-configuration for a RepositorySystemSession that uses Maven's settings.xml to
@@ -48,18 +57,34 @@
4857
public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
4958
RepositorySystemSessionAutoConfiguration {
5059

51-
private static final String HOME_DIR = System.getProperty("user.home");
60+
private static final String DEFAULT_HOME_DIR = System.getProperty("user.home");
61+
62+
private final String homeDir;
63+
64+
public SettingsXmlRepositorySystemSessionAutoConfiguration() {
65+
this(DEFAULT_HOME_DIR);
66+
}
67+
68+
SettingsXmlRepositorySystemSessionAutoConfiguration(String homeDir) {
69+
this.homeDir = homeDir;
70+
}
5271

5372
@Override
5473
public void apply(DefaultRepositorySystemSession session,
5574
RepositorySystem repositorySystem) {
5675

5776
Settings settings = loadSettings();
77+
SettingsDecryptionResult decryptionResult = decryptSettings(settings);
78+
if (!decryptionResult.getProblems().isEmpty()) {
79+
throw new IllegalStateException("Settings decryption failed: "
80+
+ decryptionResult.getProblems());
81+
}
5882

5983
session.setOffline(settings.isOffline());
6084
session.setMirrorSelector(createMirrorSelector(settings));
61-
session.setAuthenticationSelector(createAuthenticationSelector(settings));
62-
session.setProxySelector(createProxySelector(settings));
85+
session.setAuthenticationSelector(createAuthenticationSelector(decryptionResult
86+
.getServers()));
87+
session.setProxySelector(createProxySelector(decryptionResult.getProxies()));
6388

6489
String localRepository = settings.getLocalRepository();
6590
if (localRepository != null) {
@@ -69,7 +94,7 @@ public void apply(DefaultRepositorySystemSession session,
6994
}
7095

7196
private Settings loadSettings() {
72-
File settingsFile = new File(HOME_DIR, ".m2/settings.xml");
97+
File settingsFile = new File(this.homeDir, ".m2/settings.xml");
7398
SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
7499
request.setUserSettingsFile(settingsFile);
75100
try {
@@ -82,6 +107,32 @@ private Settings loadSettings() {
82107
}
83108
}
84109

110+
private SettingsDecryptionResult decryptSettings(Settings settings) {
111+
DefaultSettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest(
112+
settings);
113+
114+
return createSettingsDecrypter().decrypt(request);
115+
}
116+
117+
private SettingsDecrypter createSettingsDecrypter() {
118+
SettingsDecrypter settingsDecrypter = new DefaultSettingsDecrypter();
119+
setField(DefaultSettingsDecrypter.class, "securityDispatcher", settingsDecrypter,
120+
new SpringBootSecDispatcher());
121+
return settingsDecrypter;
122+
}
123+
124+
private void setField(Class<?> clazz, String fieldName, Object target, Object value) {
125+
try {
126+
Field field = clazz.getDeclaredField(fieldName);
127+
field.setAccessible(true);
128+
field.set(target, value);
129+
}
130+
catch (Exception e) {
131+
throw new IllegalStateException("Failed to set field '" + fieldName
132+
+ "' on '" + target + "'", e);
133+
}
134+
}
135+
85136
private MirrorSelector createMirrorSelector(Settings settings) {
86137
DefaultMirrorSelector selector = new DefaultMirrorSelector();
87138
for (Mirror mirror : settings.getMirrors()) {
@@ -91,9 +142,9 @@ private MirrorSelector createMirrorSelector(Settings settings) {
91142
return selector;
92143
}
93144

94-
private AuthenticationSelector createAuthenticationSelector(Settings settings) {
145+
private AuthenticationSelector createAuthenticationSelector(List<Server> servers) {
95146
DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector();
96-
for (Server server : settings.getServers()) {
147+
for (Server server : servers) {
97148
AuthenticationBuilder auth = new AuthenticationBuilder();
98149
auth.addUsername(server.getUsername()).addPassword(server.getPassword());
99150
auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
@@ -102,9 +153,9 @@ private AuthenticationSelector createAuthenticationSelector(Settings settings) {
102153
return new ConservativeAuthenticationSelector(selector);
103154
}
104155

105-
private ProxySelector createProxySelector(Settings settings) {
156+
private ProxySelector createProxySelector(List<Proxy> proxies) {
106157
DefaultProxySelector selector = new DefaultProxySelector();
107-
for (Proxy proxy : settings.getProxies()) {
158+
for (Proxy proxy : proxies) {
108159
Authentication authentication = new AuthenticationBuilder()
109160
.addUsername(proxy.getUsername()).addPassword(proxy.getPassword())
110161
.build();
@@ -114,4 +165,19 @@ private ProxySelector createProxySelector(Settings settings) {
114165
}
115166
return selector;
116167
}
168+
169+
private class SpringBootSecDispatcher extends DefaultSecDispatcher {
170+
171+
public SpringBootSecDispatcher() {
172+
this._configurationFile = new File(
173+
SettingsXmlRepositorySystemSessionAutoConfiguration.this.homeDir,
174+
".m2/settings-security.xml").getAbsolutePath();
175+
try {
176+
this._cipher = new DefaultPlexusCipher();
177+
}
178+
catch (PlexusCipherException e) {
179+
throw new IllegalStateException(e);
180+
}
181+
}
182+
}
117183
}

spring-boot-cli/src/test/java/org/springframework/boot/cli/compiler/grape/SettingsXmlRepositorySystemSessionAutoConfigurationTests.java

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,19 @@
2020
import org.apache.maven.settings.building.SettingsBuildingException;
2121
import org.eclipse.aether.DefaultRepositorySystemSession;
2222
import org.eclipse.aether.RepositorySystem;
23+
import org.eclipse.aether.repository.Authentication;
24+
import org.eclipse.aether.repository.AuthenticationContext;
2325
import org.eclipse.aether.repository.LocalRepositoryManager;
26+
import org.eclipse.aether.repository.Proxy;
27+
import org.eclipse.aether.repository.RemoteRepository;
2428
import org.junit.Rule;
2529
import org.junit.Test;
2630
import org.junit.rules.ExpectedException;
2731
import org.junit.runner.RunWith;
2832
import org.mockito.Mock;
2933
import org.mockito.runners.MockitoJUnitRunner;
3034

35+
import static org.junit.Assert.assertEquals;
3136
import static org.junit.Assert.assertNotNull;
3237

3338
/**
@@ -49,12 +54,60 @@ public class SettingsXmlRepositorySystemSessionAutoConfigurationTests {
4954

5055
@Test
5156
public void basicSessionCustomization() throws SettingsBuildingException {
57+
assertSessionCustomization("src/test/resources/maven-settings/basic");
58+
}
59+
60+
@Test
61+
public void encryptedSettingsSessionCustomization() throws SettingsBuildingException {
62+
assertSessionCustomization("src/test/resources/maven-settings/encrypted");
63+
}
64+
65+
private void assertSessionCustomization(String userHome) {
5266
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
5367

54-
new SettingsXmlRepositorySystemSessionAutoConfiguration().apply(session,
68+
new SettingsXmlRepositorySystemSessionAutoConfiguration(userHome).apply(session,
5569
this.repositorySystem);
5670

57-
assertNotNull(session.getMirrorSelector());
58-
assertNotNull(session.getProxySelector());
71+
RemoteRepository repository = new RemoteRepository.Builder("my-server",
72+
"default", "http://maven.example.com").build();
73+
74+
assertMirrorSelectorConfiguration(session, repository);
75+
assertProxySelectorConfiguration(session, repository);
76+
assertAuthenticationSelectorConfiguration(session, repository);
77+
}
78+
79+
private void assertProxySelectorConfiguration(DefaultRepositorySystemSession session,
80+
RemoteRepository repository) {
81+
Proxy proxy = session.getProxySelector().getProxy(repository);
82+
repository = new RemoteRepository.Builder(repository).setProxy(proxy).build();
83+
AuthenticationContext authenticationContext = AuthenticationContext.forProxy(
84+
session, repository);
85+
assertEquals("proxy.example.com", proxy.getHost());
86+
assertEquals("proxyuser",
87+
authenticationContext.get(AuthenticationContext.USERNAME));
88+
assertEquals("somepassword",
89+
authenticationContext.get(AuthenticationContext.PASSWORD));
90+
}
91+
92+
private void assertMirrorSelectorConfiguration(
93+
DefaultRepositorySystemSession session, RemoteRepository repository) {
94+
RemoteRepository mirror = session.getMirrorSelector().getMirror(repository);
95+
assertNotNull("No mirror configured for repository " + repository.getId(), mirror);
96+
assertEquals("maven.example.com", mirror.getHost());
97+
}
98+
99+
private void assertAuthenticationSelectorConfiguration(
100+
DefaultRepositorySystemSession session, RemoteRepository repository) {
101+
Authentication authentication = session.getAuthenticationSelector()
102+
.getAuthentication(repository);
103+
104+
repository = new RemoteRepository.Builder(repository).setAuthentication(
105+
authentication).build();
106+
107+
AuthenticationContext authenticationContext = AuthenticationContext
108+
.forRepository(session, repository);
109+
110+
assertEquals("tester", authenticationContext.get(AuthenticationContext.USERNAME));
111+
assertEquals("secret", authenticationContext.get(AuthenticationContext.PASSWORD));
59112
}
60113
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<settings>
2+
3+
<mirrors>
4+
<mirror>
5+
<id>my-mirror</id>
6+
<url>http://maven.example.com/mirror</url>
7+
<mirrorOf>my-server</mirrorOf>
8+
</mirror>
9+
</mirrors>
10+
11+
<servers>
12+
<server>
13+
<id>my-server</id>
14+
<username>tester</username>
15+
<password>secret</password>
16+
</server>
17+
</servers>
18+
19+
<proxies>
20+
<proxy>
21+
<id>my-proxy</id>
22+
<active>true</active>
23+
<protocol>http</protocol>
24+
<host>proxy.example.com</host>
25+
<port>8080</port>
26+
<username>proxyuser</username>
27+
<password>somepassword</password>
28+
</proxy>
29+
</proxies>
30+
31+
</settings>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<settingsSecurity>
2+
<master>{oAyWuFO63U8HHgiplpqtgXih0/pwcRA0d+uA+Z7TBEk=}</master>
3+
</settingsSecurity>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<settings>
2+
3+
<mirrors>
4+
<mirror>
5+
<id>my-mirror</id>
6+
<url>http://maven.example.com/mirror</url>
7+
<mirrorOf>my-server</mirrorOf>
8+
</mirror>
9+
</mirrors>
10+
11+
<servers>
12+
<server>
13+
<id>my-server</id>
14+
<username>tester</username>
15+
<password>{Ur5BpeQGlYUHhXsHahO/HbMBcPSFSUtN5gbWuFFPYGw=}</password>
16+
</server>
17+
</servers>
18+
19+
<proxies>
20+
<proxy>
21+
<id>my-proxy</id>
22+
<active>true</active>
23+
<protocol>http</protocol>
24+
<host>proxy.example.com</host>
25+
<port>8080</port>
26+
<username>proxyuser</username>
27+
<password>{3iRQQyaIUgQHwH8uzTvr9/52pZAjLOTWz/SlWDB7CM4=}</password>
28+
</proxy>
29+
</proxies>
30+
31+
</settings>

spring-boot-parent/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,11 @@
9898
<artifactId>plexus-archiver</artifactId>
9999
<version>2.4.4</version>
100100
</dependency>
101+
<dependency>
102+
<groupId>org.codehaus.plexus</groupId>
103+
<artifactId>plexus-component-api</artifactId>
104+
<version>1.0-alpha-33</version>
105+
</dependency>
101106
<dependency>
102107
<groupId>org.codehaus.plexus</groupId>
103108
<artifactId>plexus-utils</artifactId>

0 commit comments

Comments
 (0)