From 2045e0e2f76027672011e8cacc00555479713cb5 Mon Sep 17 00:00:00 2001 From: Denis Rosca Date: Thu, 29 Jun 2023 19:49:51 +0300 Subject: [PATCH] Fix basic HTTP authentication when resolving dependencies (#1838) Fixes https://github.com/awslabs/smithy/issues/1837 Replace `MavenAuth` with existing aether builder to configure the repository auth context. Modify the auth test to use a mock server to actually run the authentication flow. --- smithy-cli/build.gradle | 2 + .../amazon/smithy/cli/MavenResolverTest.java | 50 ++++++++++++--- .../cli/projects/maven-auth/smithy-build.json | 3 +- .../dependencies/MavenDependencyResolver.java | 61 +++---------------- 4 files changed, 54 insertions(+), 62 deletions(-) diff --git a/smithy-cli/build.gradle b/smithy-cli/build.gradle index dcf31372a6e..df4bd77d0ca 100644 --- a/smithy-cli/build.gradle +++ b/smithy-cli/build.gradle @@ -55,6 +55,8 @@ dependencies { implementation "org.apache.maven.resolver:maven-resolver-transport-file:1.9.2" implementation "org.apache.maven.resolver:maven-resolver-transport-http:1.9.2" implementation "org.slf4j:slf4j-jdk14:1.7.36" // Route slf4j used by Maven through JUL like the rest of Smithy. + + testImplementation "org.mock-server:mockserver-netty:3.10.8" } // ------ Shade Maven dependency resolvers into the JAR. ------- diff --git a/smithy-cli/src/it/java/software/amazon/smithy/cli/MavenResolverTest.java b/smithy-cli/src/it/java/software/amazon/smithy/cli/MavenResolverTest.java index a395a5ee4f7..37bc7f8a707 100644 --- a/smithy-cli/src/it/java/software/amazon/smithy/cli/MavenResolverTest.java +++ b/smithy-cli/src/it/java/software/amazon/smithy/cli/MavenResolverTest.java @@ -5,6 +5,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; +import static org.mockserver.integration.ClientAndServer.startClientAndServer; import java.io.File; import java.io.IOException; @@ -12,6 +13,9 @@ import java.nio.file.Files; import java.util.Collections; import org.junit.jupiter.api.Test; +import org.mockserver.integration.ClientAndServer; +import org.mockserver.model.HttpRequest; +import org.mockserver.model.HttpResponse; import software.amazon.smithy.model.node.Node; import software.amazon.smithy.model.node.ObjectNode; import software.amazon.smithy.utils.ListUtils; @@ -61,14 +65,46 @@ public void failsWhenBadVersionRequested() { }); } - // TODO: This test could be better and actually test that auth works somehow. @Test public void usesCustomRepoWithAuth() { - IntegUtils.runWithEmptyCache("maven-auth", ListUtils.of("validate", "--debug"), - Collections.emptyMap(), result -> { - assertThat(result.getExitCode(), equalTo(1)); - assertThat(result.getOutput(), containsString("with xxx=****")); - }); + ClientAndServer mockServer = null; + try { + mockServer = startClientAndServer(1234); + mockServer.when( + HttpRequest + .request() + .withMethod("GET") + .withHeader("Authorization", "Basic eHh4Onl5eQ==") + .withPath("/maven/not/there/software/amazon/smithy/smithy-aws-iam-traits/.*\\.jar") + ).respond( + HttpResponse + .response() + .withStatusCode(200) + .withBody("FAKE JAR CONTENT") + ); + mockServer.when( + HttpRequest + .request() + .withMethod("GET") + .withPath("/maven/not/there/software/amazon/smithy/smithy-aws-iam-traits/.*") + ).respond( + HttpResponse + .response() + .withStatusCode(401) + .withHeader("WWW-Authenticate", "Basic realm=\"Artifactory Realm\"") + ); + + IntegUtils.runWithEmptyCache("maven-auth", ListUtils.of("validate", "--debug"), + Collections.emptyMap(), result -> { + assertThat(result.getExitCode(), equalTo(1)); + assertThat(result.getOutput(), containsString("HttpAuthenticator - Selected authentication options: [BASIC [complete=true]]")); + assertThat(result.getOutput(), containsString("HttpAuthenticator - Authentication succeeded")); + }); + } finally { + if(mockServer!=null) { + mockServer.stop(); + } + } } @Test @@ -193,7 +229,7 @@ public void setSetMavenRepoWithEnvUsingAuth() { ListUtils.of("validate", "--debug", "model"), MapUtils.of(EnvironmentVariable.SMITHY_MAVEN_REPOS.toString(), repo), result -> { - assertThat(result.getOutput(), containsString("with xxx=****")); + assertThat(result.getOutput(), containsString("username=xxx, password=***")); assertThat(result.getExitCode(), equalTo(1)); }); } diff --git a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/maven-auth/smithy-build.json b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/maven-auth/smithy-build.json index 47f6fcc8a16..e2c189f6590 100644 --- a/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/maven-auth/smithy-build.json +++ b/smithy-cli/src/it/resources/software/amazon/smithy/cli/projects/maven-auth/smithy-build.json @@ -3,7 +3,8 @@ "maven": { "repositories": [ { - "url": "https://localhost:1234/maven/not/there", + // Use HTTP instead of HTTPS because we're running a mock server during tests + "url": "http://localhost:1234/maven/not/there", "httpCredentials": "xxx:yyy" } ], diff --git a/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java b/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java index 95c49e07739..08061dce90b 100644 --- a/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java +++ b/smithy-cli/src/main/java/software/amazon/smithy/cli/dependencies/MavenDependencyResolver.java @@ -23,8 +23,6 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Objects; import java.util.logging.Logger; import org.apache.maven.repository.internal.MavenRepositorySystemUtils; import org.eclipse.aether.DefaultRepositorySystemSession; @@ -36,9 +34,6 @@ import org.eclipse.aether.graph.Dependency; import org.eclipse.aether.graph.DependencyFilter; import org.eclipse.aether.impl.DefaultServiceLocator; -import org.eclipse.aether.repository.Authentication; -import org.eclipse.aether.repository.AuthenticationContext; -import org.eclipse.aether.repository.AuthenticationDigest; import org.eclipse.aether.repository.LocalRepository; import org.eclipse.aether.repository.RemoteRepository; import org.eclipse.aether.resolution.ArtifactResult; @@ -49,8 +44,8 @@ import org.eclipse.aether.transport.file.FileTransporterFactory; import org.eclipse.aether.transport.http.HttpTransporterFactory; import org.eclipse.aether.util.filter.DependencyFilterUtils; +import org.eclipse.aether.util.repository.AuthenticationBuilder; import software.amazon.smithy.build.model.MavenRepository; -import software.amazon.smithy.utils.StringUtils; /** * Resolves Maven dependencies for the Smithy CLI using Maven resolvers. @@ -121,7 +116,12 @@ private void addUserInfoAuth(URI uri, String userInfo, RemoteRepository.Builder if (parts.length != 2) { throw new DependencyResolverException("Invalid credentials provided for " + uri); } - builder.setAuthentication(new MavenAuth(parts[0], parts[1])); + builder.setAuthentication( + new AuthenticationBuilder() + .addUsername(parts[0]) + .addPassword(parts[1]) + .build() + ); } @Override @@ -190,51 +190,4 @@ private List resolveMavenArtifacts() { throw new DependencyResolverException(e); } } - - /** - * Based on Maven's StringAuthentication. There doesn't appear to be another way to do this. - */ - private static final class MavenAuth implements Authentication { - private final String key; - private final String value; - - private MavenAuth(String key, String value) { - if (StringUtils.isEmpty(key)) { - throw new IllegalArgumentException("Authentication key must be provided"); - } - this.key = key; - this.value = value; - } - - @Override - public void fill(AuthenticationContext context, String key, Map data) { - context.put(this.key, value); - } - - @Override - public void digest(AuthenticationDigest digest) { - digest.update(key, value); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } else if (obj == null || !getClass().equals(obj.getClass())) { - return false; - } - MavenAuth that = (MavenAuth) obj; - return Objects.equals(key, that.key) && Objects.equals(value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(key, value); - } - - @Override - public String toString() { - return key + "=****"; - } - } }