Skip to content

Commit 03b7ac4

Browse files
committed
Fall back to anonymous download on 4xx with Maven credentials
When Maven settings.xml credentials are rejected by the remote repository (401/403), retry the JAR download without authentication. Mirrors `MavenPomDownloader.requestAsAuthenticatedOrAnonymous()` and Apache Maven's behavior, so anonymous-accessible artifacts resolve even when configured credentials are invalid. Also fixes two nits observed during troubleshooting: - local cache filename was missing the hyphen before the classifier (`foo-1.0.0recipes.jar` → `foo-1.0.0-recipes.jar`) - download error message omitted the classifier
1 parent 5cac27f commit 03b7ac4

3 files changed

Lines changed: 104 additions & 14 deletions

File tree

rewrite-maven/src/main/java/org/openrewrite/maven/cache/LocalMavenArtifactCache.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ private Path dependencyPath(ResolvedDependency dependency) {
8585

8686
return resolvedPath.resolve(dependency.getArtifactId() + "-" +
8787
(dependency.getDatedSnapshotVersion() == null ? dependency.getVersion() : dependency.getDatedSnapshotVersion()) +
88-
(dependency.getRequested().getClassifier() == null ? "" : dependency.getRequested().getClassifier()) +
88+
(dependency.getRequested().getClassifier() == null ? "" : "-" + dependency.getRequested().getClassifier()) +
8989
".jar");
9090
}
9191
}

rewrite-maven/src/main/java/org/openrewrite/maven/utilities/MavenArtifactDownloader.java

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,35 @@ public MavenArtifactDownloader(MavenArtifactCache mavenArtifactCache,
110110
bodyStream = Files.newInputStream(Paths.get(URI.create(uri)));
111111
} else {
112112
HttpSender.Request.Builder request = applyAuthentication(dependency.getRepository(), httpSender.get(uri));
113-
try (HttpSender.Response response = Failsafe.with(retryPolicy).get(() -> httpSender.send(request.build()));
114-
InputStream body = response.getBody()) {
115-
if (!response.isSuccessful() || body == null) {
116-
onError.accept(new MavenDownloadingException(String.format("Unable to download dependency %s:%s:%s from %s. Response was %d",
117-
dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(), uri, response.getCode()), null,
113+
try {
114+
byte[] responseBytes = null;
115+
int responseCode;
116+
try (HttpSender.Response response = Failsafe.with(retryPolicy).get(() -> httpSender.send(request.build()));
117+
InputStream body = response.getBody()) {
118+
responseCode = response.getCode();
119+
if (response.isSuccessful() && body != null) {
120+
responseBytes = readAllBytes(body);
121+
}
122+
}
123+
// Fall back to anonymous if authenticated request fails with a 4xx client error
124+
if (responseBytes == null && hasCredentials(dependency.getRepository()) && responseCode >= 400 && responseCode < 500) {
125+
try (HttpSender.Response response = Failsafe.with(retryPolicy).get(() -> httpSender.send(httpSender.get(uri).build()));
126+
InputStream body = response.getBody()) {
127+
responseCode = response.getCode();
128+
if (response.isSuccessful() && body != null) {
129+
responseBytes = readAllBytes(body);
130+
}
131+
}
132+
}
133+
if (responseBytes == null) {
134+
onError.accept(new MavenDownloadingException(String.format("Unable to download dependency %s:%s:%s%s from %s. Response was %d",
135+
dependency.getGroupId(), dependency.getArtifactId(), dependency.getVersion(),
136+
dependency.getClassifier() == null ? "" : ":" + dependency.getClassifier(),
137+
uri, responseCode), null,
118138
dependency.getRequested().getGav()));
119139
return null;
120140
}
121-
bodyStream = new ByteArrayInputStream(readAllBytes(body));
141+
bodyStream = new ByteArrayInputStream(responseBytes);
122142
} catch (Throwable t) {
123143
Throwable cause = t instanceof FailsafeException && t.getCause() != null ? t.getCause() : t;
124144
throw new MavenDownloadingException("Unable to download dependency", cause,
@@ -130,14 +150,27 @@ public MavenArtifactDownloader(MavenArtifactCache mavenArtifactCache,
130150
}
131151

132152
private HttpSender.Request.Builder applyAuthentication(MavenRepository repository, HttpSender.Request.Builder request) {
153+
MavenSettings.Server authInfo = serverIdToServer.get(repository.getId());
154+
if (authInfo != null && authInfo.getConfiguration() != null && authInfo.getConfiguration().getHttpHeaders() != null) {
155+
for (MavenSettings.HttpHeader header : authInfo.getConfiguration().getHttpHeaders()) {
156+
request.withHeader(header.getName(), header.getValue());
157+
}
158+
}
159+
String[] credentials = resolveCredentials(repository);
160+
if (credentials != null) {
161+
return request.withBasicAuthentication(credentials[0], credentials[1]);
162+
}
163+
return request;
164+
}
165+
166+
private boolean hasCredentials(MavenRepository repository) {
167+
return resolveCredentials(repository) != null;
168+
}
169+
170+
private String @Nullable [] resolveCredentials(MavenRepository repository) {
133171
String username, password;
134172
MavenSettings.Server authInfo = serverIdToServer.get(repository.getId());
135173
if (authInfo != null) {
136-
if (authInfo.getConfiguration() != null && authInfo.getConfiguration().getHttpHeaders() != null) {
137-
for (MavenSettings.HttpHeader header : authInfo.getConfiguration().getHttpHeaders()) {
138-
request.withHeader(header.getName(), header.getValue());
139-
}
140-
}
141174
username = authInfo.getUsername();
142175
password = authInfo.getPassword();
143176
} else {
@@ -146,8 +179,8 @@ private HttpSender.Request.Builder applyAuthentication(MavenRepository repositor
146179
}
147180
if (username != null && !username.contains("${") &&
148181
password != null && !password.contains("${")) {
149-
return request.withBasicAuthentication(username, password);
182+
return new String[]{username, password};
150183
}
151-
return request;
184+
return null;
152185
}
153186
}

rewrite-maven/src/test/java/org/openrewrite/maven/utilities/MavenArtifactDownloaderTest.java

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,63 @@ void downloadDependenciesWithClassifier(@TempDir Path tempDir) {
132132
}
133133
}
134134

135+
@Test
136+
void fallsBackToAnonymousWhenServerReturns401(@TempDir Path tempDir) throws Exception {
137+
byte[] jarBytes = {0x50, 0x4B, 0x03, 0x04};
138+
139+
try (MockWebServer mockRepo = new MockWebServer()) {
140+
mockRepo.setDispatcher(new Dispatcher() {
141+
@Override
142+
public MockResponse dispatch(RecordedRequest request) {
143+
if (request.getHeader("Authorization") != null) {
144+
return new MockResponse().setResponseCode(401);
145+
}
146+
return new MockResponse().setResponseCode(200)
147+
.setBody(new okio.Buffer().write(jarBytes));
148+
}
149+
});
150+
mockRepo.start();
151+
152+
String repoUrl = "http://" + mockRepo.getHostName() + ":" + mockRepo.getPort();
153+
MavenSettings settings = MavenSettings.parse(new Parser.Input(
154+
Path.of("settings.xml"), () -> new ByteArrayInputStream(
155+
//language=xml
156+
"""
157+
<settings>
158+
<servers>
159+
<server>
160+
<id>mock-repo</id>
161+
<username>bad-user</username>
162+
<password>bad-password</password>
163+
</server>
164+
</servers>
165+
</settings>
166+
""".getBytes())), new InMemoryExecutionContext());
167+
168+
MavenArtifactCache artifactCache = new LocalMavenArtifactCache(tempDir);
169+
AtomicReference<Throwable> error = new AtomicReference<>();
170+
MavenArtifactDownloader downloader = new MavenArtifactDownloader(
171+
artifactCache, settings, error::set);
172+
173+
MavenRepository repo = new MavenRepository(
174+
"mock-repo", repoUrl, "true", "false", true, null, null, null, false);
175+
GroupArtifactVersion gav = new GroupArtifactVersion("com.example", "test-lib", "1.0.0");
176+
ResolvedDependency dep = ResolvedDependency.builder()
177+
.repository(repo)
178+
.gav(new ResolvedGroupArtifactVersion(repoUrl, gav.getGroupId(), gav.getArtifactId(), gav.getVersion(), null))
179+
.requested(Dependency.builder().gav(gav).build())
180+
.build();
181+
182+
Path artifact = downloader.downloadArtifact(dep);
183+
184+
assertThat(artifact).isNotNull();
185+
assertThat(error.get()).isNull();
186+
assertThat(mockRepo.getRequestCount()).isEqualTo(2);
187+
assertThat(mockRepo.takeRequest().getHeader("Authorization")).isNotNull();
188+
assertThat(mockRepo.takeRequest().getHeader("Authorization")).isNull();
189+
}
190+
}
191+
135192
@Test
136193
void fallsBackToAnonymousWhenCredentialsRejected(@TempDir Path tempDir) throws Exception {
137194
byte[] jarBytes = {0x50, 0x4B, 0x03, 0x04}; // minimal ZIP magic bytes

0 commit comments

Comments
 (0)