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 2018/11/12 17:46:09 UTC

mina-sshd git commit: [SSHD-860] Using lazy-loaded KeyPair(s) when wrapping several KeyIdentityProvider(s) into a single logical one

Repository: mina-sshd
Updated Branches:
  refs/heads/master 44576a59a -> 3bea9ff05


[SSHD-860] Using lazy-loaded KeyPair(s) when wrapping several KeyIdentityProvider(s) into a single logical one


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

Branch: refs/heads/master
Commit: 3bea9ff05503bb11665c2b0db6607eef4f7ccd28
Parents: 44576a5
Author: Lyor Goldstein <lg...@apache.org>
Authored: Mon Nov 12 07:59:54 2018 +0200
Committer: Lyor Goldstein <lg...@apache.org>
Committed: Mon Nov 12 19:51:49 2018 +0200

----------------------------------------------------------------------
 .../common/keyprovider/KeyIdentityProvider.java |  33 +++++-
 .../keyprovider/MultiKeyIdentityIterator.java   |  88 +++++++++++++++
 .../keyprovider/MultiKeyIdentityProvider.java   |  53 +++++++++
 .../apache/sshd/common/util/GenericUtils.java   |  28 +++++
 .../MultiKeyIdentityProviderTest.java           | 108 +++++++++++++++++++
 .../sshd/client/session/ClientSession.java      |   2 +-
 6 files changed, 306 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
index 72b063f..f03b68b 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyIdentityProvider.java
@@ -24,7 +24,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.function.Function;
-import java.util.function.Supplier;
 
 import org.apache.sshd.common.util.GenericUtils;
 
@@ -128,9 +127,17 @@ public interface KeyIdentityProvider {
      * @param providers The providers - ignored if {@code null}/empty (i.e., returns
      * {@link #EMPTY_KEYS_PROVIDER})
      * @return The wrapping provider
+     * @see MultiKeyIdentityProvider
      */
     static KeyIdentityProvider multiProvider(Collection<? extends KeyIdentityProvider> providers) {
-        return GenericUtils.isEmpty(providers) ? EMPTY_KEYS_PROVIDER : wrapKeyPairs(iterableOf(providers));
+        int numProviders = GenericUtils.size(providers);
+        if (numProviders <= 0) {
+            return EMPTY_KEYS_PROVIDER;
+        } else if (numProviders == 1) {
+            return GenericUtils.head(providers);
+        } else {
+            return new MultiKeyIdentityProvider(providers);
+        }
     }
 
     /**
@@ -141,9 +148,25 @@ public interface KeyIdentityProvider {
      * @return The wrapping iterable
      */
     static Iterable<KeyPair> iterableOf(Collection<? extends KeyIdentityProvider> providers) {
-        Iterable<Supplier<Iterable<KeyPair>>> keysSuppliers =
-                GenericUtils.<KeyIdentityProvider, Supplier<Iterable<KeyPair>>>wrapIterable(providers, p -> p::loadKeys);
-        return GenericUtils.multiIterableSuppliers(keysSuppliers);
+        int numProviders = GenericUtils.size(providers);
+        if (numProviders <= 0) {
+            return Collections.emptyList();
+        } else if (numProviders == 1) {
+            KeyIdentityProvider p = GenericUtils.head(providers);
+            return p.loadKeys();
+        } else {
+            return new Iterable<KeyPair>() {
+                @Override
+                public Iterator<KeyPair> iterator() {
+                    return new MultiKeyIdentityIterator(providers);
+                }
+
+                @Override
+                public String toString() {
+                    return Iterable.class.getSimpleName() + "[of(providers)]";
+                }
+            };
+        }
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java
new file mode 100644
index 0000000..5ba5386
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityIterator.java
@@ -0,0 +1,88 @@
+/*
+ * 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.common.keyprovider;
+
+import java.security.KeyPair;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Iterates over several {@link KeyIdentityProvider}-s exhausting their
+ * keys one by one (lazily).
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MultiKeyIdentityIterator implements Iterator<KeyPair> {
+    protected Iterator<KeyPair> currentProvider;
+    protected boolean finished;
+    private final Iterator<? extends KeyIdentityProvider> providers;
+
+    public MultiKeyIdentityIterator(Iterable<? extends KeyIdentityProvider> providers) {
+        this.providers = (providers == null) ? null : providers.iterator();
+    }
+
+    public Iterator<? extends KeyIdentityProvider> getProviders() {
+        return providers;
+    }
+
+    @Override
+    public boolean hasNext() {
+        if (finished) {
+            return false;
+        }
+
+        Iterator<? extends KeyIdentityProvider> provs = getProviders();
+        if (provs == null) {
+            finished = true;
+            return false;
+        }
+
+        if ((currentProvider != null) && currentProvider.hasNext()) {
+            return true;
+        }
+
+        while (provs.hasNext()) {
+            KeyIdentityProvider p = provs.next();
+            Iterable<KeyPair> keys = (p == null) ? null : p.loadKeys();
+            currentProvider = (keys == null) ? null : keys.iterator();
+
+            if ((currentProvider != null) && currentProvider.hasNext()) {
+                return true;
+            }
+        }
+
+        // exhausted all providers
+        finished = false;
+        return false;
+    }
+
+    @Override
+    public KeyPair next() {
+        if (finished) {
+            throw new NoSuchElementException("All identities have been exhausted");
+        }
+
+        if (currentProvider == null) {
+            throw new IllegalStateException("'next()' called without asking 'hasNext()'");
+        }
+
+        return currentProvider.next();
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java
new file mode 100644
index 0000000..18304ff
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.common.keyprovider;
+
+import java.security.KeyPair;
+import java.util.Collections;
+import java.util.Iterator;
+
+/**
+ * Aggregates several {@link KeyIdentityProvider}-s into a single logical
+ * one that (lazily) exposes the keys from each aggregated provider
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class MultiKeyIdentityProvider implements KeyIdentityProvider {
+    protected final Iterable<? extends KeyIdentityProvider> providers;
+
+    public MultiKeyIdentityProvider(Iterable<? extends KeyIdentityProvider> providers) {
+        this.providers = providers;
+    }
+
+    @Override
+    public Iterable<KeyPair> loadKeys() {
+        return new Iterable<KeyPair>() {
+            @Override
+            public Iterator<KeyPair> iterator() {
+                return (providers == null) ? Collections.emptyIterator() : new MultiKeyIdentityIterator(providers);
+            }
+
+            @Override
+            public String toString() {
+                return Iterable.class.getSimpleName() + "[" + MultiKeyIdentityProvider.class.getSimpleName() + "]";
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
index b84dbd4..4dba3f2 100644
--- a/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/GenericUtils.java
@@ -27,6 +27,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Deque;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -38,6 +39,7 @@ import java.util.NavigableMap;
 import java.util.NavigableSet;
 import java.util.Objects;
 import java.util.Set;
+import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
 import java.util.concurrent.ExecutionException;
@@ -830,6 +832,32 @@ public final class GenericUtils {
     }
 
     /**
+     * Returns the first element in iterable - it has some optimization for {@link List}-s
+     * {@link Deque}-s and {@link SortedSet}s.
+     *
+     * @param <T> Type of element
+     * @param it The {@link Iterable} instance - ignored if {@code null}/empty
+     * @return first element by iteration or {@code null} if none available
+     */
+    public static <T> T head(Iterable<? extends T> it) {
+        if (it == null) {
+            return null;
+        } else if (it instanceof Deque<?>) {   // check before (!) instanceof List since LinkedList implements List
+            Deque<? extends T> l = (Deque<? extends T>) it;
+            return (l.size() > 0) ? l.getFirst() : null;
+        } else if (it instanceof List<?>) {
+            List<? extends T> l = (List<? extends T>) it;
+            return (l.size() > 0) ? l.get(0) : null;
+        } else if (it instanceof SortedSet<?>) {
+            SortedSet<? extends T> s = (SortedSet<? extends T>) it;
+            return (s.size() > 0) ? s.first() : null;
+        } else {
+            Iterator<? extends T> iter = it.iterator();
+            return ((iter == null) || (!iter.hasNext())) ? null : iter.next();
+        }
+    }
+
+    /**
      * Resolves to an always non-{@code null} iterator
      *
      * @param <T> Type of value being iterated

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java
new file mode 100644
index 0000000..5a24da2
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/keyprovider/MultiKeyIdentityProviderTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.common.keyprovider;
+
+import java.security.KeyPair;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+import org.mockito.Mockito;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class MultiKeyIdentityProviderTest extends JUnitTestSupport {
+    public MultiKeyIdentityProviderTest() {
+        super();
+    }
+
+    @Test   // see SSHD-860
+    public void testLazyKeyIdentityMultiProvider() {
+        List<KeyPair> expected = new ArrayList<>();
+        for (int index = 1; index <= Short.SIZE; index++) {
+            PublicKey pub = Mockito.mock(PublicKey.class);
+            PrivateKey prv = Mockito.mock(PrivateKey.class);
+            expected.add(new KeyPair(pub, prv));
+        }
+
+        Collection<KeyIdentityProvider> providers = new ArrayList<>();
+        AtomicInteger position = new AtomicInteger(0);
+        for (int startIndex = 0, count = expected.size(), slice = count / 3; startIndex < count;) {
+            int nextIndex = Math.min(count, startIndex + slice);
+            Collection<KeyPair> keys = expected.subList(startIndex, nextIndex);
+            providers.add(wrapKeyPairs(position, keys));
+            startIndex = nextIndex;
+        }
+
+        KeyIdentityProvider multiProvider = KeyIdentityProvider.multiProvider(providers);
+        assertObjectInstanceOf(MultiKeyIdentityProvider.class.getSimpleName(), MultiKeyIdentityProvider.class, multiProvider);
+
+        Iterable<KeyPair> keys = multiProvider.loadKeys();
+        Iterator<KeyPair> iter = keys.iterator();
+        for (int index = 0, count = expected.size(); index < count; index++) {
+            KeyPair kpExpected = expected.get(index);
+            assertTrue("Premature keys exhaustion after " + index + " iterations", iter.hasNext());
+            KeyPair kpActual = iter.next();
+            assertSame("Mismatched key at index=" + index, kpExpected, kpActual);
+            assertEquals("Mismatched requested lazy key position", index + 1, position.get());
+        }
+
+        assertFalse("Not all keys exhausted", iter.hasNext());
+    }
+
+    private static KeyIdentityProvider wrapKeyPairs(AtomicInteger position, Iterable<KeyPair> keys) {
+        return new KeyIdentityProvider() {
+            @Override
+            public Iterable<KeyPair> loadKeys() {
+                return new Iterable<KeyPair>() {
+                    @Override
+                    public Iterator<KeyPair> iterator() {
+                        return new Iterator<KeyPair>() {
+                            private final Iterator<KeyPair> iter = keys.iterator();
+
+                            @Override
+                            public boolean hasNext() {
+                                return iter.hasNext();
+                            }
+
+                            @Override
+                            public KeyPair next() {
+                                position.incrementAndGet();
+                                return iter.next();
+                            }
+                        };
+                    }
+                };
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/3bea9ff0/sshd-core/src/main/java/org/apache/sshd/client/session/ClientSession.java
----------------------------------------------------------------------
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 808586f..63343cb 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
@@ -378,7 +378,7 @@ public interface ClientSession
      * @see ClientSession#getKeyPairProvider()
      */
     static KeyIdentityProvider providerOf(ClientSession session) {
-        return session == null
+        return (session == null)
             ? KeyIdentityProvider.EMPTY_KEYS_PROVIDER
             : KeyIdentityProvider.resolveKeyIdentityProvider(
                     session.getRegisteredIdentities(), session.getKeyPairProvider());