Add support to resolve credentials from settings.xml (#1362)

fixes #1350
This commit is contained in:
Eddú Meléndez Gonzales
2020-04-03 00:36:05 -06:00
committed by GitHub
parent 972df3d412
commit b500bc7ad9
16 changed files with 222 additions and 16 deletions

View File

@@ -29,6 +29,7 @@
|stubrunner.properties | | Map of properties that can be passed to custom {@link org.springframework.cloud.contract.stubrunner.StubDownloaderBuilder}.
|stubrunner.proxy-host | | Repository proxy host.
|stubrunner.proxy-port | | Repository proxy port.
|stubrunner.server-id | | Server value registered at `settings.xml` to get the proper credentials to authenticate against the remote repository.
|stubrunner.stream.enabled | true | Whether to enable Stub Runner integration with Spring Cloud Stream.
|stubrunner.stubs-mode | | Pick where the stubs should come from.
|stubrunner.stubs-per-consumer | false | Should only stubs for this particular consumer get registered in HTTP server stub.

View File

@@ -407,6 +407,11 @@
<artifactId>maven-resolver-provider</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>

View File

@@ -58,6 +58,11 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-embedder</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.maven.resolver</groupId>
<artifactId>maven-resolver-api</artifactId>

View File

@@ -140,7 +140,7 @@ final class AetherFactories {
return new File(user);
}
private static Settings settings() {
protected static Settings settings() {
SettingsBuilder builder = new DefaultSettingsBuilderFactory().newInstance();
SettingsBuildingRequest request = new DefaultSettingsBuildingRequest();
request.setUserSettingsFile(userSettings());

View File

@@ -29,6 +29,7 @@ import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.artifact.DefaultArtifact;
import org.eclipse.aether.repository.Authentication;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.RemoteRepository;
import org.eclipse.aether.resolution.ArtifactRequest;
@@ -37,6 +38,10 @@ import org.eclipse.aether.resolution.VersionRangeRequest;
import org.eclipse.aether.resolution.VersionRangeResolutionException;
import org.eclipse.aether.resolution.VersionRangeResult;
import org.eclipse.aether.util.repository.AuthenticationBuilder;
import shaded.org.apache.maven.settings.Server;
import shaded.org.apache.maven.settings.Settings;
import shaded.org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import shaded.org.apache.maven.settings.crypto.SettingsDecryptionRequest;
import org.springframework.cloud.contract.stubrunner.StubRunnerOptions.StubRunnerProxyOptions;
import org.springframework.cloud.contract.stubrunner.spring.StubRunnerProperties;
@@ -44,10 +49,12 @@ import org.springframework.util.StringUtils;
import static org.springframework.cloud.contract.stubrunner.AetherFactories.newRepositorySystem;
import static org.springframework.cloud.contract.stubrunner.AetherFactories.newSession;
import static org.springframework.cloud.contract.stubrunner.AetherFactories.settings;
import static org.springframework.cloud.contract.stubrunner.util.ZipCategory.unzipTo;
/**
* @author Mariusz Smykula
* @author Eddú Meléndez
*/
public class AetherStubDownloader implements StubDownloader {
@@ -74,12 +81,15 @@ public class AetherStubDownloader implements StubDownloader {
private final boolean deleteStubsAfterTest;
private final Settings settings;
public AetherStubDownloader(StubRunnerOptions stubRunnerOptions) {
this.deleteStubsAfterTest = stubRunnerOptions.isDeleteStubsAfterTest();
if (log.isDebugEnabled()) {
log.debug("Will be resolving versions for the following options: ["
+ stubRunnerOptions + "]");
}
this.settings = settings();
this.remoteRepos = remoteRepositories(stubRunnerOptions);
boolean remoteReposMissing = remoteReposMissing();
switch (stubRunnerOptions.stubsMode) {
@@ -110,9 +120,11 @@ public class AetherStubDownloader implements StubDownloader {
* @param session repository system session
*/
public AetherStubDownloader(RepositorySystem repositorySystem,
List<RemoteRepository> remoteRepositories, RepositorySystemSession session) {
List<RemoteRepository> remoteRepositories, RepositorySystemSession session,
Settings settings) {
this.deleteStubsAfterTest = true;
this.remoteRepos = remoteRepositories;
this.settings = settings;
this.repositorySystem = repositorySystem;
this.session = session;
if (remoteReposMissing()) {
@@ -147,10 +159,8 @@ public class AetherStubDownloader implements StubDownloader {
for (int i = 0; i < repos.length; i++) {
if (StringUtils.hasText(repos[i])) {
final RemoteRepository.Builder builder = new RemoteRepository.Builder(
"remote" + i, "default", repos[i])
.setAuthentication(new AuthenticationBuilder()
.addUsername(stubRunnerOptions.username)
.addPassword(stubRunnerOptions.password).build());
"remote" + i, "default", repos[i]).setAuthentication(
resolveAuthentication(stubRunnerOptions));
if (stubRunnerOptions.getProxyOptions() != null) {
final StubRunnerProxyOptions p = stubRunnerOptions.getProxyOptions();
builder.setProxy(new Proxy(null, p.getProxyHost(), p.getProxyPort()));
@@ -164,6 +174,22 @@ public class AetherStubDownloader implements StubDownloader {
return remoteRepos;
}
private Authentication resolveAuthentication(StubRunnerOptions stubRunnerOptions) {
if (StringUtils.hasText(stubRunnerOptions.serverId)) {
Server stubServer = this.settings.getServer(stubRunnerOptions.serverId);
if (stubServer != null) {
SettingsDecryptionRequest settingsDecryptionRequest = new DefaultSettingsDecryptionRequest(
stubServer);
String stubServerPassword = new MavenSettings().createSettingsDecrypter()
.decrypt(settingsDecryptionRequest).getServer().getPassword();
return new AuthenticationBuilder().addUsername(stubServer.getUsername())
.addPassword(stubServerPassword).build();
}
}
return new AuthenticationBuilder().addUsername(stubRunnerOptions.username)
.addPassword(stubRunnerOptions.password).build();
}
private File unpackedJar(String resolvedVersion, String stubsGroup,
String stubsModule, String classifier) {
try {

View File

@@ -0,0 +1,78 @@
package org.springframework.cloud.contract.stubrunner;
import java.io.File;
import java.lang.reflect.Field;
import org.sonatype.plexus.components.cipher.DefaultPlexusCipher;
import org.sonatype.plexus.components.cipher.PlexusCipherException;
import org.sonatype.plexus.components.sec.dispatcher.DefaultSecDispatcher;
import shaded.org.apache.maven.settings.crypto.DefaultSettingsDecrypter;
import shaded.org.apache.maven.settings.crypto.SettingsDecrypter;
import org.springframework.util.StringUtils;
public class MavenSettings {
private static final String MAVEN_USER_CONFIG_DIRECTORY = "maven.user.config.dir";
private final String homeDir;
public MavenSettings() {
this(userSettings());
}
private static String fromSystemPropOrEnv(String prop) {
String resolvedProp = System.getProperty(prop);
if (StringUtils.hasText(resolvedProp)) {
return resolvedProp;
}
return System.getenv(prop);
}
private static String userSettings() {
String user = fromSystemPropOrEnv(MAVEN_USER_CONFIG_DIRECTORY);
if (user == null) {
return System.getProperty("user.home");
}
return user;
}
public MavenSettings(String homeDir) {
this.homeDir = homeDir;
}
public SettingsDecrypter createSettingsDecrypter() {
SettingsDecrypter settingsDecrypter = new DefaultSettingsDecrypter();
setField(DefaultSettingsDecrypter.class, "securityDispatcher", settingsDecrypter,
new MavenSettings.SpringCloudContractSecDispatcher());
return settingsDecrypter;
}
private void setField(Class<?> sourceClass, String fieldName, Object target,
Object value) {
try {
Field field = sourceClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(target, value);
} catch (Exception ex) {
throw new IllegalStateException(
"Failed to set field '" + fieldName + "' on '" + target + "'", ex);
}
}
private class SpringCloudContractSecDispatcher extends DefaultSecDispatcher {
private static final String SECURITY_XML = "settings-security.xml";
SpringCloudContractSecDispatcher() {
File file = new File(MavenSettings.this.homeDir, SECURITY_XML);
this._configurationFile = file.getAbsolutePath();
try {
this._cipher = new DefaultPlexusCipher();
} catch (PlexusCipherException e) {
throw new IllegalStateException(e);
}
}
}
}

View File

@@ -37,6 +37,7 @@ import org.springframework.util.StringUtils;
* Use {@link StubRunnerOptionsBuilder} to build this object.
*
* @author Marcin Grzejszczak
* @author Eddú Meléndez
* @see StubRunnerOptionsBuilder
*/
public class StubRunnerOptions {
@@ -134,6 +135,11 @@ public class StubRunnerOptions {
*/
private Map<String, String> properties;
/**
*
*/
final String serverId;
StubRunnerOptions(Integer minPortValue, Integer maxPortValue,
Resource stubRepositoryRoot, StubRunnerProperties.StubsMode stubsMode,
String stubsClassifier, Collection<StubConfiguration> dependencies,
@@ -142,7 +148,8 @@ public class StubRunnerOptions {
boolean stubsPerConsumer, String consumerName, String mappingsOutputFolder,
boolean deleteStubsAfterTest, boolean generateStubs, boolean failOnNoStubs,
Map<String, String> properties,
Class<? extends HttpServerStubConfigurer> httpServerStubConfigurer) {
Class<? extends HttpServerStubConfigurer> httpServerStubConfigurer,
String serverId) {
this.minPortValue = minPortValue;
this.maxPortValue = maxPortValue;
this.stubRepositoryRoot = stubRepositoryRoot;
@@ -162,6 +169,7 @@ public class StubRunnerOptions {
this.failOnNoStubs = failOnNoStubs;
this.properties = properties;
this.httpServerStubConfigurer = httpServerStubConfigurer;
this.serverId = serverId;
}
public static StubRunnerOptions fromSystemProps() {
@@ -188,7 +196,8 @@ public class StubRunnerOptions {
System.getProperty("stubrunner.generate-stubs", "false")))
.withFailOnNoStubs(Boolean.parseBoolean(
System.getProperty("stubrunner.fail-on-no-stubs", "false")))
.withProperties(stubRunnerProps());
.withProperties(stubRunnerProps())
.withServerId(System.getProperty("stubrunner.server-id", ""));
builder = httpStubConfigurer(builder);
String proxyHost = System.getProperty("stubrunner.proxy.host");
if (proxyHost != null) {
@@ -354,6 +363,10 @@ public class StubRunnerOptions {
this.properties = properties;
}
public String getServerId() {
return this.serverId;
}
public Class<? extends HttpServerStubConfigurer> getHttpServerStubConfigurer() {
return this.httpServerStubConfigurer;
}
@@ -370,7 +383,7 @@ public class StubRunnerOptions {
+ ", stubRunnerProxyOptions='" + this.stubRunnerProxyOptions
+ "', stubsPerConsumer='" + this.stubsPerConsumer + '\''
+ ", httpServerStubConfigurer='" + this.httpServerStubConfigurer + '\''
+ '}';
+ ", serverId='" + this.serverId + '\'' + '}';
}
private String obfuscate(String string) {

View File

@@ -34,6 +34,7 @@ import org.springframework.util.StringUtils;
* A builder object for {@link StubRunnerOptions}.
*
* @author Marcin Grzejszczak
* @author Eddú Meléndez
*/
public class StubRunnerOptionsBuilder {
@@ -77,6 +78,8 @@ public class StubRunnerOptionsBuilder {
private Class httpServerStubConfigurer = HttpServerStubConfigurer.NoOpHttpServerStubConfigurer.class;
private String serverId;
public StubRunnerOptionsBuilder() {
}
@@ -208,6 +211,7 @@ public class StubRunnerOptionsBuilder {
this.failOnNoStubs = options.isFailOnNoStubs();
this.properties = options.getProperties();
this.httpServerStubConfigurer = options.getHttpServerStubConfigurer();
this.serverId = options.getServerId();
return this;
}
@@ -244,6 +248,11 @@ public class StubRunnerOptionsBuilder {
return this;
}
public StubRunnerOptionsBuilder withServerId(String serverId) {
this.serverId = serverId;
return this;
}
public StubRunnerOptions build() {
return new StubRunnerOptions(this.minPortValue, this.maxPortValue,
this.stubRepositoryRoot, this.stubsMode, this.stubsClassifier,
@@ -251,7 +260,7 @@ public class StubRunnerOptionsBuilder {
this.password, this.stubRunnerProxyOptions, this.stubsPerConsumer,
this.consumerName, this.mappingsOutputFolder, this.deleteStubsAfterTest,
this.generateStubs, this.failOnNoStubs, this.properties,
this.httpServerStubConfigurer);
this.httpServerStubConfigurer, this.serverId);
}
private Collection<StubConfiguration> buildDependencies() {

View File

@@ -50,6 +50,7 @@ import org.springframework.util.StringUtils;
* stub.
*
* @author Marcin Grzejszczak
* @author Eddú Meléndez
*/
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(StubRunnerProperties.class)
@@ -124,7 +125,8 @@ public class StubRunnerConfiguration {
.withGenerateStubs(Boolean
.parseBoolean(resolvePlaceholder(this.props.isGenerateStubs())))
.withProperties(this.props.getProperties())
.withHttpServerStubConfigurer(this.props.getHttpServerStubConfigurer());
.withHttpServerStubConfigurer(this.props.getHttpServerStubConfigurer())
.withServerId(resolvePlaceholder(this.props.getServerId()));
}
private String[] resolvePlaceholder(String[] string) {

View File

@@ -131,6 +131,11 @@ public class StubRunnerProperties {
*/
private Class<? extends HttpServerStubConfigurer> httpServerStubConfigurer = NoOpHttpServerStubConfigurer.class;
/**
*
*/
private String serverId;
public int getMinPort() {
return this.minPort;
}
@@ -282,6 +287,14 @@ public class StubRunnerProperties {
this.httpServerStubConfigurer = httpServerStubConfigurer;
}
public String getServerId() {
return serverId;
}
public void setServerId(String serverId) {
this.serverId = serverId;
}
@Override
public String toString() {
return "StubRunnerProperties{" + "minPort=" + this.minPort + ", maxPort="

View File

@@ -96,4 +96,31 @@ class AetherStubDownloaderSpec extends Specification {
jar != null
repositorySystemSession.getLocalRepository().getBasedir().getAbsolutePath().endsWith(m2repoFolder)
}
@RestoreSystemProperties
def 'Should return credentials from settings.xml'() {
given:
File settings = new File(AetherStubDownloaderSpec.getResource("/.m2/settings.xml").getFile())
System.setProperty("org.apache.maven.user-settings", settings.getAbsolutePath())
and:
File configDir = new File(AetherStubDownloaderSpec.getResource("/.m2").getFile())
System.setProperty("maven.user.config.dir", configDir.getAbsolutePath())
and:
StubRunnerOptions stubRunnerOptions = new StubRunnerOptionsBuilder()
.withStubsMode(StubRunnerProperties.StubsMode.REMOTE)
.withStubRepositoryRoot("file://" + folder.newFolder().absolutePath)
.withServerId("my-server")
.build()
AetherStubDownloader aetherStubDownloader = new AetherStubDownloader(stubRunnerOptions)
when:
def jar = aetherStubDownloader.downloadAndUnpackStubJar(
new StubConfiguration("org.springframework.cloud.contract.verifier.stubs",
"bootService", "0.0.1-SNAPSHOT"))
then:
jar != null
}
}

View File

@@ -231,7 +231,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
given:
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, new FileSystemResource("root"), StubRunnerProperties.StubsMode.LOCAL,
"classifier", [new StubConfiguration("a:b:c")], [(new StubConfiguration("a:b:c")): 3], "foo", "bar",
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, false, [foo: "bar"], Foo))
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, false, [foo: "bar"], Foo, "server"))
builder.withStubs("foo:bar:baz")
when:
StubRunnerOptions options = builder.build()
@@ -255,6 +255,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
options.failOnNoStubs == false
options.properties == [foo: "bar"]
options.httpServerStubConfigurer == Foo
options.serverId == "server"
}
def shouldNotPrintUsernameAndPassword() {
@@ -262,7 +263,7 @@ class StubRunnerOptionsBuilderSpec extends Specification {
StubRunnerOptionsBuilder builder = builder.withOptions(new StubRunnerOptions(1, 2, new FileSystemResource("root"),
StubRunnerProperties.StubsMode.CLASSPATH, "classifier",
[new StubConfiguration("a:b:c")], [(new StubConfiguration("a:b:c")): 3], "username123", "password123",
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, true, [:], Foo))
new StubRunnerOptions.StubRunnerProxyOptions("host", 4), true, "consumer", "folder", false, true, true, [:], Foo, "server"))
builder.withStubs("foo:bar:baz")
when:
String options = builder.build().toString()

View File

@@ -0,0 +1,3 @@
<settingsSecurity>
<master>{HUkK5nexvfwH3iCznPbiCNTL7S1owi59hR79vLMH2wc=}</master>
</settingsSecurity>

View File

@@ -0,0 +1,9 @@
<settings>
<servers>
<server>
<id>my-server</id>
<username>admin</username>
<password>{ha7QXbuAf9wH5uVeYJGWg+SC8fdkufPVfdtpTK8Yk3E=}</password>
</server>
</servers>
</settings>

View File

@@ -43,6 +43,7 @@ import org.springframework.util.StringUtils;
* Mojo for running stubs.
*
* @author Mariusz Smykula
* @author Eddú Meléndez
*/
@Mojo(name = "run", requiresProject = false,
requiresDependencyResolution = ResolutionScope.RUNTIME)
@@ -117,6 +118,12 @@ public class RunMojo extends AbstractMojo {
@Parameter(defaultValue = "${session}", readonly = true)
private MavenSession mavenSession;
/**
*
*/
@Parameter(property = "spring.cloud.contract.verifier.server-id")
private String serverId;
@Inject
public RunMojo(LocalStubRunner localStubRunner, RemoteStubRunner remoteStubRunner) {
this.localStubRunner = localStubRunner;
@@ -143,7 +150,8 @@ public class RunMojo extends AbstractMojo {
}
else {
StubRunnerOptions options = optionsBuilder.withStubs(this.stubs)
.withMinMaxPort(this.minPort, this.maxPort).build();
.withMinMaxPort(this.minPort, this.maxPort)
.withServerId(this.serverId).build();
batchStubRunner = this.remoteStubRunner.run(options, this.repoSession);
}
pressAnyKeyToContinue();

View File

@@ -25,6 +25,7 @@ import org.apache.commons.logging.LogFactory;
import org.apache.maven.project.MavenProject;
import org.eclipse.aether.RepositorySystem;
import org.eclipse.aether.RepositorySystemSession;
import shaded.org.apache.maven.settings.Settings;
import org.springframework.cloud.contract.stubrunner.AetherStubDownloader;
import org.springframework.cloud.contract.stubrunner.StubDownloader;
@@ -37,6 +38,7 @@ import org.springframework.core.io.ResourceLoader;
* Builds {@link StubDownloaderBuilder} for a Maven project.
*
* @author Mariusz Smykula
* @author Eddú Meléndez
*/
@Named
@Singleton
@@ -48,11 +50,14 @@ public class AetherStubDownloaderFactory {
private final RepositorySystem repoSystem;
private final Settings settings;
@Inject
public AetherStubDownloaderFactory(RepositorySystem repoSystem,
MavenProject project) {
MavenProject project, Settings settings) {
this.repoSystem = repoSystem;
this.project = project;
this.settings = settings;
}
public StubDownloaderBuilder build(final RepositorySystemSession repoSession) {
@@ -65,7 +70,8 @@ public class AetherStubDownloaderFactory {
AetherStubDownloaderFactory.this.repoSystem,
AetherStubDownloaderFactory.this.project
.getRemoteProjectRepositories(),
repoSession);
repoSession,
AetherStubDownloaderFactory.this.settings);
}
@Override