[SSHD-860] UserAuthPublicKeyIterator uses lazy loading of public key identities both from agent and client session
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo
Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/2c2982d6
Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/2c2982d6
Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/2c2982d6
Branch: refs/heads/master
Commit: 2c2982d67f39eeafa938ba510f2bc4f126e3b72c
Parents: c507cb0
Author: Lyor Goldstein <***@apache.org>
Authored: Wed Nov 7 12:16:37 2018 +0200
Committer: Lyor Goldstein <***@apache.org>
Committed: Wed Nov 7 20:06:39 2018 +0200
----------------------------------------------------------------------
CHANGES.md | 6 +
.../auth/AuthenticationIdentitiesProvider.java | 8 +-
.../apache/sshd/common/util/GenericUtils.java | 85 -----------
.../util/helper/LazyIterablesConcatenator.java | 114 ++++++++++++++
.../util/helper/LazyMatchingTypeIterable.java | 82 ++++++++++
.../util/helper/LazyMatchingTypeIterator.java | 105 +++++++++++++
.../sshd/common/util/GenericUtilsTest.java | 57 -------
.../helper/LazyIterablesConcatenatorTest.java | 71 +++++++++
.../helper/LazyMatchingTypeIteratorTest.java | 98 ++++++++++++
.../client/auth/pubkey/KeyPairIdentity.java | 4 +-
.../auth/pubkey/UserAuthPublicKeyIterator.java | 152 +++++++++++++++----
11 files changed, 606 insertions(+), 176 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/CHANGES.md
----------------------------------------------------------------------
diff --git a/CHANGES.md b/CHANGES.md
index 0f44a1b..c43339c 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -26,3 +26,9 @@ accept also an `AttributeRepository` connection context argument (propagated fro
user to try and repeat an encrypted private key decoding using a different password.
* `SshAgent#getIdentities` returns an `Iterable` rather than a `List`
+
+
+## Behavioral changes
+
+* [SSHD-860](https://issues.apache.org/jira/browse/SSHD-860) `UserAuthPublicKeyIterator` uses lazy loading
+of public key identities both from agent and client session
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java b/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
index 3fa61ba..ee7c856 100644
--- a/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
+++ b/sshd-common/src/main/java/org/apache/sshd/client/auth/AuthenticationIdentitiesProvider.java
@@ -26,7 +26,7 @@ import java.util.List;
import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
import org.apache.sshd.common.config.keys.KeyUtils;
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.helper.LazyMatchingTypeIterable;
/**
* @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
@@ -83,17 +83,17 @@ public interface AuthenticationIdentitiesProvider extends KeyIdentityProvider, P
return new AuthenticationIdentitiesProvider() {
@Override
public Iterable<KeyPair> loadKeys() {
- return GenericUtils.lazySelectMatchingTypes(identities, KeyPair.class);
+ return LazyMatchingTypeIterable.lazySelectMatchingTypes(identities, KeyPair.class);
}
@Override
public Iterable<String> loadPasswords() {
- return GenericUtils.lazySelectMatchingTypes(identities, String.class);
+ return LazyMatchingTypeIterable.lazySelectMatchingTypes(identities, String.class);
}
@Override
public Iterable<?> loadIdentities() {
- return GenericUtils.lazySelectMatchingTypes(identities, Object.class);
+ return LazyMatchingTypeIterable.lazySelectMatchingTypes(identities, Object.class);
}
};
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/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 52cf171..b84dbd4 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
@@ -36,7 +36,6 @@ import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
-import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
@@ -868,90 +867,6 @@ public final class GenericUtils {
}
/**
- * @param <T> Generic selected identities type
- * @param The source values - ignored if {@code null}
- * @param type The (never @code null) type of values to select - any value
- * whose type is assignable to this type will be selected by the iterator.
- * @return {@link Iterable} whose {@link Iterator} selects only values
- * matching the specific type. <b>Note:</b> the matching values are not
- * pre-calculated (hence the "lazy" denomination) - i.e.,
- * the match is performed only when {@link Iterator#hasNext()} is called.
- */
- public static <T> Iterable<T> lazySelectMatchingTypes(Iterable<?> values, Class<T> type) {
- Objects.requireNonNull(type, "No type selector specified");
- if (values == null) {
- return Collections.emptyList();
- }
-
- return new Iterable<T>() {
- @Override
- public Iterator<T> iterator() {
- return lazySelectMatchingTypes(values.iterator(), type);
- }
-
- @Override
- public String toString() {
- return Iterable.class.getSimpleName() + "[lazy-select](" + type.getSimpleName() + ")";
- }
- };
- }
-
- /**
- * @param <T> Generic selected identities type
- * @param The source values - ignored if {@code null}
- * @param type The (never @code null) type of values to select - any value
- * whose type is assignable to this type will be selected by the iterator.
- * @return An {@link Iterator} whose {@code next()} call selects only values
- * matching the specific type. <b>Note:</b> the matching values are not
- * pre-calculated (hence the "lazy" denomination) - i.e.,
- * the match is performed only when {@link Iterator#hasNext()} is called.
- */
- public static <T> Iterator<T> lazySelectMatchingTypes(Iterator<?> values, Class<T> type) {
- Objects.requireNonNull(type, "No type selector specified");
- if (values == null) {
- return Collections.emptyIterator();
- }
-
- return new Iterator<T>() {
- private boolean finished;
- private T nextValue;
-
- @Override
- public boolean hasNext() {
- if (finished) {
- return false;
- }
-
- nextValue = selectNextMatchingValue(values, type);
- if (nextValue == null) {
- finished = true;
- }
-
- return !finished;
- }
-
- @Override
- public T next() {
- if (finished) {
- throw new NoSuchElementException("All values have been exhausted");
- }
- if (nextValue == null) {
- throw new IllegalStateException("'next()' called without asking 'hasNext()'");
- }
-
- T v = nextValue;
- nextValue = null; // so it will be re-fetched when 'hasNext' is called
- return v;
- }
-
- @Override
- public String toString() {
- return Iterator.class.getSimpleName() + "[lazy-select](" + type.getSimpleName() + ")";
- }
- };
- }
-
- /**
* @param <T> Generic return type
* @param The source values - ignored if {@code null}
* @param type The (never @code null) type of values to select - any value
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java
new file mode 100644
index 0000000..2e1b042
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenator.java
@@ -0,0 +1,114 @@
+/*
+ * 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.util.helper;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Creates a "smooth" wrapping {@link Iterable} using several
+ * underlying ones to provide the values. The "lazy" denomination
+ * is due to the fact that no iterable is consulted until the one(s) before
+ * it have been fully exhausted.
+ *
+ * @param <T> Type of element being iterared
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LazyIterablesConcatenator<T> implements Iterable<T> {
+ private final Iterable<? extends Iterable<? extends T>> iterables;
+
+ public LazyIterablesConcatenator(Iterable<? extends Iterable<? extends T>> iterables) {
+ this.iterables = iterables;
+ }
+
+ public Iterable<? extends Iterable<? extends T>> getIterables() {
+ return iterables;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return new Iterator<T>() {
+ @SuppressWarnings("synthetic-access")
+ private final Iterator<? extends Iterable<? extends T>> itit =
+ (iterables == null) ? Collections.emptyIterator() : iterables.iterator();
+ private Iterator<? extends T> currentIterator;
+ private boolean finished;
+
+ @Override
+ public boolean hasNext() {
+ if (finished) {
+ return false;
+ }
+
+ // Do we have a current iterator, and if so does it still have values in it
+ if ((currentIterator != null) && currentIterator.hasNext()) {
+ return true;
+ }
+
+ while (itit.hasNext()) {
+ Iterable<? extends T> currentIterable = itit.next();
+ currentIterator = currentIterable.iterator();
+ if (currentIterator.hasNext()) {
+ return true;
+ }
+ }
+
+ // exhausted all
+ finished = true;
+ return false;
+ }
+
+ @Override
+ public T next() {
+ if (finished) {
+ throw new NoSuchElementException("All elements have been exhausted");
+ }
+
+ if (currentIterator == null) {
+ throw new IllegalStateException("'next()' called without a preceding 'hasNext()' query");
+ }
+
+ return currentIterator.next();
+ }
+
+ @Override
+ public String toString() {
+ return Iterator.class.getSimpleName() + "[lazy-concat]";
+ }
+ };
+ }
+
+ @Override
+ public String toString() {
+ return Iterable.class.getSimpleName() + "[lazy-concat]";
+ }
+
+ /**
+ * @param <T> Type if iterated element
+ * @param iterables The iterables to concatenate - ignored if {@code null}
+ * @return An {@link Iterable} that goes over all the elements in the wrapped
+ * iterables one after the other. The denomination "lazy" indicates
+ * that no iterable is consulted until the previous one has been fully exhausted.
+ */
+ public static <T> Iterable<T> lazyConcatenateIterables(Iterable<? extends Iterable<? extends T>> iterables) {
+ return (iterables == null) ? Collections.emptyList() : new LazyIterablesConcatenator<>(iterables);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java
new file mode 100644
index 0000000..267c661
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterable.java
@@ -0,0 +1,82 @@
+/*
+ * 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.util.helper;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Objects;
+
+/**
+ * Provides a selective {@link Iterable} over values that match a
+ * specific type out of all available. The "lazy" denomination
+ * is due to the fact that the next matching value is calculated on-the-fly
+ * every time {@link Iterator#hasNext()} is called
+ *
+ * @param <T> Type of element being selected
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LazyMatchingTypeIterable<T> implements Iterable<T> {
+ private final Iterable<?> values;
+ private final Class<T> type;
+
+ public LazyMatchingTypeIterable(Iterable<?> values, Class<T> type) {
+ this.values = values;
+ this.type = Objects.requireNonNull(type, "No type selector specified");
+ }
+
+ public Iterable<?> getValues() {
+ return values;
+ }
+
+ public Class<T> getType() {
+ return type;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ Iterable<?> vals = getValues();
+ if (vals == null) {
+ return Collections.emptyIterator();
+ }
+
+ return LazyMatchingTypeIterator.lazySelectMatchingTypes(vals.iterator(), getType());
+ }
+
+ @Override
+ public String toString() {
+ Class<?> t = getType();
+ return Iterable.class.getSimpleName() + "[lazy-select](" + t.getSimpleName() + ")";
+ }
+
+ /**
+ * @param <T> Type if iterated element
+ * @param The source values - ignored if {@code null}
+ * @param type The (never @code null) type of values to select - any value
+ * whose type is assignable to this type will be selected by the iterator.
+ * @return {@link Iterable} whose {@link Iterator} selects only values
+ * matching the specific type. <b>Note:</b> the matching values are not
+ * pre-calculated (hence the "lazy" denomination) - i.e.,
+ * the match is performed only when {@link Iterator#hasNext()} is called.
+ */
+ public static <T> Iterable<T> lazySelectMatchingTypes(Iterable<?> values, Class<T> type) {
+ Objects.requireNonNull(type, "No type selector specified");
+ return (values == null) ? Collections.emptyList() : new LazyMatchingTypeIterable<>(values, type);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java
new file mode 100644
index 0000000..d0fd18c
--- /dev/null
+++ b/sshd-common/src/main/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIterator.java
@@ -0,0 +1,105 @@
+/*
+ * 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.util.helper;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+import org.apache.sshd.common.util.GenericUtils;
+
+/**
+ * An {@link Iterator} that selects only objects of a certain type from
+ * the underlying available ones. The "lazy" denomination is due
+ * to the fact that selection occurs only when {@link #hasNext()} is called
+ *
+ * @param <T> Type of iterated element
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+public class LazyMatchingTypeIterator<T> implements Iterator<T> {
+ protected boolean finished;
+ protected T nextValue;
+
+ private final Iterator<?> values;
+ private final Class<T> type;
+
+ public LazyMatchingTypeIterator(Iterator<?> values, Class<T> type) {
+ this.values = values;
+ this.type = Objects.requireNonNull(type, "No type selector specified");
+ }
+
+ public Iterator<?> getValues() {
+ return values;
+ }
+
+ public Class<T> getType() {
+ return type;
+ }
+
+ @Override
+ public boolean hasNext() {
+ if (finished) {
+ return false;
+ }
+
+ nextValue = GenericUtils.selectNextMatchingValue(getValues(), getType());
+ if (nextValue == null) {
+ finished = true;
+ }
+
+ return !finished;
+ }
+
+ @Override
+ public T next() {
+ if (finished) {
+ throw new NoSuchElementException("All values have been exhausted");
+ }
+ if (nextValue == null) {
+ throw new IllegalStateException("'next()' called without asking 'hasNext()'");
+ }
+
+ T v = nextValue;
+ nextValue = null; // so it will be re-fetched when 'hasNext' is called
+ return v;
+ }
+
+ @Override
+ public String toString() {
+ Class<?> t = getType();
+ return Iterator.class.getSimpleName() + "[lazy-select](" + t.getSimpleName() + ")";
+ }
+
+ /**
+ * @param <T> Type if iterated element
+ * @param The source values - ignored if {@code null}
+ * @param type The (never @code null) type of values to select - any value
+ * whose type is assignable to this type will be selected by the iterator.
+ * @return An {@link Iterator} whose {@code next()} call selects only values
+ * matching the specific type. <b>Note:</b> the matching values are not
+ * pre-calculated (hence the "lazy" denomination) - i.e.,
+ * the match is performed only when {@link Iterator#hasNext()} is called.
+ */
+ public static <T> Iterator<T> lazySelectMatchingTypes(Iterator<?> values, Class<T> type) {
+ Objects.requireNonNull(type, "No type selector specified");
+ return (values == null) ? Collections.emptyIterator() : new LazyMatchingTypeIterator<>(values, type);
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
index 8470385..2b25f4a 100644
--- a/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/GenericUtilsTest.java
@@ -19,21 +19,10 @@
package org.apache.sshd.common.util;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.time.LocalTime;
-import java.time.temporal.Temporal;
import java.util.Arrays;
-import java.util.Collection;
import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
import org.apache.sshd.util.test.JUnitTestSupport;
import org.apache.sshd.util.test.NoIoTestCase;
@@ -184,50 +173,4 @@ public class GenericUtilsTest extends JUnitTestSupport {
assertEquals("s1 vs. s2", Integer.signum(s1.compareTo(s2)), Integer.signum(GenericUtils.compare(c1, c2)));
assertEquals("s2 vs. s1", Integer.signum(s2.compareTo(s1)), Integer.signum(GenericUtils.compare(c2, c1)));
}
-
- @Test
- public void testLazySelectMatchingTypes() {
- Collection<String> strings = Arrays.asList(
- getCurrentTestName(),
- getClass().getSimpleName(),
- getClass().getPackage().getName());
- Collection<Temporal> times = Arrays.asList(
- LocalDateTime.now(),
- LocalTime.now(),
- LocalDate.now());
- List<Object> values = Stream.concat(strings.stream(), times.stream()).collect(Collectors.toList());
- AtomicInteger matchCount = new AtomicInteger(0);
- for (int index = 1, count = values.size(); index <= count; index++) {
- Collections.shuffle(values);
- Class<?> type = ((index & 0x01) == 0) ? String.class : Temporal.class;
- Iterator<?> lazy = GenericUtils.lazySelectMatchingTypes(
- new Iterator<Object>() {
- private final Iterator<?> iter = values.iterator();
-
- {
- matchCount.set(0);
- }
-
- @Override
- public boolean hasNext() {
- return iter.hasNext();
- }
-
- @Override
- public Object next() {
- Object v = iter.next();
- if (type.isInstance(v)) {
- matchCount.incrementAndGet();
- }
- return v;
- }
- }, type);
- Set<?> expected = (type == String.class) ? new HashSet<>(strings) : new HashSet<>(times);
- for (int c = 1; lazy.hasNext(); c++) {
- Object o = lazy.next();
- assertEquals("Mismatched match count for " + o, c, matchCount.get());
- assertTrue("Unexpected value: " + o, expected.remove(o));
- }
- }
- }
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java
new file mode 100644
index 0000000..06d2e7d
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyIterablesConcatenatorTest.java
@@ -0,0 +1,71 @@
+/*
+ * 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.util.helper;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LazyIterablesConcatenatorTest extends JUnitTestSupport {
+ public LazyIterablesConcatenatorTest() {
+ super();
+ }
+
+ @Test
+ public void testLazyConcatenateIterables() {
+ Collection<String> l1 = Arrays.asList(
+ getCurrentTestName(),
+ getClass().getSimpleName(),
+ getClass().getPackage().getName());
+ Collection<String> l2 = Arrays.asList(
+ LocalDateTime.now().toString(),
+ LocalTime.now().toString(),
+ LocalDate.now().toString());
+ List<String> expected = Stream.concat(l1.stream(), l2.stream()).collect(Collectors.toList());
+ Iterable<String> iter = LazyIterablesConcatenator.lazyConcatenateIterables(Arrays.asList(l1, l2));
+ List<String> actual = new ArrayList<>(expected.size());
+ for (int index = 1, count = expected.size(); index <= count; index++) {
+ actual.clear();
+
+ for (String s : iter) {
+ actual.add(s);
+ }
+
+ assertListEquals("Attempt #" + index, expected, actual);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java
----------------------------------------------------------------------
diff --git a/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java
new file mode 100644
index 0000000..43a0d70
--- /dev/null
+++ b/sshd-common/src/test/java/org/apache/sshd/common/util/helper/LazyMatchingTypeIteratorTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.util.helper;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.Temporal;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.sshd.util.test.JUnitTestSupport;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runners.MethodSorters;
+
+/**
+ * TODO Add javadoc
+ *
+ * @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LazyMatchingTypeIteratorTest extends JUnitTestSupport {
+ public LazyMatchingTypeIteratorTest() {
+ super();
+ }
+
+ @Test
+ public void testLazySelectMatchingTypes() {
+ Collection<String> strings = Arrays.asList(
+ getCurrentTestName(),
+ getClass().getSimpleName(),
+ getClass().getPackage().getName());
+ Collection<Temporal> times = Arrays.asList(
+ LocalDateTime.now(),
+ LocalTime.now(),
+ LocalDate.now());
+ List<Object> values = Stream.concat(strings.stream(), times.stream()).collect(Collectors.toList());
+ AtomicInteger matchCount = new AtomicInteger(0);
+ for (int index = 1, count = values.size(); index <= count; index++) {
+ Collections.shuffle(values);
+ Class<?> type = ((index & 0x01) == 0) ? String.class : Temporal.class;
+ Iterator<?> lazy = LazyMatchingTypeIterator.lazySelectMatchingTypes(
+ new Iterator<Object>() {
+ private final Iterator<?> iter = values.iterator();
+
+ {
+ matchCount.set(0);
+ }
+
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ public Object next() {
+ Object v = iter.next();
+ if (type.isInstance(v)) {
+ matchCount.incrementAndGet();
+ }
+ return v;
+ }
+ }, type);
+ Set<?> expected = (type == String.class) ? new HashSet<>(strings) : new HashSet<>(times);
+ for (int c = 1; lazy.hasNext(); c++) {
+ Object o = lazy.next();
+ assertEquals("Mismatched match count for " + o, c, matchCount.get());
+ assertTrue("Unexpected value: " + o, expected.remove(o));
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
index b53c111..deb158c 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/KeyPairIdentity.java
@@ -41,8 +41,8 @@ public class KeyPairIdentity implements PublicKeyIdentity {
public KeyPairIdentity(SignatureFactoriesManager primary, SignatureFactoriesManager secondary, KeyPair pair) {
this.signatureFactories = ValidateUtils.checkNotNullAndNotEmpty(
- SignatureFactoriesManager.resolveSignatureFactories(primary, secondary),
- "No available signature factories");
+ SignatureFactoriesManager.resolveSignatureFactories(primary, secondary),
+ "No available signature factories");
this.pair = Objects.requireNonNull(pair, "No key pair");
}
http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/2c2982d6/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
----------------------------------------------------------------------
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
index 84414a0..73e34ee 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/auth/pubkey/UserAuthPublicKeyIterator.java
@@ -21,15 +21,17 @@ package org.apache.sshd.client.auth.pubkey;
import java.io.IOException;
import java.nio.channels.Channel;
+import java.security.KeyPair;
import java.security.PublicKey;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.stream.Stream;
+import java.util.concurrent.atomic.AtomicReference;
import org.apache.sshd.agent.SshAgent;
import org.apache.sshd.agent.SshAgentFactory;
@@ -37,13 +39,13 @@ import org.apache.sshd.client.session.ClientSession;
import org.apache.sshd.common.FactoryManager;
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
import org.apache.sshd.common.signature.SignatureFactoriesManager;
-import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.helper.LazyIterablesConcatenator;
+import org.apache.sshd.common.util.helper.LazyMatchingTypeIterator;
/**
* @author <a href="mailto:***@mina.apache.org">Apache MINA SSHD Project</a>
*/
public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKeyIdentity> implements Channel {
-
private final AtomicBoolean open = new AtomicBoolean(true);
private Iterator<? extends PublicKeyIdentity> current;
private SshAgent agent;
@@ -51,38 +53,132 @@ public class UserAuthPublicKeyIterator extends AbstractKeyPairIterator<PublicKey
public UserAuthPublicKeyIterator(ClientSession session, SignatureFactoriesManager signatureFactories) throws Exception {
super(session);
- Collection<Stream<? extends PublicKeyIdentity>> identities = new LinkedList<>();
+ try {
+ Collection<Iterable<? extends PublicKeyIdentity>> identities = new ArrayList<>(2);
+ Iterable<? extends PublicKeyIdentity> agentIds = initializeAgentIdentities(session);
+ if (agentIds != null) {
+ identities.add(agentIds);
+ }
- FactoryManager manager = Objects.requireNonNull(session.getFactoryManager(), "No session factory manager");
- SshAgentFactory factory = manager.getAgentFactory();
- if (factory != null) {
+ Iterable<? extends PublicKeyIdentity> sessionIds =
+ initializeSessionIdentities(session, signatureFactories);
+ if (sessionIds != null) {
+ identities.add(sessionIds);
+ }
+
+ if (identities.isEmpty()) {
+ current = Collections.emptyIterator();
+ } else {
+ Iterable<? extends PublicKeyIdentity> keys =
+ LazyIterablesConcatenator.lazyConcatenateIterables(identities);
+ current = LazyMatchingTypeIterator.lazySelectMatchingTypes(keys.iterator(), PublicKeyIdentity.class);
+ }
+ } catch (Exception e) {
try {
- agent = Objects.requireNonNull(factory.createClient(manager), "No agent created");
- Iterable<? extends Map.Entry<PublicKey, String>> agentIds = agent.getIdentities();
- Collection<KeyAgentIdentity> ids = new LinkedList<>();
- for (Map.Entry<PublicKey, String> kp : agentIds) {
- ids.add(new KeyAgentIdentity(agent, kp.getKey(), kp.getValue()));
- }
- if (!ids.isEmpty()) {
- identities.add(ids.stream());
- }
- } catch (Exception e) {
- try {
- closeAgent();
- } catch (Exception err) {
- e.addSuppressed(err);
+ closeAgent();
+ } catch (Exception err) {
+ e.addSuppressed(err);
+ }
+
+ throw e;
+ }
+ }
+
+ protected Iterable<KeyPairIdentity> initializeSessionIdentities(
+ ClientSession session, SignatureFactoriesManager signatureFactories) {
+ return new Iterable<KeyPairIdentity>() {
+ private final String sessionId = session.toString();
+ private final AtomicReference<Iterable<KeyPair>> keysHolder = new AtomicReference<>();
+
+ @Override
+ public Iterator<KeyPairIdentity> iterator() {
+ // Lazy load the keys the 1st time the iterator is called
+ if (keysHolder.get() == null) {
+ KeyIdentityProvider sessionKeysProvider = ClientSession.providerOf(session);
+ keysHolder.set(sessionKeysProvider.loadKeys());
}
- throw e;
+ return new Iterator<KeyPairIdentity>() {
+ private final Iterator<KeyPair> keys;
+
+ {
+ @SuppressWarnings("synthetic-access")
+ Iterable<KeyPair> sessionKeys =
+ Objects.requireNonNull(keysHolder.get(), "No session keys available");
+ keys = sessionKeys.iterator();
+ }
+
+ @Override
+ public boolean hasNext() {
+ return keys.hasNext();
+ }
+
+ @Override
+ public KeyPairIdentity next() {
+ KeyPair kp = keys.next();
+ return new KeyPairIdentity(signatureFactories, session, kp);
+ }
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public String toString() {
+ return KeyPairIdentity.class.getSimpleName() + "[iterator][" + sessionId + "]";
+ }
+ };
}
+
+ @Override
+ public String toString() {
+ return KeyPairIdentity.class.getSimpleName() + "[iterable][" + sessionId + "]";
+ }
+ };
+ }
+
+ protected Iterable<KeyAgentIdentity> initializeAgentIdentities(ClientSession session) throws IOException {
+ FactoryManager manager = Objects.requireNonNull(session.getFactoryManager(), "No session factory manager");
+ SshAgentFactory factory = manager.getAgentFactory();
+ if (factory == null) {
+ return null;
}
- identities.add(Stream.of(ClientSession.providerOf(session))
- .map(KeyIdentityProvider::loadKeys)
- .flatMap(GenericUtils::stream)
- .map(kp -> new KeyPairIdentity(signatureFactories, session, kp)));
+ agent = Objects.requireNonNull(factory.createClient(manager), "No agent created");
+ return new Iterable<KeyAgentIdentity>() {
+ @SuppressWarnings("synthetic-access")
+ private final Iterable<? extends Map.Entry<PublicKey, String>> agentIds = agent.getIdentities();
+ @SuppressWarnings("synthetic-access")
+ private final String agentId = agent.toString();
+
+ @Override
+ public Iterator<KeyAgentIdentity> iterator() {
+ return new Iterator<KeyAgentIdentity>() {
+ @SuppressWarnings("synthetic-access")
+ private final Iterator<? extends Map.Entry<PublicKey, String>> iter = agentIds.iterator();
+
+ @Override
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public KeyAgentIdentity next() {
+ Map.Entry<PublicKey, String> kp = iter.next();
+ return new KeyAgentIdentity(agent, kp.getKey(), kp.getValue());
+ }
+
+ @Override
+ @SuppressWarnings("synthetic-access")
+ public String toString() {
+ return KeyAgentIdentity.class.getSimpleName() + "[iterator][" + agentId + "]";
+ }
+ };
+ }
- current = identities.stream().flatMap(r -> r).iterator();
+ @Override
+ public String toString() {
+ return KeyAgentIdentity.class.getSimpleName() + "[iterable][" + agentId + "]";
+ }
+ };
}
@Override