You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@qpid.apache.org by ro...@apache.org on 2018/01/08 18:35:25 UTC

qpid-proton-j git commit: PROTON-1736: add a SASL listener to allow relevant processing as frames arrive

Repository: qpid-proton-j
Updated Branches:
  refs/heads/master 2d565b857 -> 17cef9ace


PROTON-1736: add a SASL listener to allow relevant processing as frames arrive


Project: http://git-wip-us.apache.org/repos/asf/qpid-proton-j/repo
Commit: http://git-wip-us.apache.org/repos/asf/qpid-proton-j/commit/17cef9ac
Tree: http://git-wip-us.apache.org/repos/asf/qpid-proton-j/tree/17cef9ac
Diff: http://git-wip-us.apache.org/repos/asf/qpid-proton-j/diff/17cef9ac

Branch: refs/heads/master
Commit: 17cef9ace9a7c75901d517f951ae1d4610819436
Parents: 2d565b8
Author: Robbie Gemmell <ro...@apache.org>
Authored: Mon Jan 8 18:24:44 2018 +0000
Committer: Robbie Gemmell <ro...@apache.org>
Committed: Mon Jan 8 18:24:44 2018 +0000

----------------------------------------------------------------------
 .../org/apache/qpid/proton/engine/Sasl.java     |   5 +
 .../apache/qpid/proton/engine/SaslListener.java |  79 +++++++++
 .../qpid/proton/engine/impl/SaslImpl.java       |  28 +++
 .../qpid/proton/systemtests/SaslTest.java       | 170 +++++++++++++++++++
 4 files changed, 282 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/qpid-proton-j/blob/17cef9ac/proton-j/src/main/java/org/apache/qpid/proton/engine/Sasl.java
----------------------------------------------------------------------
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/Sasl.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/Sasl.java
index e5ebabd..4ccbcfe 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/Sasl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/Sasl.java
@@ -177,4 +177,9 @@ public interface Sasl
      * that skip the SASL layer negotiation.
      */
     void allowSkip(boolean allowSkip);
+
+    /**
+     * Adds a listener to receive notice of frames having arrived.
+     */
+    void setListener(SaslListener saslListener);
 }

http://git-wip-us.apache.org/repos/asf/qpid-proton-j/blob/17cef9ac/proton-j/src/main/java/org/apache/qpid/proton/engine/SaslListener.java
----------------------------------------------------------------------
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/SaslListener.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/SaslListener.java
new file mode 100644
index 0000000..d53086d
--- /dev/null
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/SaslListener.java
@@ -0,0 +1,79 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.proton.engine;
+
+/**
+ * Listener for SASL frame arrival to facilitate relevant handling for the SASL
+ * negotiation.
+ *
+ * See the AMQP specification
+ * <a href="http://docs.oasis-open.org/amqp/core/v1.0/os/amqp-core-security-v1.0-os.html#doc-idp51040">
+ * SASL negotiation process</a> overview for related detail.
+ */
+public interface SaslListener {
+
+    /**
+     * Called when a sasl-mechanisms frame has arrived and its effect
+     * applied, indicating the offered mechanisms sent by the 'server' peer.
+     *
+     * @param sasl the Sasl object
+     * @param transport the related transport
+     */
+    void onSaslMechanisms(Sasl sasl, Transport transport);
+
+    /**
+     * Called when a sasl-init frame has arrived and its effect
+     * applied, indicating the selected mechanism and any hostname
+     * and initial-response details from the 'client' peer.
+     *
+     * @param sasl the Sasl object
+     * @param transport the related transport
+     */
+    void onSaslInit(Sasl sasl, Transport transport);
+
+    /**
+     * Called when a sasl-challenge frame has arrived and its effect
+     * applied, indicating the challenge sent by the 'server' peer.
+     *
+     * @param sasl the Sasl object
+     * @param transport the related transport
+     */
+    void onSaslChallenge(Sasl sasl, Transport transport);
+
+    /**
+     * Called when a sasl-response frame has arrived and its effect
+     * applied, indicating the response sent by the 'client' peer.
+     *
+     * @param sasl the Sasl object
+     * @param transport the related transport
+     */
+    void onSaslResponse(Sasl sasl, Transport transport);
+
+    /**
+     * Called when a sasl-outcome frame has arrived and its effect
+     * applied, indicating the outcome and any success additional-data
+     * sent by the 'server' peer.
+     *
+     * @param sasl the Sasl object
+     * @param transport the related transport
+     */
+    void onSaslOutcome(Sasl sasl, Transport transport);
+}

http://git-wip-us.apache.org/repos/asf/qpid-proton-j/blob/17cef9ac/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
----------------------------------------------------------------------
diff --git a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
index d6f510b..acbf5ea 100644
--- a/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
+++ b/proton-j/src/main/java/org/apache/qpid/proton/engine/impl/SaslImpl.java
@@ -42,6 +42,7 @@ import org.apache.qpid.proton.codec.AMQPDefinedTypes;
 import org.apache.qpid.proton.codec.DecoderImpl;
 import org.apache.qpid.proton.codec.EncoderImpl;
 import org.apache.qpid.proton.engine.Sasl;
+import org.apache.qpid.proton.engine.SaslListener;
 import org.apache.qpid.proton.engine.Transport;
 import org.apache.qpid.proton.engine.TransportException;
 
@@ -85,6 +86,8 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
     private Role _role;
     private boolean _allowSkip = true;
 
+    private SaslListener _saslListener;
+
     /**
      * @param maxFrameSize the size of the input and output buffers
      * returned by {@link SaslTransportWrapper#getInputBuffer()} and
@@ -346,6 +349,10 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
         {
             setPending(saslInit.getInitialResponse().asByteBuffer());
         }
+
+        if(_saslListener != null) {
+            _saslListener.onSaslInit(this, _transport);
+        }
     }
 
     @Override
@@ -353,6 +360,10 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
     {
         checkRole(Role.SERVER);
         setPending(saslResponse.getResponse()  == null ? null : saslResponse.getResponse().asByteBuffer());
+
+        if(_saslListener != null) {
+            _saslListener.onSaslResponse(this, _transport);
+        }
     }
 
     @Override
@@ -382,6 +393,10 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
         }
         checkRole(Role.CLIENT);
         _mechanisms = saslMechanisms.getSaslServerMechanisms();
+
+        if(_saslListener != null) {
+            _saslListener.onSaslMechanisms(this, _transport);
+        }
     }
 
     @Override
@@ -389,6 +404,10 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
     {
         checkRole(Role.CLIENT);
         setPending(saslChallenge.getChallenge()  == null ? null : saslChallenge.getChallenge().asByteBuffer());
+
+        if(_saslListener != null) {
+            _saslListener.onSaslChallenge(this, _transport);
+        }
     }
 
     @Override
@@ -416,6 +435,10 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
         {
             _logger.fine("Handled outcome: " + this);
         }
+
+        if(_saslListener != null) {
+            _saslListener.onSaslOutcome(this, _transport);
+        }
     }
 
     private SaslState classifyStateFromOutcome(SaslOutcome outcome)
@@ -742,4 +765,9 @@ public class SaslImpl implements Sasl, SaslFrameBody.SaslFrameBodyHandler<Void>,
 
         _hostname = hostname;
     }
+
+    @Override
+    public void setListener(SaslListener saslListener) {
+        _saslListener = saslListener;
+    }
 }

http://git-wip-us.apache.org/repos/asf/qpid-proton-j/blob/17cef9ac/proton-j/src/test/java/org/apache/qpid/proton/systemtests/SaslTest.java
----------------------------------------------------------------------
diff --git a/proton-j/src/test/java/org/apache/qpid/proton/systemtests/SaslTest.java b/proton-j/src/test/java/org/apache/qpid/proton/systemtests/SaslTest.java
index 12f5143..406e06f 100644
--- a/proton-j/src/test/java/org/apache/qpid/proton/systemtests/SaslTest.java
+++ b/proton-j/src/test/java/org/apache/qpid/proton/systemtests/SaslTest.java
@@ -21,17 +21,22 @@ package org.apache.qpid.proton.systemtests;
 import static org.apache.qpid.proton.systemtests.TestLoggingHelper.bold;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.logging.Logger;
 
 import org.junit.Test;
 
 import org.apache.qpid.proton.Proton;
 import org.apache.qpid.proton.engine.Sasl;
+import org.apache.qpid.proton.engine.SaslListener;
+import org.apache.qpid.proton.engine.Transport;
 import org.apache.qpid.proton.engine.Sasl.SaslOutcome;
 
 public class SaslTest extends EngineTestBase
@@ -39,6 +44,7 @@ public class SaslTest extends EngineTestBase
     private static final Logger LOGGER = Logger.getLogger(SaslTest.class.getName());
     private static final String TESTMECH1 = "TESTMECH1";
     private static final String TESTMECH2 = "TESTMECH2";
+    private static final byte[] INITIAL_RESPONSE_BYTES = "initial-response-bytes".getBytes(StandardCharsets.UTF_8);
     private static final byte[] CHALLENGE_BYTES = "challenge-bytes".getBytes(StandardCharsets.UTF_8);
     private static final byte[] RESPONSE_BYTES = "response-bytes".getBytes(StandardCharsets.UTF_8);
     private static final byte[] ADDITIONAL_DATA_BYTES = "additional-data-bytes".getBytes(StandardCharsets.UTF_8);
@@ -386,4 +392,168 @@ public class SaslTest extends EngineTestBase
 
         return bytes;
     }
+
+    @Test
+    public void testSaslNegotiationUsingListener() throws Exception
+    {
+        getClient().transport = Proton.transport();
+        getServer().transport = Proton.transport();
+
+        AtomicBoolean mechanismsReceived = new AtomicBoolean();
+        AtomicBoolean challengeReceived = new AtomicBoolean();
+        AtomicBoolean outcomeReceived = new AtomicBoolean();
+
+        Sasl clientSasl = getClient().transport.sasl();
+        clientSasl.client();
+        clientSasl.setListener(new ClientSaslHandling(mechanismsReceived, challengeReceived, outcomeReceived));
+
+        AtomicBoolean initReceived = new AtomicBoolean();
+        AtomicBoolean responseReceived = new AtomicBoolean();
+
+        Sasl serverSasl = getServer().transport.sasl();
+        serverSasl.server();
+        serverSasl.setMechanisms(TESTMECH1, TESTMECH2);
+        serverSasl.setListener(new ServerSaslHandling(initReceived, responseReceived));
+
+        pumpClientToServer();
+        pumpServerToClient();
+
+        assertTrue("mechanisms were not received by client", mechanismsReceived.get());
+        assertFalse("init was received by server", initReceived.get());
+
+        pumpClientToServer();
+
+        assertTrue("init was not received by server", initReceived.get());
+        assertFalse("challenge was received by client", challengeReceived.get());
+
+        pumpServerToClient();
+
+        assertTrue("challenge was not received by client", challengeReceived.get());
+        assertFalse("response was received by server", responseReceived.get());
+
+        pumpClientToServer();
+
+        assertTrue("response was received by server", responseReceived.get());
+        assertFalse("outcome was received by client", outcomeReceived.get());
+
+        pumpServerToClient();
+
+        assertTrue("outcome was received by client", outcomeReceived.get());
+
+        assertEquals("Unexpected SASL outcome at client", SaslOutcome.PN_SASL_OK, clientSasl.getOutcome());
+    }
+
+    private static class ServerSaslHandling implements SaslListener
+    {
+        AtomicBoolean initReceived = new AtomicBoolean();
+        AtomicBoolean responseReceived = new AtomicBoolean();
+
+        public ServerSaslHandling(AtomicBoolean initReceived, AtomicBoolean responseReceived)
+        {
+            this.initReceived = initReceived;
+            this.responseReceived = responseReceived;
+        }
+
+        @Override
+        public void onSaslInit(Sasl s, Transport t)
+        {
+            assertArrayEquals("Server should now know the client's chosen mechanism.",
+                    new String[]{TESTMECH1}, s.getRemoteMechanisms());
+
+            byte[] serverReceivedInitialBytes = new byte[s.pending()];
+            s.recv(serverReceivedInitialBytes, 0, serverReceivedInitialBytes.length);
+
+            assertArrayEquals("Server should now know the client's initial response.",
+                    INITIAL_RESPONSE_BYTES, serverReceivedInitialBytes);
+
+            s.send(CHALLENGE_BYTES, 0, CHALLENGE_BYTES.length);
+
+            assertFalse("Should not have already received init", initReceived.getAndSet(true));
+        }
+
+        @Override
+        public void onSaslResponse(Sasl s, Transport t)
+        {
+            byte[] serverReceivedResponseBytes = new byte[s.pending()];
+            s.recv(serverReceivedResponseBytes, 0, serverReceivedResponseBytes.length);
+
+            assertArrayEquals("Server should now know the client's response", RESPONSE_BYTES, serverReceivedResponseBytes);
+
+            s.send(ADDITIONAL_DATA_BYTES, 0, ADDITIONAL_DATA_BYTES.length);
+            s.done(SaslOutcome.PN_SASL_OK);
+
+            assertFalse("Should not have already received response", responseReceived.getAndSet(true));
+        }
+
+        @Override
+        public void onSaslMechanisms(Sasl s, Transport t) { }
+
+        @Override
+        public void onSaslChallenge(Sasl s, Transport t) { }
+
+        @Override
+        public void onSaslOutcome(Sasl s, Transport t) { }
+    }
+
+    private static class ClientSaslHandling implements SaslListener
+    {
+        AtomicBoolean mechanismsReceived = new AtomicBoolean();
+        AtomicBoolean challengeReceived = new AtomicBoolean();
+        AtomicBoolean outcomeReceived = new AtomicBoolean();
+
+        public ClientSaslHandling(AtomicBoolean mechanismsReceived, AtomicBoolean challengeReceived, AtomicBoolean outcomeReceived)
+        {
+            this.mechanismsReceived = mechanismsReceived;
+            this.challengeReceived = challengeReceived;
+            this.outcomeReceived = outcomeReceived;
+        }
+
+        @Override
+        public void onSaslMechanisms(Sasl s, Transport t)
+        {
+            assertArrayEquals("Client should now know the server's mechanisms.",
+                    new String[]{TESTMECH1, TESTMECH2}, s.getRemoteMechanisms());
+            assertEquals("Unexpected SASL outcome at client", SaslOutcome.PN_SASL_NONE, s.getOutcome());
+
+            s.setMechanisms(TESTMECH1);
+            s.send(INITIAL_RESPONSE_BYTES, 0, INITIAL_RESPONSE_BYTES.length);
+
+            assertFalse("Should not have already received mechanisms", mechanismsReceived.getAndSet(true));
+        }
+
+        @Override
+        public void onSaslChallenge(Sasl s, Transport t)
+        {
+            byte[] clientReceivedChallengeBytes = new byte[s.pending()];
+            s.recv(clientReceivedChallengeBytes, 0, clientReceivedChallengeBytes.length);
+
+            assertEquals("Unexpected SASL outcome at client", SaslOutcome.PN_SASL_NONE, s.getOutcome());
+            assertArrayEquals("Client should now know the server's challenge",
+                              CHALLENGE_BYTES, clientReceivedChallengeBytes);
+
+            s.send(RESPONSE_BYTES, 0, RESPONSE_BYTES.length);
+
+            assertFalse("Should not have already received challenge", challengeReceived.getAndSet(true));
+        }
+
+        @Override
+        public void onSaslOutcome(Sasl s, Transport t)
+        {
+            assertEquals("Unexpected SASL outcome at client", SaslOutcome.PN_SASL_OK, s.getOutcome());
+
+            byte[] clientReceivedAdditionalBytes = new byte[s.pending()];
+            s.recv(clientReceivedAdditionalBytes, 0, clientReceivedAdditionalBytes.length);
+
+            assertArrayEquals("Client should now know the server's outcome additional data", clientReceivedAdditionalBytes,
+                    clientReceivedAdditionalBytes);
+
+            assertFalse("Should not have already received outcome", outcomeReceived.getAndSet(true));
+        }
+
+        @Override
+        public void onSaslInit(Sasl s, Transport t) { }
+
+        @Override
+        public void onSaslResponse(Sasl s, Transport t) { }
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@qpid.apache.org
For additional commands, e-mail: commits-help@qpid.apache.org