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
pull/590/head
Andy Wilkinson 11 years ago
parent c5820d872a
commit b8858bdb8f

@ -67,6 +67,16 @@
<groupId>org.apache.maven</groupId> <groupId>org.apache.maven</groupId>
<artifactId>maven-settings-builder</artifactId> <artifactId>maven-settings-builder</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-api</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency> <dependency>
<groupId>org.eclipse.aether</groupId> <groupId>org.eclipse.aether</groupId>
<artifactId>aether-api</artifactId> <artifactId>aether-api</artifactId>

@ -17,6 +17,8 @@
package org.springframework.boot.cli.compiler.grape; package org.springframework.boot.cli.compiler.grape;
import java.io.File; import java.io.File;
import java.lang.reflect.Field;
import java.util.List;
import org.apache.maven.settings.Mirror; import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Proxy; import org.apache.maven.settings.Proxy;
@ -26,6 +28,10 @@ import org.apache.maven.settings.building.DefaultSettingsBuilderFactory;
import org.apache.maven.settings.building.DefaultSettingsBuildingRequest; import org.apache.maven.settings.building.DefaultSettingsBuildingRequest;
import org.apache.maven.settings.building.SettingsBuildingException; import org.apache.maven.settings.building.SettingsBuildingException;
import org.apache.maven.settings.building.SettingsBuildingRequest; import org.apache.maven.settings.building.SettingsBuildingRequest;
import org.apache.maven.settings.crypto.DefaultSettingsDecrypter;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.repository.Authentication; import org.eclipse.aether.repository.Authentication;
@ -38,6 +44,9 @@ import org.eclipse.aether.util.repository.ConservativeAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultAuthenticationSelector; import org.eclipse.aether.util.repository.DefaultAuthenticationSelector;
import org.eclipse.aether.util.repository.DefaultMirrorSelector; import org.eclipse.aether.util.repository.DefaultMirrorSelector;
import org.eclipse.aether.util.repository.DefaultProxySelector; import org.eclipse.aether.util.repository.DefaultProxySelector;
import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
import org.sonatype.plexus.components.cipher.PlexusCipherException;
import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
/** /**
* Auto-configuration for a RepositorySystemSession that uses Maven's settings.xml to * Auto-configuration for a RepositorySystemSession that uses Maven's settings.xml to
@ -48,18 +57,34 @@ import org.eclipse.aether.util.repository.DefaultProxySelector;
public class SettingsXmlRepositorySystemSessionAutoConfiguration implements public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
RepositorySystemSessionAutoConfiguration { RepositorySystemSessionAutoConfiguration {
private static final String HOME_DIR = System.getProperty("user.home"); private static final String DEFAULT_HOME_DIR = System.getProperty("user.home");
private final String homeDir;
public SettingsXmlRepositorySystemSessionAutoConfiguration() {
this(DEFAULT_HOME_DIR);
}
SettingsXmlRepositorySystemSessionAutoConfiguration(String homeDir) {
this.homeDir = homeDir;
}
@Override @Override
public void apply(DefaultRepositorySystemSession session, public void apply(DefaultRepositorySystemSession session,
RepositorySystem repositorySystem) { RepositorySystem repositorySystem) {
Settings settings = loadSettings(); Settings settings = loadSettings();
SettingsDecryptionResult decryptionResult = decryptSettings(settings);
if (!decryptionResult.getProblems().isEmpty()) {
throw new IllegalStateException("Settings decryption failed: "
+ decryptionResult.getProblems());
}
session.setOffline(settings.isOffline()); session.setOffline(settings.isOffline());
session.setMirrorSelector(createMirrorSelector(settings)); session.setMirrorSelector(createMirrorSelector(settings));
session.setAuthenticationSelector(createAuthenticationSelector(settings)); session.setAuthenticationSelector(createAuthenticationSelector(decryptionResult
session.setProxySelector(createProxySelector(settings)); .getServers()));
session.setProxySelector(createProxySelector(decryptionResult.getProxies()));
String localRepository = settings.getLocalRepository(); String localRepository = settings.getLocalRepository();
if (localRepository != null) { if (localRepository != null) {
@ -69,7 +94,7 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
} }
private Settings loadSettings() { private Settings loadSettings() {
File settingsFile = new File(HOME_DIR, ".m2/settings.xml"); File settingsFile = new File(this.homeDir, ".m2/settings.xml");
SettingsBuildingRequest request = new DefaultSettingsBuildingRequest(); SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
request.setUserSettingsFile(settingsFile); request.setUserSettingsFile(settingsFile);
try { try {
@ -82,6 +107,32 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
} }
} }
private SettingsDecryptionResult decryptSettings(Settings settings) {
DefaultSettingsDecryptionRequest request = new DefaultSettingsDecryptionRequest(
settings);
return createSettingsDecrypter().decrypt(request);
}
private SettingsDecrypter createSettingsDecrypter() {
SettingsDecrypter settingsDecrypter = new DefaultSettingsDecrypter();
setField(DefaultSettingsDecrypter.class, "securityDispatcher", settingsDecrypter,
new SpringBootSecDispatcher());
return settingsDecrypter;
}
private void setField(Class<?> clazz, String fieldName, Object target, Object value) {
try {
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
}
catch (Exception e) {
throw new IllegalStateException("Failed to set field '" + fieldName
+ "' on '" + target + "'", e);
}
}
private MirrorSelector createMirrorSelector(Settings settings) { private MirrorSelector createMirrorSelector(Settings settings) {
DefaultMirrorSelector selector = new DefaultMirrorSelector(); DefaultMirrorSelector selector = new DefaultMirrorSelector();
for (Mirror mirror : settings.getMirrors()) { for (Mirror mirror : settings.getMirrors()) {
@ -91,9 +142,9 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
return selector; return selector;
} }
private AuthenticationSelector createAuthenticationSelector(Settings settings) { private AuthenticationSelector createAuthenticationSelector(List<Server> servers) {
DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector(); DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector();
for (Server server : settings.getServers()) { for (Server server : servers) {
AuthenticationBuilder auth = new AuthenticationBuilder(); AuthenticationBuilder auth = new AuthenticationBuilder();
auth.addUsername(server.getUsername()).addPassword(server.getPassword()); auth.addUsername(server.getUsername()).addPassword(server.getPassword());
auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase()); auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
@ -102,9 +153,9 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
return new ConservativeAuthenticationSelector(selector); return new ConservativeAuthenticationSelector(selector);
} }
private ProxySelector createProxySelector(Settings settings) { private ProxySelector createProxySelector(List<Proxy> proxies) {
DefaultProxySelector selector = new DefaultProxySelector(); DefaultProxySelector selector = new DefaultProxySelector();
for (Proxy proxy : settings.getProxies()) { for (Proxy proxy : proxies) {
Authentication authentication = new AuthenticationBuilder() Authentication authentication = new AuthenticationBuilder()
.addUsername(proxy.getUsername()).addPassword(proxy.getPassword()) .addUsername(proxy.getUsername()).addPassword(proxy.getPassword())
.build(); .build();
@ -114,4 +165,19 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
} }
return selector; return selector;
} }
private class SpringBootSecDispatcher extends DefaultSecDispatcher {
public SpringBootSecDispatcher() {
this._configurationFile = new File(
SettingsXmlRepositorySystemSessionAutoConfiguration.this.homeDir,
".m2/settings-security.xml").getAbsolutePath();
try {
this._cipher = new DefaultPlexusCipher();
}
catch (PlexusCipherException e) {
throw new IllegalStateException(e);
}
}
}
} }

@ -20,7 +20,11 @@ import org.apache.maven.repository.internal.MavenRepositorySystemUtils;
import org.apache.maven.settings.building.SettingsBuildingException; import org.apache.maven.settings.building.SettingsBuildingException;
import org.eclipse.aether.DefaultRepositorySystemSession; import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystem; import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.AuthenticationContext;
import org.eclipse.aether.repository.LocalRepositoryManager; import org.eclipse.aether.repository.LocalRepositoryManager;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.RemoteRepository;
import org.junit.Rule; import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.rules.ExpectedException; import org.junit.rules.ExpectedException;
@ -28,6 +32,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner; import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
/** /**
@ -49,12 +54,60 @@ public class SettingsXmlRepositorySystemSessionAutoConfigurationTests {
@Test @Test
public void basicSessionCustomization() throws SettingsBuildingException { public void basicSessionCustomization() throws SettingsBuildingException {
assertSessionCustomization("src/test/resources/maven-settings/basic");
}
@Test
public void encryptedSettingsSessionCustomization() throws SettingsBuildingException {
assertSessionCustomization("src/test/resources/maven-settings/encrypted");
}
private void assertSessionCustomization(String userHome) {
DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession(); DefaultRepositorySystemSession session = MavenRepositorySystemUtils.newSession();
new SettingsXmlRepositorySystemSessionAutoConfiguration().apply(session, new SettingsXmlRepositorySystemSessionAutoConfiguration(userHome).apply(session,
this.repositorySystem); this.repositorySystem);
assertNotNull(session.getMirrorSelector()); RemoteRepository repository = new RemoteRepository.Builder("my-server",
assertNotNull(session.getProxySelector()); "default", "http://maven.example.com").build();
assertMirrorSelectorConfiguration(session, repository);
assertProxySelectorConfiguration(session, repository);
assertAuthenticationSelectorConfiguration(session, repository);
}
private void assertProxySelectorConfiguration(DefaultRepositorySystemSession session,
RemoteRepository repository) {
Proxy proxy = session.getProxySelector().getProxy(repository);
repository = new RemoteRepository.Builder(repository).setProxy(proxy).build();
AuthenticationContext authenticationContext = AuthenticationContext.forProxy(
session, repository);
assertEquals("proxy.example.com", proxy.getHost());
assertEquals("proxyuser",
authenticationContext.get(AuthenticationContext.USERNAME));
assertEquals("somepassword",
authenticationContext.get(AuthenticationContext.PASSWORD));
}
private void assertMirrorSelectorConfiguration(
DefaultRepositorySystemSession session, RemoteRepository repository) {
RemoteRepository mirror = session.getMirrorSelector().getMirror(repository);
assertNotNull("No mirror configured for repository " + repository.getId(), mirror);
assertEquals("maven.example.com", mirror.getHost());
}
private void assertAuthenticationSelectorConfiguration(
DefaultRepositorySystemSession session, RemoteRepository repository) {
Authentication authentication = session.getAuthenticationSelector()
.getAuthentication(repository);
repository = new RemoteRepository.Builder(repository).setAuthentication(
authentication).build();
AuthenticationContext authenticationContext = AuthenticationContext
.forRepository(session, repository);
assertEquals("tester", authenticationContext.get(AuthenticationContext.USERNAME));
assertEquals("secret", authenticationContext.get(AuthenticationContext.PASSWORD));
} }
} }

@ -0,0 +1,31 @@
<settings>
<mirrors>
<mirror>
<id>my-mirror</id>
<url>http://maven.example.com/mirror</url>
<mirrorOf>my-server</mirrorOf>
</mirror>
</mirrors>
<servers>
<server>
<id>my-server</id>
<username>tester</username>
<password>secret</password>
</server>
</servers>
<proxies>
<proxy>
<id>my-proxy</id>
<active>true</active>
<protocol>http</protocol>
<host>proxy.example.com</host>
<port>8080</port>
<username>proxyuser</username>
<password>somepassword</password>
</proxy>
</proxies>
</settings>

@ -0,0 +1,3 @@
<settingsSecurity>
<master>{oAyWuFO63U8HHgiplpqtgXih0/pwcRA0d+uA+Z7TBEk=}</master>
</settingsSecurity>

@ -0,0 +1,31 @@
<settings>
<mirrors>
<mirror>
<id>my-mirror</id>
<url>http://maven.example.com/mirror</url>
<mirrorOf>my-server</mirrorOf>
</mirror>
</mirrors>
<servers>
<server>
<id>my-server</id>
<username>tester</username>
<password>{Ur5BpeQGlYUHhXsHahO/HbMBcPSFSUtN5gbWuFFPYGw=}</password>
</server>
</servers>
<proxies>
<proxy>
<id>my-proxy</id>
<active>true</active>
<protocol>http</protocol>
<host>proxy.example.com</host>
<port>8080</port>
<username>proxyuser</username>
<password>{3iRQQyaIUgQHwH8uzTvr9/52pZAjLOTWz/SlWDB7CM4=}</password>
</proxy>
</proxies>
</settings>

@ -98,6 +98,11 @@
<artifactId>plexus-archiver</artifactId> <artifactId>plexus-archiver</artifactId>
<version>2.4.4</version> <version>2.4.4</version>
</dependency> </dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-api</artifactId>
<version>1.0-alpha-33</version>
</dependency>
<dependency> <dependency>
<groupId>org.codehaus.plexus</groupId> <groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId> <artifactId>plexus-utils</artifactId>

Loading…
Cancel
Save