This is an automated email from the ASF dual-hosted git repository. twolf pushed a commit to branch dev_3.0 in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 42850b3a350ba1a16c5187739953cac1285f68e1 Author: Thomas Wolf <tw...@apache.org> AuthorDate: Sat Apr 12 14:53:30 2025 +0200 GH-728: pass HostConfigEntry to AbstractClientSession A ClientSession needs to know the HostConfigEntry that applies for this session. The HostConfigEntry may contain numerous configuration overrides that the session may need to consider. --- CHANGES.md | 3 +- .../java/org/apache/sshd/client/SshClient.java | 52 +++++++++++++++++++++- .../sshd/client/session/AbstractClientSession.java | 11 +++++ .../apache/sshd/client/session/ClientSession.java | 8 ++++ .../client/ClientAuthenticationManagerTest.java | 7 ++- .../sshd/client/session/ClientSessionTest.java | 21 ++++++--- 6 files changed, 94 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cc64e5550..eb8414f50 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,4 +17,5 @@ Version 3 includes all the features and bug fixes of version 2, including the [l ## New Features * Random padding on SSH packets as suggested by [RFC 4253, section 6](https://datatracker.ietf.org/doc/html/rfc4253#section-6). -* New event callback `SessionListener.sessionStarting()`. See the [filter documentation](./docs/technical/filters.md). `SessionListener.sessionEstablished()` was removed; it was called from the constructor of `AbstractSession` at a time when the object was not yet fully initialized. \ No newline at end of file +* New event callback `SessionListener.sessionStarting()`. See the [filter documentation](./docs/technical/filters.md). `SessionListener.sessionEstablished()` was removed; it was called from the constructor of `AbstractSession` at a time when the object was not yet fully initialized. +* [GH-728](https://github.com/apache/mina-sshd/issues/728) New method `ClientSession.getHostConfigEntry()` to get the `HostConfigEntry` for the session. diff --git a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java index 6289f2491..22091c646 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/SshClient.java @@ -34,9 +34,13 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -647,10 +651,16 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa throw new IllegalStateException("SshClient not started. Please call start() method before connecting to a server"); } + Map<AttributeRepository.AttributeKey<?>, Object> sessionAttributes = new HashMap<>(); + sessionAttributes.put(AbstractClientSession.HOST_CONFIG_ENTRY, hostConfig); + AttributeRepository sessionContext = AttributeRepository.ofAttributesMap(sessionAttributes); + if (context != null) { + sessionContext = new ShadowingAttributeRepository(sessionContext, context); + } ConnectFuture connectFuture = new DefaultConnectFuture(username + "@" + targetAddress, null); SshFutureListener<IoConnectFuture> listener = createConnectCompletionListener( connectFuture, username, targetAddress, identities, hostConfig); - IoConnectFuture connectingFuture = connector.connect(targetAddress, context, localAddress); + IoConnectFuture connectingFuture = connector.connect(targetAddress, sessionContext, localAddress); connectFuture.addListener(c -> { if (c.isCanceled()) { connectingFuture.cancel(); @@ -1056,4 +1066,44 @@ public class SshClient extends AbstractFactoryManager implements ClientFactoryMa return client; } + + protected static class ShadowingAttributeRepository implements AttributeRepository { + + private final AttributeRepository shadow; + + private final AttributeRepository parent; + + public ShadowingAttributeRepository(AttributeRepository shadow, AttributeRepository parent) { + this.shadow = Objects.requireNonNull(shadow); + this.parent = Objects.requireNonNull(parent); + } + + @Override + public int getAttributesCount() { + return attributeKeys().size(); + } + + @Override + public <T> T getAttribute(AttributeKey<T> key) { + if (shadow.attributeKeys().contains(Objects.requireNonNull(key))) { + return shadow.getAttribute(key); + } + return parent.getAttribute(key); + } + + @Override + public Collection<AttributeKey<?>> attributeKeys() { + Set<AttributeKey<?>> keys = new HashSet<>(shadow.attributeKeys()); + keys.addAll(parent.attributeKeys()); + return keys; + } + + @Override + public <T> T resolveAttribute(AttributeKey<T> key) { + if (shadow.attributeKeys().contains(Objects.requireNonNull(key))) { + return shadow.getAttribute(key); + } + return parent.resolveAttribute(key); + } + } } diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java index d5f40381a..a5471760e 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java @@ -43,6 +43,7 @@ import org.apache.sshd.client.channel.ChannelExec; import org.apache.sshd.client.channel.ChannelShell; import org.apache.sshd.client.channel.ChannelSubsystem; import org.apache.sshd.client.channel.ClientChannel; +import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.keyverifier.ServerKeyVerifier; import org.apache.sshd.common.AttributeRepository; import org.apache.sshd.common.FactoryManager; @@ -77,12 +78,16 @@ import org.apache.sshd.core.CoreModuleProperties; * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public abstract class AbstractClientSession extends AbstractSession implements ClientSession { + + public static final AttributeKey<HostConfigEntry> HOST_CONFIG_ENTRY = new AttributeKey<>(); + protected final boolean sendImmediateClientIdentification; protected final boolean sendImmediateKexInit; private final List<Object> identities = new CopyOnWriteArrayList<>(); private final AuthenticationIdentitiesProvider identitiesProvider; private final AttributeRepository connectionContext; + private final HostConfigEntry hostConfig; private PublicKey serverKey; private ServerKeyVerifier serverKeyVerifier; @@ -105,6 +110,12 @@ public abstract class AbstractClientSession extends AbstractSession implements C identitiesProvider = AuthenticationIdentitiesProvider.wrapIdentities(identities); connectionContext = (AttributeRepository) ioSession.getAttribute(AttributeRepository.class); + hostConfig = connectionContext.getAttribute(HOST_CONFIG_ENTRY); + } + + @Override + public HostConfigEntry getHostConfigEntry() { + return hostConfig; } @Override diff --git a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java index e792c5d72..d649309d8 100644 --- a/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java @@ -47,6 +47,7 @@ import org.apache.sshd.client.channel.ChannelShell; import org.apache.sshd.client.channel.ChannelSubsystem; import org.apache.sshd.client.channel.ClientChannel; import org.apache.sshd.client.channel.ClientChannelEvent; +import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.future.AuthFuture; import org.apache.sshd.client.session.forward.DynamicPortForwardingTracker; import org.apache.sshd.client.session.forward.ExplicitPortForwardingTracker; @@ -106,6 +107,13 @@ public interface ClientSession */ SocketAddress getConnectAddress(); + /** + * Retrieves the {@link HostConfigEntry} that was used to create this session. + * + * @return the {@link HostConfigEntry}; or {@code null} if none + */ + HostConfigEntry getHostConfigEntry(); + /** * @return The "context" data provided when session connection was established - {@code null} if none. */ diff --git a/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java index 20dfa76a0..47d409017 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/ClientAuthenticationManagerTest.java @@ -24,6 +24,7 @@ import java.lang.reflect.Method; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; +import java.util.HashMap; import java.util.List; import java.util.concurrent.atomic.AtomicReference; @@ -38,6 +39,7 @@ import org.apache.sshd.client.auth.pubkey.PublicKeyAuthenticationReporter; import org.apache.sshd.client.keyverifier.ServerKeyVerifier; import org.apache.sshd.client.session.ClientSession; import org.apache.sshd.client.session.ClientSessionImpl; +import org.apache.sshd.common.AttributeRepository; import org.apache.sshd.common.Factory; import org.apache.sshd.common.NamedResource; import org.apache.sshd.common.channel.ChannelListener; @@ -306,7 +308,10 @@ class ClientAuthenticationManagerTest extends BaseTestSupport { } private ClientSession createMockClientSession(ClientFactoryManager client) throws Exception { - return new ClientSessionImpl(client, Mockito.mock(IoSession.class)) { + IoSession ioSession = Mockito.mock(IoSession.class); + Mockito.when(ioSession.getAttribute(AttributeRepository.class)) + .thenReturn(AttributeRepository.ofAttributesMap(new HashMap<>())); + return new ClientSessionImpl(client, ioSession) { @Override protected void initializeKeyExchangePhase() throws Exception { diff --git a/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionTest.java b/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionTest.java index d251e2666..c643f83c4 100644 --- a/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/client/session/ClientSessionTest.java @@ -35,6 +35,7 @@ import java.util.concurrent.atomic.AtomicInteger; import org.apache.sshd.client.SshClient; import org.apache.sshd.client.channel.ClientChannel; import org.apache.sshd.client.channel.ClientChannelEvent; +import org.apache.sshd.client.config.hosts.HostConfigEntry; import org.apache.sshd.client.future.AuthFuture; import org.apache.sshd.common.AttributeRepository; import org.apache.sshd.common.AttributeRepository.AttributeKey; @@ -234,18 +235,28 @@ class ClientSessionTest extends BaseTestSupport { assertEquals(Integer.toString(expectedErrorCode), actualErrorMessage, "Mismatched captured error code"); } - // see SSHD-859 + // see SSHD-859 and GH-728 @Test void connectionContextPropagation() throws Exception { - AttributeRepository expected = AttributeRepository.ofKeyValuePair( - new AttributeKey<String>(), getCurrentTestName()); + String myName = getCurrentTestName(); + AttributeKey<String> myAttribute = new AttributeKey<String>(); + AttributeRepository expected = AttributeRepository.ofKeyValuePair(myAttribute, myName); AtomicInteger creationCount = new AtomicInteger(0); SessionListener listener = new SessionListener() { @Override public void sessionCreated(Session session) { - AttributeRepository actual = ((ClientSession) session).getConnectionContext(); - assertSame(expected, actual, "Mismatched connection context"); creationCount.incrementAndGet(); + AttributeRepository actual = ((ClientSession) session).getConnectionContext(); + assertNotNull(actual, "No connection context"); + String value = actual.getAttribute(myAttribute); + assertSame(myName, value, "Mismatched connection context"); + HostConfigEntry entry1 = actual.getAttribute(AbstractClientSession.HOST_CONFIG_ENTRY); + assertNotNull(entry1, "No host config entry"); + HostConfigEntry entry = ((ClientSession) session).getHostConfigEntry(); + assertSame(entry1, entry, "Mismatched host config entry"); + assertEquals(TEST_LOCALHOST, entry.getHostName(), "Unexpeted host name"); + assertEquals(port, entry.getPort(), "Unexpected port"); + assertEquals(myName, entry.getUsername(), "Unexpected user name"); } };