You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mina.apache.org by lg...@apache.org on 2017/08/14 04:34:04 UTC

mina-sshd git commit: [SSHD-762] Add InteractivePasswordIdentityProvider

Repository: mina-sshd
Updated Branches:
  refs/heads/master d03d98113 -> 1e8ac3095


[SSHD-762] Add InteractivePasswordIdentityProvider


Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/1e8ac309
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/1e8ac309
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/1e8ac309

Branch: refs/heads/master
Commit: 1e8ac3095656fcc55c9a0fb7b64242cca8f3a4c2
Parents: d03d981
Author: Goldstein Lyor <ly...@c-b4.com>
Authored: Sun Aug 13 17:08:07 2017 +0300
Committer: Goldstein Lyor <ly...@c-b4.com>
Committed: Mon Aug 14 07:33:51 2017 +0300

----------------------------------------------------------------------
 README.md                                       |  56 +++++++-
 .../InteractivePasswordIdentityProvider.java    | 140 +++++++++++++++++++
 .../SimpleAccessControlScpEventListener.java    |   1 +
 .../proxyprotocol/ProxyProtocolAcceptor.java    |   1 +
 .../SimpleAccessControlSftpEventListener.java   |   1 +
 ...InteractivePasswordIdentityProviderTest.java |  99 +++++++++++++
 .../client/auth/keyboard/UserInteraction.java   |   6 +-
 7 files changed, 298 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1e8ac309/README.md
----------------------------------------------------------------------
diff --git a/README.md b/README.md
index f1971b3..de1e2dd 100644
--- a/README.md
+++ b/README.md
@@ -1129,7 +1129,9 @@ decide whether to accept a successfully authenticated session.
 
 # Extension modules
 
-There are several extension modules available
+There are several extension modules available - specifically, the _sshd-contrib_ module contains some of them. **Note:** the module contains experimental code that may find its way some time
+in the future to a standard artifact. It is also subject to changes and/or deletion without any prior announcement. Therefore, any code that relies on it should also store a copy of the sources
+in case the classes it used it are modified or deleted.
 
 ## Command line clients
 
@@ -1162,15 +1164,61 @@ The _sshd-ldap_ artifact contains an [LdapPasswordAuthenticator](https://issues.
 
 ## PROXY / SSLH protocol hooks
 
-The code contains [support for "wrapper" protocols](https://issues.apache.org/jira/browse/SSHD-656) such as [PROXY](http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt) or  [sslh](http://www.rutschle.net/tech/sslh.shtml). The idea is that one can register either a `ClientProxyConnector` or `ServerProxyAcceptor` and intercept the 1st packet being sent/received (respectively) **before** it reaches the SSHD code. This gives the programmer the capability to write a front-end that routes outgoing/incoming packets:
+The code contains [support for "wrapper" protocols](https://issues.apache.org/jira/browse/SSHD-656) such as [PROXY](http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt) or [sslh](http://www.rutschle.net/tech/sslh.shtml). The idea is that one can register either a `ClientProxyConnector` or `ServerProxyAcceptor` and intercept the 1st packet being sent/received (respectively) **before** it reaches the SSHD code. This gives the programmer the capability to write a front-end that routes outgoing/incoming packets:
 
 * `SshClient/ClientSesssion#setClientProxyConnector` - sets a proxy that intercepts the 1st packet before being sent to the server
 
 * `SshServer/ServerSession#setServerProxyAcceptor` - sets a proxy that intercept the 1st incoming packet before being processed by the server
 
-## PUTTY key file(s) readers
+## Useful extra components in _sshd-contrib_
+
+* PUTTY key file(s) readers - see `org.apache.sshd.common.config.keys.loader.putty` package - specifically `PuttyKeyUtils#DEFAULT_INSTANCE KeyPairResourceParser`.
+
+
+* `InteractivePasswordIdentityProvider` - helps implement a `PasswordIdentityProvider` by delegating calls to `UserInteraction#getUpdatedPassword`.
+The way to use it would be as follows:
+
+
+```java
+try (ClientSession session = client.connect(login, host, port).await().getSession()) {
+     session.setUserInteraction(...);     // this can also be set at the client level
+     PasswordIdentityProvider passwordIdentityProvider =
+          InteractivePasswordIdentityProvider.providerOf(session, "My prompt");
+     session.setPasswordIdentityProvider(passwordIdentityProvider);
+     session.auth.verify(...timeout...);
+     ... continue with the authenticated session ...
+}
+```
+
+or
+
+
+```java
+UserInteraction ui = ....;
+try (ClientSession session = client.connect(login, host, port).await().getSession()) {
+    PasswordIdentityProvider passwordIdentityProvider =
+         InteractivePasswordIdentityProvider.providerOf(session, ui, "My prompt");
+    session.setPasswordIdentityProvider(passwordIdentityProvider);
+    session.auth.verify(...timeout...);
+     ... continue with the authenticated session ...
+}
+```
+
+
+**Note:** `UserInteraction#isInteractionAllowed` is consulted prior to invoking `getUpdatedPassword` - if it returns _false_ then password retrieval method is not invoked,
+and it is assumed that no more passwords are available
+
+
+* `SimpleAccessControlScpEventListener` - Provides a simple access control by making a distinction between methods that upload data and ones that download it via SCP.
+In order to use it, simply extend it and override its `isFileUpload/DownloadAllowed` methods
+
+
+* `SimpleAccessControlSftpEventListener` - Provides a simple access control by making a distinction between methods that provide SFTP file information - including
+reading data - and those that modify it
+
+
+* `ProxyProtocolAcceptor` - A working prototype to support the PROXY protocol as described in [HAProxy Documentation](http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt)
 
-Part of the _sshd-contrib_ artifact.
 
 # Builtin components
 

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1e8ac309/sshd-contrib/src/main/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProvider.java b/sshd-contrib/src/main/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProvider.java
new file mode 100644
index 0000000..798735b
--- /dev/null
+++ b/sshd-contrib/src/main/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProvider.java
@@ -0,0 +1,140 @@
+/*
+ * 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.sshd.client.auth.password;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.client.session.ClientSessionHolder;
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * <P>
+ * Helps implement a {@link PasswordIdentityProvider} by delegating calls
+ * to {@link UserInteraction#getUpdatedPassword(ClientSession, String, String)}.
+ * The way to use it would be as follows:
+ * </P>
+ * <CODE><PRE>
+ * try (ClientSession session = client.connect(login, host, port).await().getSession()) {
+ *     session.setUserInteraction(...);     // this can also be set at the client level
+ *     PasswordIdentityProvider passwordIdentityProvider =
+ *          InteractivePasswordIdentityProvider.providerOf(session, "My prompt");
+ *     session.setPasswordIdentityProvider(passwordIdentityProvider);
+ *     session.auth.verify(...timeout...);
+ * }
+ *
+ * or
+ *
+ * UserInteraction ui = ....;
+ * try (ClientSession session = client.connect(login, host, port).await().getSession()) {
+ *     PasswordIdentityProvider passwordIdentityProvider =
+ *          InteractivePasswordIdentityProvider.providerOf(session, ui, "My prompt");
+ *     session.setPasswordIdentityProvider(passwordIdentityProvider);
+ *     session.auth.verify(...timeout...);
+ * }
+ * </PRE></CODE>
+ *
+ * <B>Note:</B> {@link UserInteraction#isInteractionAllowed(ClientSession)} is consulted
+ * prior to invoking {@code getUpdatedPassword} - if returns {@code false} then password
+ * retrieval method is not invoked, and it is assumed that no more passwords are available
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class InteractivePasswordIdentityProvider implements Iterator<String>, ClientSessionHolder {
+    /** Special marker to indicate that we exhausted all attempts */
+    protected static final String EOF = "EOF";
+
+    private ClientSession clientSession;
+    private UserInteraction userInteraction;
+    private String prompt;
+    private AtomicReference<String> nextPassword = new AtomicReference<>();
+
+    public InteractivePasswordIdentityProvider(ClientSession clientSession, UserInteraction userInteraction, String prompt) {
+        this.clientSession = Objects.requireNonNull(clientSession, "No client session provided");
+        this.userInteraction = Objects.requireNonNull(userInteraction, "No user interaction instance configured");
+        this.prompt = prompt;
+    }
+
+    @Override
+    public ClientSession getClientSession() {
+        return clientSession;
+    }
+
+    public UserInteraction getUserInteraction() {
+        return userInteraction;
+    }
+
+    public String getPrompt() {
+        return prompt;
+    }
+
+    @Override
+    public boolean hasNext() {
+        String password = nextPassword.get();
+        if (GenericUtils.isEmpty(password)) {
+            password = resolveNextPassword();
+            if (GenericUtils.isEmpty(password)) {
+                password = EOF;
+            }
+            nextPassword.set(password);
+        }
+
+        return !GenericUtils.isSameReference(password, EOF);
+    }
+
+    @Override
+    public String next() {
+        String password = nextPassword.get();
+        if (password == null) {
+            throw new IllegalStateException("hasNext() not called before next()");
+        }
+
+        if (GenericUtils.isSameReference(password, EOF)) {
+            throw new NoSuchElementException("All passwords exhausted");
+        }
+
+        nextPassword.set(null);     // force read of next password when 'hasNext' invoked
+        return password;
+    }
+
+    protected String resolveNextPassword() {
+        ClientSession session = getClientSession();
+        UserInteraction ui = getUserInteraction();
+        if (!ui.isInteractionAllowed(session)) {
+            return null;
+        }
+
+        return ui.getUpdatedPassword(session, getPrompt(), "");
+    }
+
+    public static PasswordIdentityProvider providerOf(ClientSession clientSession, String prompt) {
+        return providerOf(clientSession, (clientSession == null) ? null : clientSession.getUserInteraction(), prompt);
+    }
+
+    public static PasswordIdentityProvider providerOf(ClientSession clientSession, UserInteraction userInteraction, String prompt) {
+        Objects.requireNonNull(clientSession, "No client session provided");
+        Objects.requireNonNull(userInteraction, "No user interaction instance configured");
+        Iterable<String> passwords = () -> new InteractivePasswordIdentityProvider(clientSession, userInteraction, prompt);
+        return () -> passwords;
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1e8ac309/sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java b/sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java
index 527b48d..9139bcb 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/server/scp/SimpleAccessControlScpEventListener.java
@@ -30,6 +30,7 @@ import org.apache.sshd.common.scp.AbstractScpTransferEventListenerAdapter;
 /**
  * Provides a simple access control by making a distinction between methods
  * that upload data and ones that download it
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public abstract class SimpleAccessControlScpEventListener extends AbstractScpTransferEventListenerAdapter {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1e8ac309/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java b/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java
index 29696aa..11eb561 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/server/session/proxyprotocol/ProxyProtocolAcceptor.java
@@ -34,6 +34,7 @@ import org.apache.sshd.server.session.ServerSession;
 /**
  * A working prototype to support PROXY protocol as described in
  * <A HREF="http://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">HAProxy Documentation</A>.
+ *
  * @see <A HREF="https://gist.github.com/codingtony/a8684c9ffa08ad56899f94d3b6c2a040">Tony Bussieres's</A> contribution
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1e8ac309/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java b/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
index 1a80bd0..f1fa81f 100644
--- a/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
+++ b/sshd-contrib/src/main/java/org/apache/sshd/server/subsystem/sftp/SimpleAccessControlSftpEventListener.java
@@ -34,6 +34,7 @@ import org.apache.sshd.server.session.ServerSession;
 /**
  * Provides a simple access control by making a distinction between methods
  * that provide information - including reading data - and those that modify it
+ *
  * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
  */
 public abstract class SimpleAccessControlSftpEventListener extends AbstractSftpEventListenerAdapter {

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1e8ac309/sshd-contrib/src/test/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProviderTest.java
----------------------------------------------------------------------
diff --git a/sshd-contrib/src/test/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProviderTest.java b/sshd-contrib/src/test/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProviderTest.java
new file mode 100644
index 0000000..af52e7a
--- /dev/null
+++ b/sshd-contrib/src/test/java/org/apache/sshd/client/auth/password/InteractivePasswordIdentityProviderTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.sshd.client.auth.password;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.client.auth.keyboard.UserInteraction;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.util.test.BaseTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Matchers;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class InteractivePasswordIdentityProviderTest extends BaseTestSupport {
+    public InteractivePasswordIdentityProviderTest() {
+        super();
+    }
+
+    @Test
+    public void testPasswordEnumerations() {
+        List<String> expected = Arrays.asList(getClass().getSimpleName(), getClass().getPackage().getName(), getCurrentTestName());
+        ClientSession session = Mockito.mock(ClientSession.class);
+        AtomicInteger passwordIndex = new AtomicInteger(0);
+        String prompt = getCurrentTestName();
+        UserInteraction userInteraction = Mockito.mock(UserInteraction.class);
+        Mockito.when(userInteraction.isInteractionAllowed(Matchers.any(ClientSession.class))).thenReturn(Boolean.TRUE);
+        Mockito.when(userInteraction.getUpdatedPassword(Matchers.any(ClientSession.class), Matchers.anyString(), Matchers.anyString()))
+            .thenAnswer(new Answer<String>() {
+                @Override
+                public String answer(InvocationOnMock invocation) throws Throwable {
+                    Object[] args = invocation.getArguments();
+                    assertSame("Mismatched session instance at index=" + passwordIndex, session, args[0]);
+                    assertSame("Mismatched prompt instance at index=" + passwordIndex, prompt, args[1]);
+
+                    int index = passwordIndex.getAndIncrement();
+                    if (index < expected.size()) {
+                        return expected.get(index);
+                    }
+                    assertEquals("Mismatched last call index", expected.size(), index);
+                    return null;
+                }
+            });
+        Mockito.when(session.getUserInteraction()).thenReturn(userInteraction);
+
+        PasswordIdentityProvider provider = InteractivePasswordIdentityProvider.providerOf(session, prompt);
+        Iterable<String> passwords = provider.loadPasswords();
+        int expIndex = 0;
+        for (String actValue : passwords) {
+            String expValue = expected.get(expIndex);
+            assertSame("Mismatched password provided at index=" + expIndex, expValue, actValue);
+            expIndex++;
+        }
+
+        assertEquals("Not all passwords exhausted", expected.size() + 1, passwordIndex.get());
+        assertEquals("Mismatched retrieved passwords count", expIndex, expected.size());
+    }
+
+    @Test
+    public void testInteractionAllowedConsultation() {
+        ClientSession session = Mockito.mock(ClientSession.class);
+        UserInteraction userInteraction = Mockito.mock(UserInteraction.class);
+        Mockito.when(userInteraction.isInteractionAllowed(Matchers.any(ClientSession.class))).thenReturn(Boolean.FALSE);
+        Mockito.when(userInteraction.getUpdatedPassword(Matchers.any(ClientSession.class), Matchers.anyString(), Matchers.anyString()))
+            .thenThrow(new UnsupportedOperationException("Unexpected call"));
+        PasswordIdentityProvider provider = InteractivePasswordIdentityProvider.providerOf(session, userInteraction, getCurrentTestName());
+        Iterable<String> passwords = provider.loadPasswords();
+        for (String p : passwords) {
+            fail("Unexpected password: " + p);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/1e8ac309/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
index a641243..56f5e1d 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/keyboard/UserInteraction.java
@@ -59,9 +59,11 @@ public interface UserInteraction {
     /**
      *
      * @param session The {@link ClientSession}
-     * @return {@code true} if user interaction allowed for this session
+     * @return {@code true} if user interaction allowed for this session (default)
      */
-    boolean isInteractionAllowed(ClientSession session);
+    default boolean isInteractionAllowed(ClientSession session) {
+        return true;
+    }
 
     /**
      * Called if the server sent any extra information beyond the standard