diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java
index 5777978f2d3..b8b34a50690 100644
--- a/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java
+++ b/rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java
@@ -85,7 +85,7 @@ private Path dependencyPath(ResolvedDependency dependency) {
return resolvedPath.resolve(dependency.getArtifactId() + "-" +
(dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) +
- (dependency.getRequested().getClassifier() == null ? "" : dependency.getRequested().getClassifier()) +
+ (dependency.getRequested().getClassifier() == null ? "" : "-" + dependency.getRequested().getClassifier()) +
".jar");
}
}
diff --git a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java
index b675c991d7c..57a971d708e 100644
--- a/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java
+++ b/rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java
@@ -109,16 +109,37 @@ public MavenArtifactDownloader(MavenArtifactCache mavenArtifactCache,
} else if ("file".equals(URI.create(uri).getScheme())) {
bodyStream = Files.newInputStream(Paths.get(URI.create(uri)));
} else {
- HttpSender.Request.Builder request = applyAuthentication(dependency.getRepository(), httpSender.get(uri));
- try (HttpSender.Response response = Failsafe.with(retryPolicy).get(() -> httpSender.send(request.build()));
- InputStream body = response.getBody()) {
- if (!response.isSuccessful() || body == null) {
- onError.accept(new MavenDownloadingException(String.format("Unable to download dependency %s:%s:%s from %s. Response was %d",
- dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), uri, response.getCode()), null,
+ try {
+ // Try anonymously first, mirroring Apache Maven's DeferredCredentialsProvider behavior
+ byte[] responseBytes = null;
+ int responseCode;
+ try (HttpSender.Response response = Failsafe.with(retryPolicy).get(() -> httpSender.send(httpSender.get(uri).build()));
+ InputStream body = response.getBody()) {
+ responseCode = response.getCode();
+ if (response.isSuccessful() && body != null) {
+ responseBytes = readAllBytes(body);
+ }
+ }
+ // Retry with credentials if the anonymous request failed with a 4xx and we have credentials
+ if (responseBytes == null && responseCode >= 400 && responseCode < 500 && hasCredentials(dependency.getRepository())) {
+ HttpSender.Request.Builder request = applyAuthentication(dependency.getRepository(), httpSender.get(uri));
+ try (HttpSender.Response response = Failsafe.with(retryPolicy).get(() -> httpSender.send(request.build()));
+ InputStream body = response.getBody()) {
+ responseCode = response.getCode();
+ if (response.isSuccessful() && body != null) {
+ responseBytes = readAllBytes(body);
+ }
+ }
+ }
+ if (responseBytes == null) {
+ onError.accept(new MavenDownloadingException(String.format("Unable to download dependency %s:%s:%s%s from %s. Response was %d",
+ dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(),
+ dependency.getClassifier() == null ? "" : ":" + dependency.getClassifier(),
+ uri, responseCode), null,
dependency.getRequested().getGav()));
return null;
}
- bodyStream = new ByteArrayInputStream(readAllBytes(body));
+ bodyStream = new ByteArrayInputStream(responseBytes);
} catch (Throwable t) {
Throwable cause = t instanceof FailsafeException && t.getCause() != null ? t.getCause() : t;
throw new MavenDownloadingException("Unable to download dependency", cause,
@@ -130,14 +151,27 @@ public MavenArtifactDownloader(MavenArtifactCache mavenArtifactCache,
}
private HttpSender.Request.Builder applyAuthentication(MavenRepository repository, HttpSender.Request.Builder request) {
+ MavenSettings.Server authInfo = serverIdToServer.get(repository.getId());
+ if (authInfo != null && authInfo.getConfiguration() != null && authInfo.getConfiguration().getHttpHeaders() != null) {
+ for (MavenSettings.HttpHeader header : authInfo.getConfiguration().getHttpHeaders()) {
+ request.withHeader(header.getName(), header.getValue());
+ }
+ }
+ String[] credentials = resolveCredentials(repository);
+ if (credentials != null) {
+ return request.withBasicAuthentication(credentials[0], credentials[1]);
+ }
+ return request;
+ }
+
+ private boolean hasCredentials(MavenRepository repository) {
+ return resolveCredentials(repository) != null;
+ }
+
+ private String @Nullable [] resolveCredentials(MavenRepository repository) {
String username, password;
MavenSettings.Server authInfo = serverIdToServer.get(repository.getId());
if (authInfo != null) {
- if (authInfo.getConfiguration() != null && authInfo.getConfiguration().getHttpHeaders() != null) {
- for (MavenSettings.HttpHeader header : authInfo.getConfiguration().getHttpHeaders()) {
- request.withHeader(header.getName(), header.getValue());
- }
- }
username = authInfo.getUsername();
password = authInfo.getPassword();
} else {
@@ -146,8 +180,8 @@ private HttpSender.Request.Builder applyAuthentication(MavenRepository repositor
}
if (username != null && !username.contains("${") &&
password != null && !password.contains("${")) {
- return request.withBasicAuthentication(username, password);
+ return new String[]{username, password};
}
- return request;
+ return null;
}
}
diff --git a/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java b/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java
index a501181570f..69dbd0ff0ce 100644
--- a/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java
+++ b/rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java
@@ -133,7 +133,7 @@ void downloadDependenciesWithClassifier(@TempDir Path tempDir) {
}
@Test
- void fallsBackToAnonymousWhenCredentialsRejected(@TempDir Path tempDir) throws Exception {
+ void publicArtifactsResolveAnonymouslyEvenWhenCredentialsAreInvalid(@TempDir Path tempDir) throws Exception {
byte[] jarBytes = {0x50, 0x4B, 0x03, 0x04}; // minimal ZIP magic bytes
try (MockWebServer mockRepo = new MockWebServer()) {
@@ -141,7 +141,7 @@ void fallsBackToAnonymousWhenCredentialsRejected(@TempDir Path tempDir) throws E
@Override
public MockResponse dispatch(RecordedRequest request) {
if (request.getHeader("Authorization") != null) {
- return new MockResponse().setResponseCode(403); // Throw if used; it should not be called at all
+ return new MockResponse().setResponseCode(401);
}
return new MockResponse().setResponseCode(200)
.setBody(new okio.Buffer().write(jarBytes));
@@ -158,8 +158,8 @@ public MockResponse dispatch(RecordedRequest request) {
mock-repo
- ${placeholder}
- ${placeholder}
+ bad-user
+ bad-password
@@ -184,6 +184,64 @@ public MockResponse dispatch(RecordedRequest request) {
assertThat(artifact).isNotNull();
assertThat(error.get()).isNull();
assertThat(mockRepo.getRequestCount()).isEqualTo(1);
+ assertThat(mockRepo.takeRequest().getHeader("Authorization")).isNull();
+ }
+ }
+
+ @Test
+ void retriesWithCredentialsWhenAnonymousReturns401(@TempDir Path tempDir) throws Exception {
+ byte[] jarBytes = {0x50, 0x4B, 0x03, 0x04};
+
+ try (MockWebServer mockRepo = new MockWebServer()) {
+ mockRepo.setDispatcher(new Dispatcher() {
+ @Override
+ public MockResponse dispatch(RecordedRequest request) {
+ if (request.getHeader("Authorization") == null) {
+ return new MockResponse().setResponseCode(401);
+ }
+ return new MockResponse().setResponseCode(200)
+ .setBody(new okio.Buffer().write(jarBytes));
+ }
+ });
+ mockRepo.start();
+
+ String repoUrl = "http://" + mockRepo.getHostName() + ":" + mockRepo.getPort();
+ MavenSettings settings = MavenSettings.parse(new Parser.Input(
+ Path.of("settings.xml"), () -> new ByteArrayInputStream(
+ //language=xml
+ """
+
+
+
+ mock-repo
+ good-user
+ good-password
+
+
+
+ """.getBytes())), new InMemoryExecutionContext());
+
+ MavenArtifactCache artifactCache = new LocalMavenArtifactCache(tempDir);
+ AtomicReference error = new AtomicReference<>();
+ MavenArtifactDownloader downloader = new MavenArtifactDownloader(
+ artifactCache, settings, error::set);
+
+ MavenRepository repo = new MavenRepository(
+ "mock-repo", repoUrl, "true", "false", true, null, null, null, false);
+ GroupArtifactVersion gav = new GroupArtifactVersion("com.example", "test-lib", "1.0.0");
+ ResolvedDependency dep = ResolvedDependency.builder()
+ .repository(repo)
+ .gav(new ResolvedGroupArtifactVersion(repoUrl, gav.getGroupId(), gav.getArtifactId(), gav.getVersion(), null))
+ .requested(Dependency.builder().gav(gav).build())
+ .build();
+
+ Path artifact = downloader.downloadArtifact(dep);
+
+ assertThat(artifact).isNotNull();
+ assertThat(error.get()).isNull();
+ assertThat(mockRepo.getRequestCount()).isEqualTo(2);
+ assertThat(mockRepo.takeRequest().getHeader("Authorization")).isNull();
+ assertThat(mockRepo.takeRequest().getHeader("Authorization")).isNotNull();
}
}
}