You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by cs...@apache.org on 2026/08/31 10:23:26 UTC
(maven-resolver) branch master updated: Carry repository provenance into aggregateRepositories (#2090)
This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/master by this push:
new f511a616a Carry repository provenance into aggregateRepositories (#2090)
f511a616a is described below
commit f511a616a5800a89bf926cec6bcfa240ca834412
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Mon Aug 31 12:23:21 2026 +0200
Carry repository provenance into aggregateRepositories (#2090)
`RemoteRepositoryManager.aggregateRepositories` cannot currently tell where the recessive repository
definitions came from. Repositories declared by an artifact descriptor downloaded during dependency
collection arrive by the same path as repositories the build itself supplied, so session mirror,
proxy and authentication settings are applied identically to both.
This adds an overload carrying that provenance:
```java
aggregateRepositories(session, dominant, recessive, recessiveIsRaw, recessiveIsFromDescriptor)
```
An implementation can then apply session authentication only to repositories the operator
configured, and withhold it from descriptor-declared ones unless an operator-defined mirror has been
selected for them. Repositories supplied by the build keep receiving mirror, proxy and authentication
settings exactly as documented today.
The default implementation ignores the new argument and delegates to the existing four-argument
method, so behaviour is unchanged for any implementation that does not override it. Marked
`@since 2.0.23`.
---
.../aether/impl/RemoteRepositoryManager.java | 38 +++++++
.../impl/DefaultRemoteRepositoryManager.java | 55 ++++++++-
.../impl/collect/DependencyCollectorDelegate.java | 2 +-
.../impl/collect/bf/BfDependencyCollector.java | 2 +-
.../impl/collect/df/DfDependencyCollector.java | 2 +-
.../impl/DefaultRemoteRepositoryManagerTest.java | 126 +++++++++++++++++++++
6 files changed, 220 insertions(+), 5 deletions(-)
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RemoteRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RemoteRepositoryManager.java
index 2998bb661..2371f517d 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RemoteRepositoryManager.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/impl/RemoteRepositoryManager.java
@@ -54,6 +54,44 @@ public interface RemoteRepositoryManager {
List<RemoteRepository> recessiveRepositories,
boolean recessiveIsRaw);
+ /**
+ * Aggregates repository definitions by merging duplicate repositories and optionally applies mirror, proxy and
+ * authentication settings from the supplied session, additionally distinguishing the provenance of the recessive
+ * repository definitions. Repository definitions that originate from a remote artifact descriptor (i.e. a POM
+ * downloaded during dependency collection) are remotely supplied input: implementations may withhold session
+ * authentication from them unless an operator-defined mirror has been selected for them, so that session
+ * authentication is applied only to repositories the operator configured. Repository definitions supplied
+ * by the build itself (e.g. via
+ * {@code RepositorySystem#newResolutionRepositories}) must keep receiving mirror, proxy and authentication
+ * settings as documented for
+ * {@link #aggregateRepositories(RepositorySystemSession, List, List, boolean)}.
+ * <p>
+ * The default implementation ignores the provenance hint and delegates to
+ * {@link #aggregateRepositories(RepositorySystemSession, List, List, boolean)}.
+ *
+ * @param session The repository session during which the repositories will be accessed, must not be {@code null}.
+ * @param dominantRepositories The current list of remote repositories to merge the new definitions into, must not
+ * be {@code null}.
+ * @param recessiveRepositories The remote repositories to merge into the existing list, must not be {@code null}.
+ * @param recessiveIsRaw {@code true} if the recessive repository definitions have not yet been subjected to mirror,
+ * proxy and authentication settings, {@code false} otherwise.
+ * @param recessiveIsFromDescriptor {@code true} if the recessive repository definitions were declared by a remote
+ * artifact descriptor (POM) rather than by the build itself, {@code false} otherwise.
+ * @return The aggregated list of remote repositories, never {@code null}.
+ * @since 2.0.23
+ * @see RepositorySystemSession#getMirrorSelector()
+ * @see RepositorySystemSession#getProxySelector()
+ * @see RepositorySystemSession#getAuthenticationSelector()
+ */
+ default List<RemoteRepository> aggregateRepositories(
+ RepositorySystemSession session,
+ List<RemoteRepository> dominantRepositories,
+ List<RemoteRepository> recessiveRepositories,
+ boolean recessiveIsRaw,
+ boolean recessiveIsFromDescriptor) {
+ return aggregateRepositories(session, dominantRepositories, recessiveRepositories, recessiveIsRaw);
+ }
+
/**
* Gets the effective repository policy for the specified remote repository by merging the applicable
* snapshot/release policy of the repository with global settings from the supplied session.
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java
index 1f003be8f..8e522eec5 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManager.java
@@ -27,6 +27,7 @@ import java.util.List;
import java.util.ListIterator;
import java.util.stream.Collectors;
+import org.eclipse.aether.ConfigurationProperties;
import org.eclipse.aether.Keys;
import org.eclipse.aether.RepositoryCache;
import org.eclipse.aether.RepositorySystemSession;
@@ -42,6 +43,7 @@ import org.eclipse.aether.repository.RepositoryKeyFunction;
import org.eclipse.aether.repository.RepositoryPolicy;
import org.eclipse.aether.spi.connector.checksum.ChecksumPolicyProvider;
import org.eclipse.aether.spi.remoterepo.RepositoryKeyFunctionFactory;
+import org.eclipse.aether.util.ConfigUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,6 +55,30 @@ import static java.util.Objects.requireNonNull;
@Named
public class DefaultRemoteRepositoryManager implements RemoteRepositoryManager {
+ private static final String CONFIG_PROPS_PREFIX =
+ ConfigurationProperties.PREFIX_AETHER + "remoteRepositoryManager.";
+
+ /**
+ * Flag indicating whether session authentication (i.e. credentials configured in {@code settings.xml}) may be
+ * applied, matched by plain repository ID, to repositories declared by remote artifact descriptors (POMs) that
+ * are merged into the effective repository list during dependency collection. When disabled (the default),
+ * session authentication is only applied to such a repository when an operator-defined mirror has been selected
+ * for it; if credentials would have matched a descriptor-declared repository, a warning naming the repository ID
+ * and URL is logged instead. Repositories supplied by the build itself (e.g. aggregated via
+ * {@code RepositorySystem#newResolutionRepositories}) are unaffected and keep receiving matching credentials.
+ * Enabling this restores the legacy behavior of applying matching session authentication to descriptor
+ * declared repositories regardless of their provenance.
+ *
+ * @since 2.0.23
+ * @configurationSource {@link RepositorySystemSession#getConfigProperties()}
+ * @configurationType {@link java.lang.Boolean}
+ * @configurationDefaultValue {@link #DEFAULT_AUTH_TO_DESCRIPTOR_REPOSITORIES}
+ */
+ public static final String CONFIG_PROP_AUTH_TO_DESCRIPTOR_REPOSITORIES =
+ CONFIG_PROPS_PREFIX + "authToDescriptorRepositories";
+
+ public static final boolean DEFAULT_AUTH_TO_DESCRIPTOR_REPOSITORIES = false;
+
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultRemoteRepositoryManager.class);
private final UpdatePolicyAnalyzer updatePolicyAnalyzer;
@@ -78,6 +104,16 @@ public class DefaultRemoteRepositoryManager implements RemoteRepositoryManager {
List<RemoteRepository> dominantRepositories,
List<RemoteRepository> recessiveRepositories,
boolean recessiveIsRaw) {
+ return aggregateRepositories(session, dominantRepositories, recessiveRepositories, recessiveIsRaw, false);
+ }
+
+ @Override
+ public List<RemoteRepository> aggregateRepositories(
+ RepositorySystemSession session,
+ List<RemoteRepository> dominantRepositories,
+ List<RemoteRepository> recessiveRepositories,
+ boolean recessiveIsRaw,
+ boolean recessiveIsFromDescriptor) {
requireNonNull(session, "session cannot be null");
requireNonNull(dominantRepositories, "dominantRepositories cannot be null");
requireNonNull(recessiveRepositories, "recessiveRepositories cannot be null");
@@ -90,11 +126,15 @@ public class DefaultRemoteRepositoryManager implements RemoteRepositoryManager {
AuthenticationSelector authSelector = session.getAuthenticationSelector();
ProxySelector proxySelector = session.getProxySelector();
+ boolean authToDescriptorRepositories = ConfigUtils.getBoolean(
+ session, DEFAULT_AUTH_TO_DESCRIPTOR_REPOSITORIES, CONFIG_PROP_AUTH_TO_DESCRIPTOR_REPOSITORIES);
+
List<RemoteRepository> result = new ArrayList<>(dominantRepositories);
next:
for (RemoteRepository recessiveRepository : recessiveRepositories) {
RemoteRepository repository = recessiveRepository;
+ boolean mirrored = false;
if (recessiveIsRaw) {
RemoteRepository mirrorRepository = mirrorSelector.getMirror(recessiveRepository);
@@ -102,6 +142,7 @@ public class DefaultRemoteRepositoryManager implements RemoteRepositoryManager {
if (mirrorRepository != null) {
logMirror(session, recessiveRepository, mirrorRepository);
repository = mirrorRepository;
+ mirrored = true;
}
}
@@ -128,8 +169,18 @@ public class DefaultRemoteRepositoryManager implements RemoteRepositoryManager {
RemoteRepository.Builder builder = null;
Authentication auth = authSelector.getAuthentication(repository);
if (auth != null) {
- builder = new RemoteRepository.Builder(repository);
- builder.setAuthentication(auth);
+ if (!recessiveIsFromDescriptor || mirrored || authToDescriptorRepositories) {
+ builder = new RemoteRepository.Builder(repository);
+ builder.setAuthentication(auth);
+ } else if (auth != repository.getAuthentication()) {
+ LOGGER.warn(
+ "Not applying session authentication to repository {} ({}) declared by a remote"
+ + " artifact descriptor; set {}=true to restore the legacy behavior of"
+ + " matching credentials to such repositories by repository ID",
+ repository.getId(),
+ repository.getUrl(),
+ CONFIG_PROP_AUTH_TO_DESCRIPTOR_REPOSITORIES);
+ }
}
Proxy proxy = proxySelector.getProxy(repository);
if (proxy != null) {
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java
index 7bf6e26eb..a11039588 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/DependencyCollectorDelegate.java
@@ -225,7 +225,7 @@ public abstract class DependencyCollectorDelegate implements DependencyCollector
if (!session.isIgnoreArtifactDescriptorRepositories()) {
repositories = remoteRepositoryManager.aggregateRepositories(
- session, repositories, descriptorResult.getRepositories(), true);
+ session, repositories, descriptorResult.getRepositories(), true, true);
}
dependencies = mergeDeps(dependencies, descriptorResult.getDependencies());
managedDependencies = mergeDeps(managedDependencies, descriptorResult.getManagedDependencies());
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java
index 8d1d6f763..5887f791b 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/bf/BfDependencyCollector.java
@@ -385,7 +385,7 @@ public class BfDependencyCollector extends DependencyCollectorDelegate {
final List<RemoteRepository> childRepos = args.ignoreRepos
? parentContext.repositories
: remoteRepositoryManager.aggregateRepositories(
- args.session, parentContext.repositories, descriptorResult.getRepositories(), true);
+ args.session, parentContext.repositories, descriptorResult.getRepositories(), true, true);
// Optimization: try pool cache with the parent manager as a speculative key BEFORE
// calling deriveChildManager. When deriveChildManager returns `this` (the common case —
diff --git a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java
index 034d6691e..c27fda561 100644
--- a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java
+++ b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/collect/df/DfDependencyCollector.java
@@ -335,7 +335,7 @@ public class DfDependencyCollector extends DependencyCollectorDelegate {
final List<RemoteRepository> childRepos = args.ignoreRepos
? repositories
: remoteRepositoryManager.aggregateRepositories(
- args.session, repositories, descriptorResult.getRepositories(), true);
+ args.session, repositories, descriptorResult.getRepositories(), true, true);
Object key =
args.pool.toKey(d.getArtifact(), childRepos, childSelector, childManager, childTraverser, childFilter);
diff --git a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java
index 9b51a2db5..1c88bdc4c 100644
--- a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java
+++ b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultRemoteRepositoryManagerTest.java
@@ -26,6 +26,8 @@ import org.eclipse.aether.DefaultRepositorySystemSession;
import org.eclipse.aether.RepositorySystemSession;
import org.eclipse.aether.impl.UpdatePolicyAnalyzer;
import org.eclipse.aether.internal.test.util.TestUtils;
+import org.eclipse.aether.repository.Authentication;
+import org.eclipse.aether.repository.AuthenticationSelector;
import org.eclipse.aether.repository.MirrorSelector;
import org.eclipse.aether.repository.Proxy;
import org.eclipse.aether.repository.ProxySelector;
@@ -202,6 +204,130 @@ public class DefaultRemoteRepositoryManagerTest {
assertSame(mirror.getAuthentication(), result.get(0).getAuthentication());
}
+ @Test
+ void testDescriptorRepositoryAuthentication_NotAppliedByDefault() {
+ final RemoteRepository repo = newRepo("a", "http://", true, "", "").build();
+ final Authentication auth =
+ new AuthenticationBuilder().addUsername("test").build();
+ session.setAuthenticationSelector(new AuthenticationSelector() {
+ public Authentication getAuthentication(RemoteRepository repository) {
+ return "a".equals(repository.getId()) ? auth : null;
+ }
+ });
+ session.setMirrorSelector(new MirrorSelector() {
+ public RemoteRepository getMirror(RemoteRepository repository) {
+ return null;
+ }
+ });
+
+ List<RemoteRepository> result =
+ manager.aggregateRepositories(session, Collections.emptyList(), Arrays.asList(repo), true, true);
+
+ assertEquals(1, result.size());
+ assertNull(result.get(0).getAuthentication());
+ }
+
+ @Test
+ void testDescriptorRepositoryAuthentication_AppliedWhenEnabled() {
+ final RemoteRepository repo = newRepo("a", "http://", true, "", "").build();
+ final Authentication auth =
+ new AuthenticationBuilder().addUsername("test").build();
+ session.setAuthenticationSelector(new AuthenticationSelector() {
+ public Authentication getAuthentication(RemoteRepository repository) {
+ return "a".equals(repository.getId()) ? auth : null;
+ }
+ });
+ session.setMirrorSelector(new MirrorSelector() {
+ public RemoteRepository getMirror(RemoteRepository repository) {
+ return null;
+ }
+ });
+ session.setConfigProperty(DefaultRemoteRepositoryManager.CONFIG_PROP_AUTH_TO_DESCRIPTOR_REPOSITORIES, true);
+
+ List<RemoteRepository> result =
+ manager.aggregateRepositories(session, Collections.emptyList(), Arrays.asList(repo), true, true);
+
+ assertEquals(1, result.size());
+ assertSame(auth, result.get(0).getAuthentication());
+ }
+
+ @Test
+ void testDescriptorRepositoryAuthentication_AppliedToSelectedMirror() {
+ final RemoteRepository repo = newRepo("a", "http://", true, "", "").build();
+ final RemoteRepository mirror = newRepo("m", "http://", true, "", "").build();
+ final Authentication auth =
+ new AuthenticationBuilder().addUsername("test").build();
+ session.setAuthenticationSelector(new AuthenticationSelector() {
+ public Authentication getAuthentication(RemoteRepository repository) {
+ return "m".equals(repository.getId()) ? auth : null;
+ }
+ });
+ session.setMirrorSelector(new MirrorSelector() {
+ public RemoteRepository getMirror(RemoteRepository repository) {
+ return mirror;
+ }
+ });
+
+ List<RemoteRepository> result =
+ manager.aggregateRepositories(session, Collections.emptyList(), Arrays.asList(repo), true, true);
+
+ assertEquals(1, result.size());
+ assertSame(auth, result.get(0).getAuthentication());
+ }
+
+ @Test
+ void testResolutionRepositoryAuthentication_StillApplied() {
+ // the RepositorySystem.newResolutionRepositories path: repositories declared by the build itself
+ // (pom.xml, profiles, settings.xml) aggregate via the provenance-less overload and keep receiving
+ // session credentials matched by ID, per that method's documented contract
+ final RemoteRepository repo = newRepo("a", "http://", true, "", "").build();
+ final Authentication auth =
+ new AuthenticationBuilder().addUsername("test").build();
+ session.setAuthenticationSelector(new AuthenticationSelector() {
+ public Authentication getAuthentication(RemoteRepository repository) {
+ return "a".equals(repository.getId()) ? auth : null;
+ }
+ });
+ session.setMirrorSelector(new MirrorSelector() {
+ public RemoteRepository getMirror(RemoteRepository repository) {
+ return null;
+ }
+ });
+
+ List<RemoteRepository> result =
+ manager.aggregateRepositories(session, Collections.emptyList(), Arrays.asList(repo), true);
+
+ assertEquals(1, result.size());
+ assertSame(auth, result.get(0).getAuthentication());
+ }
+
+ @Test
+ void testDescriptorRepositoryAuthentication_DominantRepositoryUnaffected() {
+ final Authentication dominantAuth =
+ new AuthenticationBuilder().addUsername("dominant").build();
+ final RemoteRepository dominant = newRepo("a", "file://", true, "", "")
+ .setAuthentication(dominantAuth)
+ .build();
+ final RemoteRepository recessive = newRepo("b", "http://", true, "", "").build();
+ session.setAuthenticationSelector(new AuthenticationSelector() {
+ public Authentication getAuthentication(RemoteRepository repository) {
+ return null;
+ }
+ });
+ session.setMirrorSelector(new MirrorSelector() {
+ public RemoteRepository getMirror(RemoteRepository repository) {
+ return null;
+ }
+ });
+
+ List<RemoteRepository> result =
+ manager.aggregateRepositories(session, Arrays.asList(dominant), Arrays.asList(recessive), true, true);
+
+ assertEquals(2, result.size());
+ assertSame(dominantAuth, result.get(0).getAuthentication());
+ assertNull(result.get(1).getAuthentication());
+ }
+
@Test
void testMirrorProxy() {
final RemoteRepository repo = newRepo("a", "http://", true, "", "").build();