Commit b8858bdb authored by Andy Wilkinson's avatar Andy Wilkinson

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
parent c5820d87
......@@ -67,6 +67,16 @@
<groupId>org.apache.maven</groupId>
<artifactId>maven-settings-builder</artifactId>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-api</artifactId>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.eclipse.aether</groupId>
<artifactId>aether-api</artifactId>
......
......@@ -17,6 +17,8 @@
package org.springframework.boot.cli.compiler.grape;
import java.io.File;
import java.lang.reflect.Field;
import java.util.List;
import org.apache.maven.settings.Mirror;
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.SettingsBuildingException;
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.RepositorySystem;
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.DefaultMirrorSelector;
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
......@@ -48,18 +57,34 @@ import org.eclipse.aether.util.repository.DefaultProxySelector;
public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
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
public void apply(DefaultRepositorySystemSession session,
RepositorySystem repositorySystem) {
Settings settings = loadSettings();
SettingsDecryptionResult decryptionResult = decryptSettings(settings);
if (!decryptionResult.getProblems().isEmpty()) {
throw new IllegalStateException("Settings decryption failed: "
+ decryptionResult.getProblems());
}
session.setOffline(settings.isOffline());
session.setMirrorSelector(createMirrorSelector(settings));
session.setAuthenticationSelector(createAuthenticationSelector(settings));
session.setProxySelector(createProxySelector(settings));
session.setAuthenticationSelector(createAuthenticationSelector(decryptionResult
.getServers()));
session.setProxySelector(createProxySelector(decryptionResult.getProxies()));
String localRepository = settings.getLocalRepository();
if (localRepository != null) {
......@@ -69,7 +94,7 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
}
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();
request.setUserSettingsFile(settingsFile);
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) {
DefaultMirrorSelector selector = new DefaultMirrorSelector();
for (Mirror mirror : settings.getMirrors()) {
......@@ -91,9 +142,9 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
return selector;
}
private AuthenticationSelector createAuthenticationSelector(Settings settings) {
private AuthenticationSelector createAuthenticationSelector(List<Server> servers) {
DefaultAuthenticationSelector selector = new DefaultAuthenticationSelector();
for (Server server : settings.getServers()) {
for (Server server : servers) {
AuthenticationBuilder auth = new AuthenticationBuilder();
auth.addUsername(server.getUsername()).addPassword(server.getPassword());
auth.addPrivateKey(server.getPrivateKey(), server.getPassphrase());
......@@ -102,9 +153,9 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
return new ConservativeAuthenticationSelector(selector);
}
private ProxySelector createProxySelector(Settings settings) {
private ProxySelector createProxySelector(List<Proxy> proxies) {
DefaultProxySelector selector = new DefaultProxySelector();
for (Proxy proxy : settings.getProxies()) {
for (Proxy proxy : proxies) {
Authentication authentication = new AuthenticationBuilder()
.addUsername(proxy.getUsername()).addPassword(proxy.getPassword())
.build();
......@@ -114,4 +165,19 @@ public class SettingsXmlRepositorySystemSessionAutoConfiguration implements
}
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.eclipse.aether.DefaultRepositorySystemSession;
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.Proxy;
import org.eclipse.aether.repository.RemoteRepository;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
......@@ -28,6 +32,7 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
/**
......@@ -49,12 +54,60 @@ public class SettingsXmlRepositorySystemSessionAutoConfigurationTests {
@Test
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();
new SettingsXmlRepositorySystemSessionAutoConfiguration().apply(session,
new SettingsXmlRepositorySystemSessionAutoConfiguration(userHome).apply(session,
this.repositorySystem);
assertNotNull(session.getMirrorSelector());
assertNotNull(session.getProxySelector());
RemoteRepository repository = new RemoteRepository.Builder("my-server",
"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));
}
}
<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>
\ No newline at end of file
<settingsSecurity>
<master>{oAyWuFO63U8HHgiplpqtgXih0/pwcRA0d+uA+Z7TBEk=}</master>
</settingsSecurity>
\ No newline at end of file
<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>
\ No newline at end of file
......@@ -98,6 +98,11 @@
<artifactId>plexus-archiver</artifactId>
<version>2.4.4</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-api</artifactId>
<version>1.0-alpha-33</version>
</dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment