[SSHD-861] Handle correctly special characters in username/password when creating an SFTP file system URI
Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/c24635d5 Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/c24635d5 Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/c24635d5 Branch: refs/heads/master Commit: c24635d5cb86a106f5ca33f22e2a1b39d7aad512 Parents: 005ee40 Author: Lyor Goldstein <[email protected]> Authored: Sun Nov 11 08:59:46 2018 +0200 Committer: Lyor Goldstein <[email protected]> Committed: Sun Nov 11 18:56:28 2018 +0200 ---------------------------------------------------------------------- .../sshd/common/auth/BasicCredentialsImpl.java | 92 ++++++++++++++ .../common/auth/BasicCredentialsProvider.java | 27 +++++ .../common/auth/MutableBasicCredentials.java | 27 +++++ .../sshd/common/auth/MutablePassword.java | 27 +++++ .../apache/sshd/common/auth/PasswordHolder.java | 28 +++++ .../loader/PrivateKeyEncryptionContext.java | 5 +- .../apache/sshd/util/test/JUnitTestSupport.java | 22 +++- .../apache/sshd/util/test/SimpleUserInfo.java | 3 +- .../subsystem/sftp/SftpFileSystemProvider.java | 92 +++++++++++--- .../subsystem/sftp/SftpFileSystemURITest.java | 121 +++++++++++++++++++ .../sftp/ApacheSshdSftpSessionFactory.java | 15 ++- .../sftp/ApacheSshdSftpSessionFactoryTest.java | 2 +- 12 files changed, 432 insertions(+), 29 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java new file mode 100644 index 0000000..4e444f0 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsImpl.java @@ -0,0 +1,92 @@ +/* + * 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.auth; + +import java.util.Objects; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public class BasicCredentialsImpl implements MutableBasicCredentials, Cloneable { + private String username; + private String password; + + public BasicCredentialsImpl() { + super(); + } + + public BasicCredentialsImpl(String username, String password) { + this.username = username; + this.password = password; + } + + @Override + public String getUsername() { + return username; + } + + @Override + public void setUsername(String username) { + this.username = username; + } + + @Override + public String getPassword() { + return password; + } + + @Override + public void setPassword(String password) { + this.password = password; + } + + @Override + public BasicCredentialsImpl clone() { + try { + return getClass().cast(super.clone()); + } catch (CloneNotSupportedException e) { + throw new UnsupportedOperationException("Unexpected failure to clone: " + e.getMessage(), e); + } + } + + @Override + public int hashCode() { + return Objects.hash(getUsername(), getPassword()); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (obj == this) { + return true; + } + if (getClass() != obj.getClass()) { + return false; + } + + BasicCredentialsImpl other = (BasicCredentialsImpl) obj; + return Objects.equals(getUsername(), other.getUsername()) + && Objects.equals(getPassword(), other.getPassword()); + } + + // NOTE: do not implement 'toString' on purpose to avoid inadvertent logging of contents +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java new file mode 100644 index 0000000..7ddde04 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/BasicCredentialsProvider.java @@ -0,0 +1,27 @@ +/* + * 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.auth; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface BasicCredentialsProvider extends UsernameHolder, PasswordHolder { + // Nothing extra +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java new file mode 100644 index 0000000..a7bb1d5 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutableBasicCredentials.java @@ -0,0 +1,27 @@ +/* + * 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.auth; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface MutableBasicCredentials extends BasicCredentialsProvider, MutableUserHolder, MutablePassword { + // Nothing extra +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java new file mode 100644 index 0000000..07e4843 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/MutablePassword.java @@ -0,0 +1,27 @@ +/* + * 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.auth; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +public interface MutablePassword extends PasswordHolder { + void setPassword(String password); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java b/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java new file mode 100644 index 0000000..5f2eff7 --- /dev/null +++ b/sshd-common/src/main/java/org/apache/sshd/common/auth/PasswordHolder.java @@ -0,0 +1,28 @@ +/* + * 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.auth; + +/** + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FunctionalInterface +public interface PasswordHolder { + String getPassword(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java index 5e03cdb..5303d57 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/loader/PrivateKeyEncryptionContext.java @@ -30,13 +30,14 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.sshd.common.auth.MutablePassword; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; /** * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> */ -public class PrivateKeyEncryptionContext implements Cloneable { +public class PrivateKeyEncryptionContext implements MutablePassword, Cloneable { public static final String DEFAULT_CIPHER_MODE = "CBC"; private static final Map<String, PrivateKeyObfuscator> OBFUSCATORS = @@ -82,10 +83,12 @@ public class PrivateKeyEncryptionContext implements Cloneable { cipherMode = value; } + @Override public String getPassword() { return password; } + @Override public void setPassword(String value) { password = value; } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java ---------------------------------------------------------------------- diff --git a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java index 01ba808..175cd93 100644 --- a/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java +++ b/sshd-common/src/test/java/org/apache/sshd/util/test/JUnitTestSupport.java @@ -51,6 +51,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; +import java.util.function.BiPredicate; import org.apache.sshd.common.keyprovider.KeyPairProvider; import org.apache.sshd.common.util.GenericUtils; @@ -360,6 +361,11 @@ public abstract class JUnitTestSupport extends Assert { } public static <E> void assertListEquals(String message, List<? extends E> expected, List<? extends E> actual) { + assertListEquals(message, expected, actual, Objects::equals); + } + + public static <E> void assertListEquals( + String message, List<? extends E> expected, List<? extends E> actual, BiPredicate<? super E, ? super E> equator) { int expSize = GenericUtils.size(expected); int actSize = GenericUtils.size(actual); assertEquals(message + "[size]", expSize, actSize); @@ -367,18 +373,28 @@ public abstract class JUnitTestSupport extends Assert { for (int index = 0; index < expSize; index++) { E expValue = expected.get(index); E actValue = actual.get(index); - assertEquals(message + "[" + index + "]", expValue, actValue); + if (!equator.test(expValue, actValue)) { + fail(message + "[" + index + "]: expected=" + expValue + ", actual=" + actValue); + } } } - public static <K, V> void assertMapEquals(String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual) { + public static <K, V> void assertMapEquals( + String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual) { + assertMapEquals(message, expected, actual, Objects::equals); + } + + public static <K, V> void assertMapEquals( + String message, Map<? extends K, ? extends V> expected, Map<? super K, ? extends V> actual, BiPredicate<? super V, ? super V> equator) { int numItems = GenericUtils.size(expected); assertEquals(message + "[size]", numItems, GenericUtils.size(actual)); if (numItems > 0) { expected.forEach((key, expValue) -> { V actValue = actual.get(key); - assertEquals(message + "[" + key + "]", expValue, actValue); + if (!equator.test(expValue, actValue)) { + fail(message + "[" + key + "]: expected=" + expValue + ", actual=" + actValue); + } }); } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java b/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java index 31f7c41..5062e8d 100644 --- a/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java +++ b/sshd-core/src/test/java/org/apache/sshd/util/test/SimpleUserInfo.java @@ -65,7 +65,8 @@ public class SimpleUserInfo implements UserInfo, UIKeyboardInteractive { } @Override - public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt, boolean[] echo) { + public String[] promptKeyboardInteractive( + String destination, String name, String instruction, String[] prompt, boolean[] echo) { return new String[]{password}; } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java ---------------------------------------------------------------------- diff --git a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java index 3c171d0..f8cd75b 100644 --- a/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java +++ b/sshd-sftp/src/main/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemProvider.java @@ -24,6 +24,7 @@ import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; +import java.net.URISyntaxException; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.nio.file.AccessDeniedException; @@ -75,6 +76,9 @@ import org.apache.sshd.common.PropertyResolver; import org.apache.sshd.common.PropertyResolverUtils; import org.apache.sshd.common.SshConstants; import org.apache.sshd.common.SshException; +import org.apache.sshd.common.auth.BasicCredentialsImpl; +import org.apache.sshd.common.auth.BasicCredentialsProvider; +import org.apache.sshd.common.auth.MutableBasicCredentials; import org.apache.sshd.common.io.IoSession; import org.apache.sshd.common.subsystem.sftp.SftpConstants; import org.apache.sshd.common.subsystem.sftp.SftpException; @@ -184,12 +188,9 @@ public class SftpFileSystemProvider extends FileSystemProvider { port = SshConstants.DEFAULT_PORT; } - String userInfo = ValidateUtils.checkNotNullAndNotEmpty(uri.getUserInfo(), "UserInfo not provided"); - String[] ui = GenericUtils.split(userInfo, ':'); - ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo); - - String username = ui[0]; - String password = ui[1]; + BasicCredentialsProvider credentials = parseCredentials(uri); + ValidateUtils.checkState(credentials != null, "No credentials provided"); + String username = credentials.getUsername(); String id = getFileSystemIdentifier(host, port, username); Map<String, Object> params = resolveFileSystemParameters(env, parseURIParameters(uri)); PropertyResolver resolver = PropertyResolverUtils.toPropertyResolver(params); @@ -198,6 +199,7 @@ public class SftpFileSystemProvider extends FileSystemProvider { PropertyResolverUtils.getCharset(resolver, NAME_DECORDER_CHARSET_PROP_NAME, DEFAULT_NAME_DECODER_CHARSET); long maxConnectTime = resolver.getLongProperty(CONNECT_TIME_PROP_NAME, DEFAULT_CONNECT_TIME); long maxAuthTime = resolver.getLongProperty(AUTH_TIME_PROP_NAME, DEFAULT_AUTH_TIME); + String password = credentials.getPassword(); SftpFileSystem fileSystem; synchronized (fileSystems) { @@ -308,6 +310,27 @@ public class SftpFileSystemProvider extends FileSystemProvider { return resolved; } + /** + * Attempts to parse the user information from the URI + * + * @param uri The {@link URI} value - ignored if {@code null} or does not + * contain any {@link URI#getUserInfo() user info}. + * @return The parsed credentials - {@code null} if none available + */ + public static MutableBasicCredentials parseCredentials(URI uri) { + return parseCredentials((uri == null) ? "" : uri.getUserInfo()); + } + + public static MutableBasicCredentials parseCredentials(String userInfo) { + if (GenericUtils.isEmpty(userInfo)) { + return null; + } + + String[] ui = GenericUtils.split(userInfo, ':'); + ValidateUtils.checkTrue(GenericUtils.length(ui) == 2, "Invalid user info: %s", userInfo); + return new BasicCredentialsImpl(ui[0], ui[1]); + } + public static Map<String, Object> parseURIParameters(URI uri) { return parseURIParameters((uri == null) ? "" : uri.getQuery()); } @@ -336,7 +359,9 @@ public class SftpFileSystemProvider extends FileSystemProvider { String key = p.substring(0, pos); String value = p.substring(pos + 1); if (NumberUtils.isIntegerNumber(value)) { - map.put(key, Long.parseLong(value)); + map.put(key, Long.valueOf(value)); + } else if ("true".equals(value) || "false".equals("value")) { + map.put(key, Boolean.valueOf(value)); } else { map.put(key, value); } @@ -1235,22 +1260,53 @@ public class SftpFileSystemProvider extends FileSystemProvider { } public static URI createFileSystemURI(String host, int port, String username, String password, Map<String, ?> params) { - StringBuilder sb = new StringBuilder(Byte.MAX_VALUE); - sb.append(SftpConstants.SFTP_SUBSYSTEM_NAME) - .append("://").append(username).append(':').append(password) - .append('@').append(host).append(':').append(port) - .append('/'); - if (GenericUtils.size(params) > 0) { - boolean firstParam = true; - // Cannot use forEach because firstParam is not effectively final + ValidateUtils.checkNotNullAndNotEmpty(host, "No host provided"); + + String queryPart = null; + int numParams = GenericUtils.size(params); + if (numParams > 0) { + StringBuilder sb = new StringBuilder(numParams * Short.SIZE); for (Map.Entry<String, ?> pe : params.entrySet()) { String key = pe.getKey(); Object value = pe.getValue(); - sb.append(firstParam ? '?' : '&').append(key).append('=').append(Objects.toString(value, null)); - firstParam = false; + if (sb.length() > 0) { + sb.append('&'); + } + sb.append(key); + if (value != null) { + sb.append('=').append(Objects.toString(value, null)); + } } + + queryPart = sb.toString(); } - return URI.create(sb.toString()); + try { + String userAuth = encodeCredentials(username, password); + return new URI(SftpConstants.SFTP_SUBSYSTEM_NAME, userAuth, host, port, "/", queryPart, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Failed (" + e.getClass().getSimpleName() + ")" + + " to create access URI: " + e.getMessage(), e); + } + } + + public static String encodeCredentials(String username, String password) { + ValidateUtils.checkNotNullAndNotEmpty(username, "No username provided"); + ValidateUtils.checkNotNullAndNotEmpty(password, "No password provided"); + /* + * There is no way to properly encode/decode credentials that already contain + * colon. See also https://tools.ietf.org/html/rfc3986#section-3.2.1: + * + * + * Use of the format "user:password" in the userinfo field is + * deprecated. Applications should not render as clear text any data + * after the first colon (":") character found within a userinfo + * subcomponent unless the data after the colon is the empty string + * (indicating no password). Applications may choose to ignore or + * reject such data when it is received as part of a reference and + * should reject the storage of such data in unencrypted form. + */ + ValidateUtils.checkTrue((username.indexOf(':') < 0) && (password.indexOf(':') < 0), "Reserved character used in credentials"); + return username + ":" + password; } } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java ---------------------------------------------------------------------- diff --git a/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java new file mode 100644 index 0000000..1e89e47 --- /dev/null +++ b/sshd-sftp/src/test/java/org/apache/sshd/client/subsystem/sftp/SftpFileSystemURITest.java @@ -0,0 +1,121 @@ +/* + * 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.subsystem.sftp; + +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.apache.sshd.common.SshConstants; +import org.apache.sshd.common.auth.BasicCredentialsProvider; +import org.apache.sshd.common.subsystem.sftp.SftpConstants; +import org.apache.sshd.common.util.net.SshdSocketAddress; +import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory; +import org.apache.sshd.util.test.JUnitTestSupport; +import org.apache.sshd.util.test.NoIoTestCase; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.Parameterized.UseParametersRunnerFactory; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:[email protected]">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class) +@Category({ NoIoTestCase.class }) +public class SftpFileSystemURITest extends JUnitTestSupport { + private final String host; + private final int port; + private final String username; + private final String password; + private final Map<String, ?> params; + + public SftpFileSystemURITest(String host, int port, String username, String password, Map<String, ?> params) { + this.host = host; + this.port = port; + this.username = username; + this.password = password; + this.params = params; + } + + @Parameters(name = "host={0}, port={1}, user={2}, password={3}, params={4}") + public static List<Object[]> parameters() { + return new ArrayList<Object[]>() { + // Not serializing it + private static final long serialVersionUID = 1L; + + { + add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, 0, "user", "password", null}); + add(new Object[] {"37.77.34.7", 2222, "user", "password", Collections.singletonMap("non-default-port", true)}); + add(new Object[] {SshdSocketAddress.LOCALHOST_NAME, SshConstants.DEFAULT_PORT, "J@ck", "d@Ripper", new HashMap<String, Object>() { + // not serializing it + private static final long serialVersionUID = 1L; + + { + put("param1", "1st"); + put("param2", 2); + put("param3", false); + } + } + }); + add(new Object[] {"19.65.7.3", 0, "J%ck", "d%Ripper", null}); + } + }; + } + + @Test + public void testFullURIEncoding() { + URI uri = SftpFileSystemProvider.createFileSystemURI(host, port, username, password, params); + assertEquals("Mismatched scheme", SftpConstants.SFTP_SUBSYSTEM_NAME, uri.getScheme()); + assertEquals("Mismatched host", host, uri.getHost()); + assertEquals("Mismatched port", port, uri.getPort()); + + BasicCredentialsProvider credentials = SftpFileSystemProvider.parseCredentials(uri); + assertNotNull("No credentials provided", credentials); + assertEquals("Mismatched user", username, credentials.getUsername()); + assertEquals("Mismatched password", password, credentials.getPassword()); + + Map<String, ?> uriParams = SftpFileSystemProvider.parseURIParameters(uri); + assertMapEquals(getCurrentTestName(), params, uriParams, (v1, v2) -> Objects.equals(v1.toString(), v2.toString())); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[host=" + host + + ", port=" + port + + ", username=" + username + + ", password=" + password + + ", params=" + params + + "]"; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java ---------------------------------------------------------------------- diff --git a/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java b/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java index dfa5855..3c0a65c 100644 --- a/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java +++ b/sshd-spring-sftp/src/main/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactory.java @@ -38,6 +38,7 @@ import org.apache.sshd.client.subsystem.sftp.SftpClientFactory; import org.apache.sshd.client.subsystem.sftp.SftpVersionSelector; import org.apache.sshd.common.PropertyResolverUtils; import org.apache.sshd.common.SshConstants; +import org.apache.sshd.common.auth.MutableBasicCredentials; import org.apache.sshd.common.config.keys.FilePasswordProvider; import org.apache.sshd.common.config.keys.KeyUtils; import org.apache.sshd.common.config.keys.loader.pem.PEMResourceParserUtils; @@ -62,7 +63,7 @@ import org.springframework.integration.file.remote.session.SharedSessionCapable; public class ApacheSshdSftpSessionFactory extends AbstractLoggingBean implements SessionFactory<DirEntry>, SharedSessionCapable, - SimpleClientConfigurator, + MutableBasicCredentials, SimpleClientConfigurator, InitializingBean, DisposableBean { // TODO add support for loading multiple private keys @@ -119,19 +120,22 @@ public class ApacheSshdSftpSessionFactory this.portValue = port; } - public String getUser() { + @Override + public String getUsername() { return userValue; } /** * The remote user to use. This is a mandatory property. * - * @param user The username + * @param user The (never {@code null}/empty) username */ - public void setUser(String user) { + @Override + public void setUsername(String user) { this.userValue = ValidateUtils.checkNotNullAndNotEmpty(user, "No user specified: %s", user); } + @Override public String getPassword() { return passwordValue; } @@ -143,6 +147,7 @@ public class ApacheSshdSftpSessionFactory * @param password The password to use - if {@code null} then no password * is set - in which case the {@link #getPrivateKey()} resource is used */ + @Override public void setPassword(String password) { this.passwordValue = password; } @@ -396,7 +401,7 @@ public class ApacheSshdSftpSessionFactory protected ClientSession createClientSession() throws Exception { String hostname = ValidateUtils.checkNotNullAndNotEmpty(getHost(), "Host must not be empty"); - String username = ValidateUtils.checkNotNullAndNotEmpty(getUser(), "User must not be empty"); + String username = ValidateUtils.checkNotNullAndNotEmpty(getUsername(), "User must not be empty"); String passwordIdentity = getPassword(); KeyPair kp = getPrivateKeyPair(); ValidateUtils.checkState(GenericUtils.isNotEmpty(passwordIdentity) || (kp != null), http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/c24635d5/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java ---------------------------------------------------------------------- diff --git a/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java b/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java index 75d7195..4b85c20 100644 --- a/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java +++ b/sshd-spring-sftp/src/test/java/org/apache/sshd/spring/integration/sftp/ApacheSshdSftpSessionFactoryTest.java @@ -388,7 +388,7 @@ public class ApacheSshdSftpSessionFactoryTest extends BaseTestSupport { ApacheSshdSftpSessionFactory factory = new ApacheSshdSftpSessionFactory(sharedSession); factory.setHost(TEST_LOCALHOST); factory.setPort(port); - factory.setUser(getCurrentTestName()); + factory.setUsername(getCurrentTestName()); factory.setPassword(getCurrentTestName()); factory.setSshClient(client); factory.setConnectTimeout(TimeUnit.SECONDS.toMillis(7L));
