This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 56f10dce77a5108a3624e898aa4526c4e4f12bca Author: Lyor Goldstein <lgoldst...@apache.org> AuthorDate: Thu Apr 1 21:22:59 2021 +0300 [SSHD-1116] Provide SessionContext argument to PasswordIdentityProvider#loadPasswords --- CHANGES.md | 1 + .../auth/AuthenticationIdentitiesProvider.java | 2 +- .../auth/password/PasswordIdentityProvider.java | 102 ++++++++++++++------- .../password/PasswordIdentityProviderTest.java | 11 ++- .../InteractivePasswordIdentityProvider.java | 4 +- .../InteractivePasswordIdentityProviderTest.java | 10 +- .../apache/sshd/client/session/ClientSession.java | 17 ++-- .../common/auth/PasswordAuthenticationTest.java | 2 +- 8 files changed, 96 insertions(+), 53 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d2e0fcf..f99e6ea 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,7 @@ * [SSHD-1110](https://issues.apache.org/jira/browse/SSHD-1110) Replace `Class#newInstance()` calls with `Class#getDefaultConstructor().newInstance()` * [SSHD-1111](https://issues.apache.org/jira/browse/SSHD-1111) Fixed SshClientCliSupport compression option detection * [SSHD-1116](https://issues.apache.org/jira/browse/SSHD-1116) Provide SessionContext argument to HostKeyIdentityProvider#loadHostKeys +* [SSHD-1116](https://issues.apache.org/jira/browse/SSHD-1116) Provide SessionContext argument to PasswordIdentityProvider#loadPasswords * [SSHD-1125](https://issues.apache.org/jira/browse/SSHD-1125) Added option to require immediate close of channel in command `ExitCallback` invocation * [SSHD-1127](https://issues.apache.org/jira/browse/SSHD-1127) Consolidated `SftpSubsystem` support implementations into `SftpSubsystemConfigurator` * [SSHD-1148](https://issues.apache.org/jira/browse/SSHD-1148) Generate a unique thread name for each `SftpSubsystem` instance 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 3b54c9d..6234d33 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 @@ -88,7 +88,7 @@ public interface AuthenticationIdentitiesProvider extends KeyIdentityProvider, P } @Override - public Iterable<String> loadPasswords() { + public Iterable<String> loadPasswords(SessionContext session) { return LazyMatchingTypeIterable.lazySelectMatchingTypes(identities, String.class); } diff --git a/sshd-common/src/main/java/org/apache/sshd/client/auth/password/PasswordIdentityProvider.java b/sshd-common/src/main/java/org/apache/sshd/client/auth/password/PasswordIdentityProvider.java index a0d6eca..90db5c9 100644 --- a/sshd-common/src/main/java/org/apache/sshd/client/auth/password/PasswordIdentityProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/client/auth/password/PasswordIdentityProvider.java @@ -19,12 +19,14 @@ package org.apache.sshd.client.auth.password; +import java.io.IOException; +import java.security.GeneralSecurityException; 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.session.SessionContext; import org.apache.sshd.common.util.GenericUtils; /** @@ -34,11 +36,12 @@ import org.apache.sshd.common.util.GenericUtils; public interface PasswordIdentityProvider { /** - * An "empty" implementation of {@link PasswordIdentityProvider} that returns and empty group of passwords + * An "empty" implementation of {@link PasswordIdentityProvider} that returns an empty group of passwords */ PasswordIdentityProvider EMPTY_PASSWORDS_PROVIDER = new PasswordIdentityProvider() { @Override - public Iterable<String> loadPasswords() { + public Iterable<String> loadPasswords(SessionContext session) + throws IOException, GeneralSecurityException { return Collections.emptyList(); } @@ -49,37 +52,48 @@ public interface PasswordIdentityProvider { }; /** - * Invokes {@link PasswordIdentityProvider#loadPasswords()} and returns the result. Ignores {@code null} providers - * (i.e., returns an empty iterable instance) + * @param session The {@link SessionContext} for invoking this load command - may be {@code null} + * if not invoked within a session context (e.g., offline tool). + * @return The currently available passwords - ignored if {@code null} + * @throws IOException If failed to load the passwords + * @throws GeneralSecurityException If some security issue with the passwords */ - Function<PasswordIdentityProvider, Iterable<String>> LOADER - = p -> (p == null) ? Collections.emptyList() : p.loadPasswords(); - - /** - * @return The currently available passwords - ignored if {@code null} - */ - Iterable<String> loadPasswords(); + Iterable<String> loadPasswords(SessionContext session) + throws IOException, GeneralSecurityException; /** * Creates a "unified" {@link Iterator} of passwords out of 2 possible {@link PasswordIdentityProvider} * - * @param identities The registered passwords - * @param passwords Extra available passwords - * @return The wrapping iterator - * @see #resolvePasswordIdentityProvider(PasswordIdentityProvider, PasswordIdentityProvider) + * @param session The {@link SessionContext} for invoking this load command - may be {@code null} + * if not invoked within a session context (e.g., offline tool). + * @param identities The registered passwords + * @param passwords Extra available passwords + * @return The wrapping iterator + * @throws IOException If failed to load the passwords + * @throws GeneralSecurityException If some security issue with the passwords + * @see #resolvePasswordIdentityProvider(SessionContext, PasswordIdentityProvider, + * PasswordIdentityProvider) */ - static Iterator<String> iteratorOf(PasswordIdentityProvider identities, PasswordIdentityProvider passwords) { - return iteratorOf(resolvePasswordIdentityProvider(identities, passwords)); + static Iterator<String> iteratorOf( + SessionContext session, PasswordIdentityProvider identities, PasswordIdentityProvider passwords) + throws IOException, GeneralSecurityException { + return iteratorOf(session, resolvePasswordIdentityProvider(session, identities, passwords)); } /** * Resolves a non-{@code null} iterator of the available passwords * - * @param provider The {@link PasswordIdentityProvider} - ignored if {@code null} (i.e., return an empty iterator) - * @return A non-{@code null} iterator - which may be empty if no provider or no passwords + * @param session The {@link SessionContext} for invoking this load command - may be {@code null} + * if not invoked within a session context (e.g., offline tool). + * @param provider The {@link PasswordIdentityProvider} - ignored if {@code null} (i.e., return an + * empty iterator) + * @return A non-{@code null} iterator - which may be empty if no provider or no passwords + * @throws IOException If failed to load the passwords + * @throws GeneralSecurityException If some security issue with the passwords */ - static Iterator<String> iteratorOf(PasswordIdentityProvider provider) { - return GenericUtils.iteratorOf((provider == null) ? null : provider.loadPasswords()); + static Iterator<String> iteratorOf(SessionContext session, PasswordIdentityProvider provider) + throws IOException, GeneralSecurityException { + return GenericUtils.iteratorOf((provider == null) ? null : provider.loadPasswords(session)); } /** @@ -93,53 +107,71 @@ public interface PasswordIdentityProvider { * <LI>If both are the same instance then use it.</U> * <LI>Otherwise, returns a wrapper that groups both providers.</LI> * </UL> - * + * + * @param session The {@link SessionContext} for invoking this load command - may be {@code null} if not invoked + * within a session context (e.g., offline tool). * @param identities The registered passwords * @param passwords The extra available passwords * @return The resolved provider - * @see #multiProvider(PasswordIdentityProvider...) + * @see #multiProvider(SessionContext, PasswordIdentityProvider...) */ static PasswordIdentityProvider resolvePasswordIdentityProvider( - PasswordIdentityProvider identities, PasswordIdentityProvider passwords) { + SessionContext session, PasswordIdentityProvider identities, PasswordIdentityProvider passwords) { if ((passwords == null) || (identities == passwords)) { return identities; } else if (identities == null) { return passwords; } else { - return multiProvider(identities, passwords); + return multiProvider(session, identities, passwords); } } /** * Wraps a group of {@link PasswordIdentityProvider} into a single one * + * @param session The {@link SessionContext} for invoking this load command - may be {@code null} if not invoked + * within a session context (e.g., offline tool). * @param providers The providers - ignored if {@code null}/empty (i.e., returns {@link #EMPTY_PASSWORDS_PROVIDER} * @return The wrapping provider - * @see #multiProvider(Collection) + * @see #multiProvider(SessionContext, Collection) */ - static PasswordIdentityProvider multiProvider(PasswordIdentityProvider... providers) { - return multiProvider(GenericUtils.asList(providers)); + static PasswordIdentityProvider multiProvider( + SessionContext session, PasswordIdentityProvider... providers) { + return multiProvider(session, GenericUtils.asList(providers)); } /** * Wraps a group of {@link PasswordIdentityProvider} into a single one * + * @param session The {@link SessionContext} for invoking this load command - may be {@code null} if not invoked + * within a session context (e.g., offline tool). * @param providers The providers - ignored if {@code null}/empty (i.e., returns {@link #EMPTY_PASSWORDS_PROVIDER} * @return The wrapping provider */ - static PasswordIdentityProvider multiProvider(Collection<? extends PasswordIdentityProvider> providers) { - return GenericUtils.isEmpty(providers) ? EMPTY_PASSWORDS_PROVIDER : wrapPasswords(iterableOf(providers)); + static PasswordIdentityProvider multiProvider( + SessionContext session, Collection<? extends PasswordIdentityProvider> providers) { + return GenericUtils.isEmpty(providers) ? EMPTY_PASSWORDS_PROVIDER : wrapPasswords(iterableOf(session, providers)); } /** * Wraps a group of {@link PasswordIdentityProvider} into an {@link Iterable} of their combined passwords * + * @param session The {@link SessionContext} for invoking this load command - may be {@code null} if not invoked + * within a session context (e.g., offline tool). * @param providers The providers - ignored if {@code null}/empty (i.e., returns an empty iterable instance) * @return The wrapping iterable */ - static Iterable<String> iterableOf(Collection<? extends PasswordIdentityProvider> providers) { - Iterable<Supplier<Iterable<String>>> passwordSuppliers = GenericUtils.<PasswordIdentityProvider, - Supplier<Iterable<String>>> wrapIterable(providers, p -> p::loadPasswords); + static Iterable<String> iterableOf( + SessionContext session, Collection<? extends PasswordIdentityProvider> providers) { + Iterable<Supplier<Iterable<String>>> passwordSuppliers + = GenericUtils.<PasswordIdentityProvider, Supplier<Iterable<String>>> wrapIterable( + providers, p -> () -> { + try { + return p.loadPasswords(session); + } catch (IOException | GeneralSecurityException e) { + throw new RuntimeException(e); + } + }); return GenericUtils.multiIterableSuppliers(passwordSuppliers); } @@ -161,6 +193,6 @@ public interface PasswordIdentityProvider { * @return The provider wrapper */ static PasswordIdentityProvider wrapPasswords(Iterable<String> passwords) { - return (passwords == null) ? EMPTY_PASSWORDS_PROVIDER : () -> passwords; + return (passwords == null) ? EMPTY_PASSWORDS_PROVIDER : session -> passwords; } } diff --git a/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java b/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java index 5da42b8..679f515 100644 --- a/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java +++ b/sshd-common/src/test/java/org/apache/sshd/client/auth/password/PasswordIdentityProviderTest.java @@ -19,6 +19,8 @@ package org.apache.sshd.client.auth.password; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -44,7 +46,7 @@ public class PasswordIdentityProviderTest extends JUnitTestSupport { } @Test - public void testMultiProvider() { + public void testMultiProvider() throws IOException, GeneralSecurityException { String[][] values = { { getClass().getSimpleName(), getCurrentTestName() }, { new Date(System.currentTimeMillis()).toString() }, @@ -61,12 +63,13 @@ public class PasswordIdentityProviderTest extends JUnitTestSupport { providers.add(p); } - PasswordIdentityProvider p = PasswordIdentityProvider.multiProvider(providers); + PasswordIdentityProvider p = PasswordIdentityProvider.multiProvider(null, providers); assertProviderContents("Multi", p, expected); } - private static void assertProviderContents(String message, PasswordIdentityProvider p, Iterable<String> expected) { + private static void assertProviderContents(String message, PasswordIdentityProvider p, Iterable<String> expected) + throws IOException, GeneralSecurityException { assertNotNull(message + ": no provider", p); - assertEquals(message, expected, p.loadPasswords()); + assertEquals(message, expected, p.loadPasswords(null)); } } diff --git a/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java index 769d656..7fd50ec 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProvider.java @@ -36,7 +36,7 @@ import org.apache.sshd.common.util.functors.UnaryEquator; * 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> - * + * * <pre> * <code> * try (ClientSession session = client.connect(login, host, port).await().getSession()) { @@ -148,6 +148,6 @@ public class InteractivePasswordIdentityProvider 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; + return s -> passwords; } } diff --git a/sshd-contrib/src/test/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProviderTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProviderTest.java index ee381cb..94990c4 100644 --- a/sshd-contrib/src/test/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProviderTest.java +++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/client/auth/password/InteractivePasswordIdentityProviderTest.java @@ -18,6 +18,8 @@ */ package org.apache.sshd.contrib.client.auth.password; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -49,7 +51,7 @@ public class InteractivePasswordIdentityProviderTest extends BaseTestSupport { } @Test - public void testPasswordEnumerations() { + public void testPasswordEnumerations() throws IOException, GeneralSecurityException { List<String> expected = Arrays.asList(getClass().getSimpleName(), getClass().getPackage().getName(), getCurrentTestName()); ClientSession session = Mockito.mock(ClientSession.class); @@ -77,7 +79,7 @@ public class InteractivePasswordIdentityProviderTest extends BaseTestSupport { Mockito.when(session.getUserInteraction()).thenReturn(userInteraction); PasswordIdentityProvider provider = InteractivePasswordIdentityProvider.providerOf(session, prompt); - Iterable<String> passwords = provider.loadPasswords(); + Iterable<String> passwords = provider.loadPasswords(session); int expIndex = 0; for (String actValue : passwords) { String expValue = expected.get(expIndex); @@ -90,7 +92,7 @@ public class InteractivePasswordIdentityProviderTest extends BaseTestSupport { } @Test - public void testInteractionAllowedConsultation() { + public void testInteractionAllowedConsultation() throws IOException, GeneralSecurityException { ClientSession session = Mockito.mock(ClientSession.class); UserInteraction userInteraction = Mockito.mock(UserInteraction.class); Mockito.when(userInteraction.isInteractionAllowed(ArgumentMatchers.any(ClientSession.class))).thenReturn(Boolean.FALSE); @@ -99,7 +101,7 @@ public class InteractivePasswordIdentityProviderTest extends BaseTestSupport { .thenThrow(new UnsupportedOperationException("Unexpected call")); PasswordIdentityProvider provider = InteractivePasswordIdentityProvider.providerOf(session, userInteraction, getCurrentTestName()); - Iterable<String> passwords = provider.loadPasswords(); + Iterable<String> passwords = provider.loadPasswords(session); for (String p : passwords) { fail("Unexpected password: " + p); } 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 2de7695..ff4c2f6 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 @@ -27,6 +27,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.rmi.RemoteException; import java.rmi.ServerException; +import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.PublicKey; import java.time.Duration; @@ -452,15 +453,19 @@ public interface ClientSession * Creates a "unified" {@link Iterator} of passwords out of the registered passwords and the extra * available ones as a single iterator of passwords * - * @param session The {@link ClientSession} - ignored if {@code null} (i.e., empty iterator returned) - * @return The wrapping iterator - * @see ClientSession#getRegisteredIdentities() - * @see ClientSession#getPasswordIdentityProvider() + * @param session The {@link ClientSession} - ignored if {@code null} (i.e., empty iterator + * returned) + * @return The wrapping iterator + * @throws IOException If failed to load the passwords + * @throws GeneralSecurityException If some security issue with the passwords + * @see ClientSession#getRegisteredIdentities() + * @see ClientSession#getPasswordIdentityProvider() */ - static Iterator<String> passwordIteratorOf(ClientSession session) { + static Iterator<String> passwordIteratorOf(ClientSession session) + throws IOException, GeneralSecurityException { return (session == null) ? Collections.<String> emptyIterator() : PasswordIdentityProvider.iteratorOf( - session.getRegisteredIdentities(), session.getPasswordIdentityProvider()); + session, session.getRegisteredIdentities(), session.getPasswordIdentityProvider()); } } diff --git a/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java b/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java index 7c04231..c0a81a1 100644 --- a/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java +++ b/sshd-core/src/test/java/org/apache/sshd/common/auth/PasswordAuthenticationTest.java @@ -330,7 +330,7 @@ public class PasswordAuthenticationTest extends AuthenticationTestSupport { try (SshClient client = setupTestClient()) { List<String> passwords = Collections.singletonList(getCurrentTestName()); AtomicInteger loadCount = new AtomicInteger(0); - PasswordIdentityProvider provider = () -> { + PasswordIdentityProvider provider = session -> { loadCount.incrementAndGet(); outputDebugMessage("loadPasswords - count=%s", loadCount); return passwords;