This is an automated email from the ASF dual-hosted git repository.

lgoldstein pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mina-sshd.git

commit eed4e1c55ed9e4c5efe71e1e9cb4514fd44b7789
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Thu Oct 3 12:05:39 2019 +0300

    [SSHD-947] Added SessionListener#sessionEstablished method to allow for 
early session customization based on the peer's address
---
 CHANGES.md                                         |  4 ++-
 docs/event-listeners.md                            | 10 ++++--
 .../java/org/apache/sshd/common/util/Invoker.java  | 40 ++++++++++++++++++----
 .../sshd/client/session/AbstractClientSession.java | 11 +++---
 .../sshd/common/session/SessionListener.java       | 23 +++++++++++--
 .../common/session/helpers/AbstractSession.java    | 28 +++++++++++----
 .../sshd/common/session/helpers/SessionHelper.java | 30 ++++++++++++++++
 7 files changed, 122 insertions(+), 24 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 7fa2f29..02f2b7d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -36,9 +36,11 @@ the standard does not specifically specify the behavior 
regarding symbolic links
 
 ## Minor code helpers
 
-* `SessionListener` supports `sessionPeerIdentificationReceived` that is 
invoked once successful
+* `SessionListener` supports `sessionPeerIdentificationReceived` method that 
is invoked once successful
 peer version data is received.
 
+* `SessionListener` supports `sessionEstablished` method that is invoked when 
initial constructor is executed.
+
 * `ChannelIdTrackingUnknownChannelReferenceHandler` extends the functionality 
of the `DefaultUnknownChannelReferenceHandler`
 by tracking the initialized channels identifiers and being lenient only if 
command is received for a channel that was
 initialized in the past.
diff --git a/docs/event-listeners.md b/docs/event-listeners.md
index 49a4a83..6aa59cf 100644
--- a/docs/event-listeners.md
+++ b/docs/event-listeners.md
@@ -38,8 +38,9 @@ registered listener.
 ### `SessionListener`
 
 Informs about session related events. One can modify the session - although 
the modification effect depends on the session's **state**. E.g., if one
-changes the ciphers *after* the key exchange (KEX) phase, then they will take 
effect only if the keys are re-negotiated. It is important to read the
-documentation very carefully and understand at which stage each listener 
method is invoked and what are the repercussions of changes at that stage.
+changes the ciphers *after* the key exchange (KEX) phase, then they will take 
effect only if the keys are re-negotiated. Furthermore, invoking some
+session API(s) - event `getSomeValue` at the wrong time might yield unexpected 
results. It is important to read the documentation very carefully and
+understand at which stage each listener method is invoked, what are the 
limitations and what are the repercussions of changes at that stage.
 In this context, it is worth mentioning that one can attach to sessions 
**arbitrary attributes** that can be retrieved by the user's code later on:
 
 
@@ -50,6 +51,11 @@ In this context, it is worth mentioning that one can attach 
to sessions **arbitr
 
     sshClient/Server.addSessionListener(new SessionListener() {
         @Override
+        public void sessionEstablished(Session session) {
+            // examine the peer address or the connection context and set some 
attributes
+        }
+
+        @Override
         public void sessionCreated(Session session) {
             session.setAttribute(STR_KEY, "Some string value");
             session.setAttribute(LONG_KEY, 3777347L);
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java 
b/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java
index 71cbebd..1fc2115 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/Invoker.java
@@ -24,7 +24,7 @@ import java.util.Map;
 
 /**
  * The complement to the {@code Callable} interface - accepts one argument
- * and possibly throws somethind
+ * and possibly throws something
  *
  * @param <ARG> Argument type
  * @param <RET> Return type
@@ -34,7 +34,19 @@ import java.util.Map;
 public interface Invoker<ARG, RET> {
     RET invoke(ARG arg) throws Throwable;
 
-    static <ARG> Invoker<ARG, Void> wrapAll(Collection<? extends Invoker<? 
super ARG, ?>> invokers) {
+    /**
+     * Wraps a bunch of {@link Invoker}-s that return no value into one that
+     * invokes them in the same <U>order</U> as they appear. <B>Note:</B>
+     * <U>all</U> invokers are used and any thrown exceptions are 
<U>accumulated</U>
+     * and thrown as a single exception at the end of invoking all of them.
+     *
+     * @param <ARG> The argument type
+     * @param invokers The invokers to wrap - ignored if {@code null}/empty
+     * @return The wrapper
+     * @see #invokeAll(Object, Collection) invokeAll
+     */
+    static <ARG> Invoker<ARG, Void> wrapAll(
+            Collection<? extends Invoker<? super ARG, ?>> invokers) {
         return arg -> {
             invokeAll(arg, invokers);
             return null;
@@ -51,7 +63,9 @@ public interface Invoker<ARG, RET> {
      * (also ignores {@code null} members)
      * @throws Throwable If invocation failed
      */
-    static <ARG> void invokeAll(ARG arg, Collection<? extends Invoker<? super 
ARG, ?>> invokers) throws Throwable {
+    static <ARG> void invokeAll(
+            ARG arg, Collection<? extends Invoker<? super ARG, ?>> invokers)
+                throws Throwable {
         if (GenericUtils.isEmpty(invokers)) {
             return;
         }
@@ -74,9 +88,21 @@ public interface Invoker<ARG, RET> {
         }
     }
 
-    static <ARG> Invoker<ARG, Void> wrapFirst(Collection<? extends Invoker<? 
super ARG, ?>> invokers) {
+    /**
+     * Wraps a bunch of {@link Invoker}-s that return no value into one that
+     * invokes them in the same <U>order</U> as they appear. <B>Note:</B>
+     * stops when <U>first</U> invoker throws an exception (otherwise invokes 
all)
+     *
+     * @param <ARG> The argument type
+     * @param invokers The invokers to wrap - ignored if {@code null}/empty
+     * @return The wrapper
+     * @see #invokeTillFirstFailure(Object, Collection) invokeTillFirstFailure
+     */
+    static <ARG> Invoker<ARG, Void> wrapFirst(
+            Collection<? extends Invoker<? super ARG, ?>> invokers) {
         return arg -> {
-            Map.Entry<Invoker<? super ARG, ?>, Throwable> result = 
invokeTillFirstFailure(arg, invokers);
+            Map.Entry<Invoker<? super ARG, ?>, Throwable> result =
+                invokeTillFirstFailure(arg, invokers);
             if (result != null) {
                 throw result.getValue();
             }
@@ -94,7 +120,9 @@ public interface Invoker<ARG, RET> {
      * @return A {@link SimpleImmutableEntry} representing the <U>first</U> 
failed
      * invocation - {@code null} if all were successful (or none invoked).
      */
-    static <ARG> SimpleImmutableEntry<Invoker<? super ARG, ?>, Throwable> 
invokeTillFirstFailure(ARG arg, Collection<? extends Invoker<? super ARG, ?>> 
invokers) {
+    static <ARG> SimpleImmutableEntry<Invoker<? super ARG, ?>, Throwable>
+            invokeTillFirstFailure(
+                ARG arg, Collection<? extends Invoker<? super ARG, ?>> 
invokers) {
         if (GenericUtils.isEmpty(invokers)) {
             return null;
         }
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 285d2a1..38d854c 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,7 +43,6 @@ import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
 import org.apache.sshd.common.AttributeRepository;
 import org.apache.sshd.common.FactoryManager;
 import org.apache.sshd.common.NamedResource;
-import org.apache.sshd.common.PropertyResolverUtils;
 import org.apache.sshd.common.RuntimeSshException;
 import org.apache.sshd.common.SshConstants;
 import org.apache.sshd.common.SshException;
@@ -96,12 +95,12 @@ public abstract class AbstractClientSession extends 
AbstractSession implements C
     protected AbstractClientSession(ClientFactoryManager factoryManager, 
IoSession ioSession) {
         super(false, factoryManager, ioSession);
 
-        sendImmediateClientIdentification = 
PropertyResolverUtils.getBooleanProperty(
-            factoryManager, ClientFactoryManager.SEND_IMMEDIATE_IDENTIFICATION,
+        sendImmediateClientIdentification = this.getBooleanProperty(
+            ClientFactoryManager.SEND_IMMEDIATE_IDENTIFICATION,
             ClientFactoryManager.DEFAULT_SEND_IMMEDIATE_IDENTIFICATION);
-        sendImmediateKexInit = PropertyResolverUtils.getBooleanProperty(
-                factoryManager, ClientFactoryManager.SEND_IMMEDIATE_KEXINIT,
-                ClientFactoryManager.DEFAULT_SEND_KEXINIT);
+        sendImmediateKexInit = this.getBooleanProperty(
+            ClientFactoryManager.SEND_IMMEDIATE_KEXINIT,
+            ClientFactoryManager.DEFAULT_SEND_KEXINIT);
 
         identitiesProvider = 
AuthenticationIdentitiesProvider.wrapIdentities(identities);
         connectionContext = (AttributeRepository) 
ioSession.getAttribute(AttributeRepository.class);
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java 
b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java
index d322f9d..e93d024 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/SessionListener.java
@@ -35,6 +35,20 @@ public interface SessionListener extends SshdEventListener {
     }
 
     /**
+     * An initial session connection has been established - <B>Caveat 
emptor:</B>
+     * the main difference between this callback and {@link 
#sessionCreated(Session)}
+     * is that when this callback is called, the session is not yet fully 
initialized
+     * so not all API(s) will respond as expected. The main purpose of this 
callback
+     * is to allow the user to customize some session properties based on the 
peer's
+     * address and/or any provided connection context.
+     *
+     * @param session The established {@code Session}
+     */
+    default void sessionEstablished(Session session) {
+        // ignored
+    }
+
+    /**
      * A new session just been created
      *
      * @param session The created {@link Session}
@@ -64,7 +78,8 @@ public interface SessionListener extends SshdEventListener {
      * @param serverProposal The server proposal options (un-modifiable)
      */
     default void sessionNegotiationStart(Session session,
-            Map<KexProposalOption, String> clientProposal, 
Map<KexProposalOption, String> serverProposal) {
+            Map<KexProposalOption, String> clientProposal,
+            Map<KexProposalOption, String> serverProposal) {
         // ignored
     }
 
@@ -79,8 +94,10 @@ public interface SessionListener extends SshdEventListener {
      * @param reason Negotiation end reason - {@code null} if successful
      */
     default void sessionNegotiationEnd(Session session,
-            Map<KexProposalOption, String> clientProposal, 
Map<KexProposalOption, String> serverProposal,
-            Map<KexProposalOption, String> negotiatedOptions, Throwable 
reason) {
+            Map<KexProposalOption, String> clientProposal,
+            Map<KexProposalOption, String> serverProposal,
+            Map<KexProposalOption, String> negotiatedOptions,
+            Throwable reason) {
         // ignored
     }
 
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
index 5144e95..27e87a8 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/AbstractSession.java
@@ -230,6 +230,16 @@ public abstract class AbstractSession extends 
SessionHelper {
         sessionListenerProxy = 
EventListenerUtils.proxyWrapper(SessionListener.class, loader, 
sessionListeners);
         channelListenerProxy = 
EventListenerUtils.proxyWrapper(ChannelListener.class, loader, 
channelListeners);
         tunnelListenerProxy = 
EventListenerUtils.proxyWrapper(PortForwardingEventListener.class, loader, 
tunnelListeners);
+
+        try {
+            signalSessionEstablished(ioSession);
+        } catch (Exception e) {
+            if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            } else {
+                throw new RuntimeSshException(e);
+            }
+        }
     }
 
     @Override
@@ -2040,7 +2050,8 @@ public abstract class AbstractSession extends 
SessionHelper {
     }
 
     /**
-     * @param seed The result of the KEXINIT handshake - required for correct 
session key establishment
+     * @param seed The result of the KEXINIT handshake - required
+     * for correct session key establishment
      */
     protected abstract void setKexSeed(byte... seed);
 
@@ -2052,7 +2063,8 @@ public abstract class AbstractSession extends 
SessionHelper {
      * @see #getFactoryManager()
      * @see #resolveAvailableSignaturesProposal(FactoryManager)
      */
-    protected String resolveAvailableSignaturesProposal() throws IOException, 
GeneralSecurityException {
+    protected String resolveAvailableSignaturesProposal()
+            throws IOException, GeneralSecurityException {
         return resolveAvailableSignaturesProposal(getFactoryManager());
     }
 
@@ -2064,7 +2076,7 @@ public abstract class AbstractSession extends 
SessionHelper {
      * @throws GeneralSecurityException If failed to generate the keys
      */
     protected abstract String 
resolveAvailableSignaturesProposal(FactoryManager manager)
-            throws IOException, GeneralSecurityException;
+        throws IOException, GeneralSecurityException;
 
     /**
      * Indicates the the key exchange is completed and the exchanged keys
@@ -2091,7 +2103,9 @@ public abstract class AbstractSession extends 
SessionHelper {
         return seed;
     }
 
-    protected abstract void receiveKexInit(Map<KexProposalOption, String> 
proposal, byte[] seed) throws IOException;
+    protected abstract void receiveKexInit(
+        Map<KexProposalOption, String> proposal, byte[] seed)
+            throws IOException;
 
     /**
      * Retrieve the SSH session from the I/O session. If the session has not 
been attached,
@@ -2102,7 +2116,8 @@ public abstract class AbstractSession extends 
SessionHelper {
      * @see #getSession(IoSession, boolean)
      * @throws MissingAttachedSessionException if no attached SSH session
      */
-    public static AbstractSession getSession(IoSession ioSession) throws 
MissingAttachedSessionException {
+    public static AbstractSession getSession(IoSession ioSession)
+            throws MissingAttachedSessionException {
         return getSession(ioSession, false);
     }
 
@@ -2134,7 +2149,8 @@ public abstract class AbstractSession extends 
SessionHelper {
      * @return the session attached to the I/O session or {@code null}
      * @throws MissingAttachedSessionException if no attached session and 
<tt>allowNull=false</tt>
      */
-    public static AbstractSession getSession(IoSession ioSession, boolean 
allowNull) throws MissingAttachedSessionException {
+    public static AbstractSession getSession(IoSession ioSession, boolean 
allowNull)
+            throws MissingAttachedSessionException {
         AbstractSession session = (AbstractSession) 
ioSession.getAttribute(SESSION);
         if ((session == null) && (!allowNull)) {
             throw new MissingAttachedSessionException("No session attached to 
" + ioSession);
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
index 55324a5..b9ed2f1 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/common/session/helpers/SessionHelper.java
@@ -520,6 +520,36 @@ public abstract class SessionHelper extends 
AbstractKexFactoryManager implements
         return writeFuture;
     }
 
+    protected void signalSessionEstablished(IoSession ioSession) throws 
Exception {
+        try {
+            invokeSessionSignaller(l -> {
+                signalSessionEstablished(l);
+                return null;
+            });
+        } catch (Throwable err) {
+            Throwable e = GenericUtils.peelException(err);
+            if (log.isDebugEnabled()) {
+                log.debug("Failed ({}) to announce session={} established: {}",
+                      e.getClass().getSimpleName(), ioSession, e.getMessage());
+            }
+            if (log.isTraceEnabled()) {
+                log.trace("Session=" + ioSession + " establish failure 
details", e);
+            }
+            if (e instanceof Exception) {
+                throw (Exception) e;
+            } else {
+                throw new RuntimeSshException(e);
+            }
+        }
+    }
+
+    protected void signalSessionEstablished(SessionListener listener) {
+        if (listener == null) {
+            return;
+        }
+        listener.sessionEstablished(this);
+    }
+
     protected void signalSessionCreated(IoSession ioSession) throws Exception {
         try {
             invokeSessionSignaller(l -> {

Reply via email to