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 &quot;context&quot; 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");
             }
         };
 

Reply via email to