Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 {
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,15 @@ 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()) {
mockRepo.setDispatcher(new Dispatcher() {
@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));
Expand All @@ -158,8 +158,8 @@ public MockResponse dispatch(RecordedRequest request) {
<servers>
<server>
<id>mock-repo</id>
<username>${placeholder}</username>
<password>${placeholder}</password>
<username>bad-user</username>
<password>bad-password</password>
</server>
</servers>
</settings>
Expand All @@ -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
"""
<settings>
<servers>
<server>
<id>mock-repo</id>
<username>good-user</username>
<password>good-password</password>
</server>
</servers>
</settings>
""".getBytes())), new InMemoryExecutionContext());

MavenArtifactCache artifactCache = new LocalMavenArtifactCache(tempDir);
AtomicReference<Throwable> 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();
}
}
}