This is an automated email from the ASF dual-hosted git repository.
garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git
The following commit(s) were added to refs/heads/master by this push:
new b1e2aaef0 Port SFTP tests from Apache SSHd 0.8.0 to 3.0.0-M2 #754
b1e2aaef0 is described below
commit b1e2aaef0c54213c362207f7532ffd3014a98fe8
Author: Gary Gregory <[email protected]>
AuthorDate: Tue Apr 28 14:54:22 2026 -0400
Port SFTP tests from Apache SSHd 0.8.0 to 3.0.0-M2 #754
- Reuse IOUtils.closeQuietly()
- Sort members
- Use longer lines
- Reduce nesting
---
.../vfs2/provider/sftp/SftpPasswordAuthTest.java | 74 +++---
.../vfs2/provider/sftp/SftpTestServerHelper.java | 294 ++++++++++-----------
src/changes/changes.xml | 2 +
3 files changed, 167 insertions(+), 203 deletions(-)
diff --git
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpPasswordAuthTest.java
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpPasswordAuthTest.java
index 0408630cb..347cd4b50 100644
---
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpPasswordAuthTest.java
+++
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpPasswordAuthTest.java
@@ -29,6 +29,7 @@ import java.nio.file.Path;
import java.time.Duration;
import java.util.Collections;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemException;
import org.apache.commons.vfs2.FileSystemOptions;
@@ -50,48 +51,66 @@ import com.jcraft.jsch.TestIdentityRepositoryFactory;
/**
* Tests SFTP password authentication using {@link StaticUserAuthenticator}.
* <p>
- * Verifies that credentials supplied via {@link
DefaultFileSystemConfigBuilder#setUserAuthenticator}
- * are correctly propagated to the SFTP server.
+ * Verifies that credentials supplied via {@link
DefaultFileSystemConfigBuilder#setUserAuthenticator} are correctly propagated
to the SFTP server.
* </p>
*/
public class SftpPasswordAuthTest {
private static final String TEST_USERNAME = "testuser";
+
private static final String TEST_PASSWORD = "testpass";
private static SshServer sshServer;
+
private static int serverPort;
+
private static DefaultFileSystemManager manager;
+ private static String baseUri() {
+ return String.format("sftp://%s@localhost:%d", TEST_USERNAME,
serverPort);
+ }
+
+ private static void configureSftpOptions(final FileSystemOptions opts)
throws FileSystemException {
+ final SftpFileSystemConfigBuilder builder =
SftpFileSystemConfigBuilder.getInstance();
+ builder.setStrictHostKeyChecking(opts, "no");
+ builder.setUserInfo(opts, new TrustEveryoneUserInfo());
+ builder.setIdentityRepositoryFactory(opts, new
TestIdentityRepositoryFactory());
+ builder.setConnectTimeout(opts, Duration.ofSeconds(60));
+ builder.setSessionTimeout(opts, Duration.ofSeconds(60));
+ }
+
@BeforeAll
static void setUp() throws Exception {
sshServer = SshServer.setUpDefaultServer();
sshServer.setPort(0);
-
final Path tmpKeyFile = Files.createTempFile("sshd-test-key", ".ser");
tmpKeyFile.toFile().deleteOnExit();
final SimpleGeneratorHostKeyProvider keyProvider = new
SimpleGeneratorHostKeyProvider(tmpKeyFile);
keyProvider.setAlgorithm("RSA");
sshServer.setKeyPairProvider(keyProvider);
-
- sshServer.setPasswordAuthenticator(
- (user, pass, session) -> TEST_USERNAME.equals(user) &&
TEST_PASSWORD.equals(pass));
-
+ sshServer.setPasswordAuthenticator((user, pass, session) ->
TEST_USERNAME.equals(user) && TEST_PASSWORD.equals(pass));
sshServer.setSubsystemFactories(Collections.singletonList(new
SftpSubsystemFactory()));
-
final File homeDir = getTestDirectoryFile();
- sshServer.setFileSystemFactory(
- new
VirtualFileSystemFactory(homeDir.toPath().toAbsolutePath()));
-
+ sshServer.setFileSystemFactory(new
VirtualFileSystemFactory(homeDir.toPath().toAbsolutePath()));
sshServer.start();
serverPort = sshServer.getPort();
-
manager = new DefaultFileSystemManager();
manager.addProvider("sftp", new SftpFileProvider());
manager.addProvider("file", new DefaultLocalFileProvider());
manager.init();
}
+ private static void stopServerWithTimeout(final long timeoutMs) {
+ final Thread stopThread = new Thread(() ->
IOUtils.closeQuietly(sshServer), "sshd-stop");
+ stopThread.setDaemon(true);
+ stopThread.start();
+ try {
+ stopThread.join(timeoutMs);
+ } catch (final InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+
@AfterAll
static void tearDown() throws Exception {
if (manager != null) {
@@ -106,27 +125,6 @@ public class SftpPasswordAuthTest {
}
}
- private static void stopServerWithTimeout(final long timeoutMs) {
- final Thread stopThread = new Thread(() -> {
- try {
- sshServer.close();
- } catch (final Exception e) {
- // ignore
- }
- }, "sshd-stop");
- stopThread.setDaemon(true);
- stopThread.start();
- try {
- stopThread.join(timeoutMs);
- } catch (final InterruptedException e) {
- Thread.currentThread().interrupt();
- }
- }
-
- private static String baseUri() {
- return String.format("sftp://%s@localhost:%d", TEST_USERNAME,
serverPort);
- }
-
private FileSystemOptions authOptions() throws FileSystemException {
final FileSystemOptions opts = new FileSystemOptions();
final StaticUserAuthenticator auth = new StaticUserAuthenticator(null,
TEST_USERNAME, TEST_PASSWORD);
@@ -135,15 +133,6 @@ public class SftpPasswordAuthTest {
return opts;
}
- private static void configureSftpOptions(final FileSystemOptions opts)
throws FileSystemException {
- final SftpFileSystemConfigBuilder builder =
SftpFileSystemConfigBuilder.getInstance();
- builder.setStrictHostKeyChecking(opts, "no");
- builder.setUserInfo(opts, new TrustEveryoneUserInfo());
- builder.setIdentityRepositoryFactory(opts, new
TestIdentityRepositoryFactory());
- builder.setConnectTimeout(opts, Duration.ofSeconds(60));
- builder.setSessionTimeout(opts, Duration.ofSeconds(60));
- }
-
@Test
void testResolveFile() throws FileSystemException {
final FileSystemOptions opts = authOptions();
@@ -179,7 +168,6 @@ public class SftpPasswordAuthTest {
final StaticUserAuthenticator auth = new StaticUserAuthenticator(null,
"wronguser", "wrongpassword");
DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, auth);
configureSftpOptions(opts);
-
final String wrongUserUri =
String.format("sftp://wronguser@localhost:%d/read-tests/file1.txt", serverPort);
assertThrows(FileSystemException.class, () -> {
try (FileObject file = manager.resolveFile(wrongUserUri, opts)) {
diff --git
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpTestServerHelper.java
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpTestServerHelper.java
index aa02f87a0..0937d3110 100644
---
a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpTestServerHelper.java
+++
b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/sftp/SftpTestServerHelper.java
@@ -14,6 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
package org.apache.commons.vfs2.provider.sftp;
import static org.apache.commons.vfs2.VfsTestUtils.getTestDirectoryFile;
@@ -38,6 +39,7 @@ import java.util.NavigableMap;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.io.file.PathUtils;
import org.apache.commons.lang3.Strings;
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
@@ -60,89 +62,105 @@ import org.apache.sshd.sftp.server.SftpSubsystemProxy;
*/
public final class SftpTestServerHelper {
- private static final String DEFAULT_USER = "testuser";
- private static final int TEST_UID = 1000;
- private static final int TEST_GID = 1000;
- private static SshServer server;
- private static String connectionUri;
-
- private SftpTestServerHelper() {
- }
-
/**
- * Custom {@link SftpFileSystemAccessor} that tracks POSIX permissions in
memory so that
- * {@code setExecutable}/{@code setReadable}/{@code setWritable}
round-trip correctly on
- * platforms without native POSIX support (e.g. Windows).
+ * Command factory for handling special commands needed by VFS tests.
*/
- private static class TestSftpFileSystemAccessor implements
SftpFileSystemAccessor {
- private static final ConcurrentHashMap<String,
Set<PosixFilePermission>> permissionsCache =
- new ConcurrentHashMap<>();
+ private static class TestCommandFactory implements CommandFactory {
@Override
- public void setFilePermissions(
- final SftpSubsystemProxy subsystem, final Path file,
- final Set<PosixFilePermission> perms, final LinkOption...
options) throws IOException {
- permissionsCache.put(file.toAbsolutePath().toString(), new
HashSet<>(perms));
+ public Command createCommand(final ChannelSession channel, final
String command) throws IOException {
+ if (command.startsWith("id -u")) {
+ return createIdCommand("1000");
+ }
+ if (command.startsWith("id -G")) {
+ return createIdCommand("1000 1001 1002");
+ }
+ throw new IOException("Unknown command: " + command);
}
- @Override
- public void setFileOwner(
- final SftpSubsystemProxy subsystem, final Path file,
- final Principal owner, final LinkOption... options) throws
IOException {
+ private Command createIdCommand(final String output) {
+ return new Command() {
+
+ private ExitCallback callback;
+
+ private OutputStream out;
+
+ @Override
+ public void destroy(final ChannelSession channel) throws
Exception {
+ }
+
+ @Override
+ public void setErrorStream(final OutputStream err) {
+ }
+
+ @Override
+ public void setExitCallback(final ExitCallback callback) {
+ this.callback = callback;
+ }
+
+ @Override
+ public void setInputStream(final InputStream in) {
+ }
+
+ @Override
+ public void setOutputStream(final OutputStream out) {
+ this.out = out;
+ }
+
+ @Override
+ public void start(final ChannelSession channel, final
Environment env) throws IOException {
+ out.write((output + "\n").getBytes());
+ out.flush();
+ callback.onExit(0);
+ }
+ };
}
+ }
- @Override
- public void setGroupOwner(
- final SftpSubsystemProxy subsystem, final Path file,
- final Principal group, final LinkOption... options) throws
IOException {
- // No-op: same rationale as setFileOwner.
+ /**
+ * Custom {@link SftpFileSystemAccessor} that tracks POSIX permissions in
memory so that {@code setExecutable}/{@code setReadable}/{@code setWritable}
+ * round-trip correctly on platforms without native POSIX support
(e.g. Windows).
+ */
+ private static class TestSftpFileSystemAccessor implements
SftpFileSystemAccessor {
+
+ private static final ConcurrentHashMap<String,
Set<PosixFilePermission>> permissionsCache = new ConcurrentHashMap<>();
+
+ static void clearCache() {
+ permissionsCache.clear();
}
- @Override
- public void setFileAttribute(
- final SftpSubsystemProxy subsystem, final Path file,
- final String view, final String attribute, final Object value,
- final LinkOption... options) throws IOException {
- if ("uid".equals(attribute) || "gid".equals(attribute)) {
- // No-op: SSHD routes integer uid/gid through setFileAttribute
- // (not setFileOwner/setGroupOwner). The default calls
- // Files.setAttribute(file, "unix:uid", ...) which throws
- // IOException on non-root Unix when the uid doesn't match.
- return;
- }
+ private static Set<PosixFilePermission> defaultPermissions(final Path
file, final LinkOption... options) {
+ final Set<PosixFilePermission> perms =
EnumSet.of(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE);
try {
- SftpFileSystemAccessor.super.setFileAttribute(subsystem, file,
view, attribute, value, options);
- } catch (final IOException | RuntimeException ignored) {
- // Silently swallow; covers platforms without native POSIX
- // support and edge-cases where the rooted file system
- // rejects certain attribute writes.
+ if (Files.isDirectory(file, options)) {
+ perms.add(PosixFilePermission.OWNER_EXECUTE);
+ }
+ } catch (final Exception ignored) {
+ // ignore
}
+ return perms;
}
@Override
- public Map<String, ?> readFileAttributes(
- final SftpSubsystemProxy subsystem, final Path file,
- final String view, final LinkOption... options) throws
IOException {
+ public Map<String, ?> readFileAttributes(final SftpSubsystemProxy
subsystem, final Path file, final String view, final LinkOption... options)
+ throws IOException {
Map<String, ?> result;
boolean synthetic = false;
try {
result =
SftpFileSystemAccessor.super.readFileAttributes(subsystem, file, view, options);
} catch (final UnsupportedOperationException |
IllegalArgumentException e) {
- if (view.startsWith("unix:") || view.startsWith("posix:")) {
- final Map<String, Object> attrs = new HashMap<>(
- Files.readAttributes(file, "basic:*", options));
- attrs.put("uid", TEST_UID);
- attrs.put("gid", TEST_GID);
- result = attrs;
- synthetic = true;
- } else {
+ if (!view.startsWith("unix:") && !view.startsWith("posix:")) {
throw e;
}
+ final Map<String, Object> attrs = new
HashMap<>(Files.readAttributes(file, "basic:*", options));
+ attrs.put("uid", TEST_UID);
+ attrs.put("gid", TEST_GID);
+ result = attrs;
+ synthetic = true;
}
if (view.startsWith("unix:") || view.startsWith("posix:") ||
synthetic) {
final Map<String, Object> attrs = new HashMap<>(result);
- final Set<PosixFilePermission> cached =
- permissionsCache.get(file.toAbsolutePath().toString());
+ final Set<PosixFilePermission> cached =
permissionsCache.get(file.toAbsolutePath().toString());
attrs.put("permissions", cached != null ? cached :
defaultPermissions(file, options));
attrs.put("uid", TEST_UID);
attrs.put("gid", TEST_GID);
@@ -152,15 +170,19 @@ public final class SftpTestServerHelper {
}
@Override
- public NavigableMap<String, Object> resolveReportedFileAttributes(
- final SftpSubsystemProxy subsystem, final Path file, final int
flags,
+ public void removeFile(final SftpSubsystemProxy subsystem, final Path
path, final boolean isDirectory) throws IOException {
+ permissionsCache.remove(path.toAbsolutePath().toString());
+ SftpFileSystemAccessor.super.removeFile(subsystem, path,
isDirectory);
+ }
+
+ @Override
+ public NavigableMap<String, Object>
resolveReportedFileAttributes(final SftpSubsystemProxy subsystem, final Path
file, final int flags,
final NavigableMap<String, Object> attrs, final LinkOption...
options) throws IOException {
// Always override uid/gid to match TestCommandFactory's "id
-u"/"id -G" responses,
// so that PosixPermissions owner checks are consistent across all
platforms.
attrs.put("uid", TEST_UID);
attrs.put("gid", TEST_GID);
- final Set<PosixFilePermission> cached =
- permissionsCache.get(file.toAbsolutePath().toString());
+ final Set<PosixFilePermission> cached =
permissionsCache.get(file.toAbsolutePath().toString());
if (cached != null) {
attrs.put("permissions", cached);
} else if (!attrs.containsKey("permissions")) {
@@ -170,83 +192,66 @@ public final class SftpTestServerHelper {
}
@Override
- public void removeFile(
- final SftpSubsystemProxy subsystem, final Path path,
- final boolean isDirectory) throws IOException {
- permissionsCache.remove(path.toAbsolutePath().toString());
- SftpFileSystemAccessor.super.removeFile(subsystem, path,
isDirectory);
- }
-
- private static Set<PosixFilePermission> defaultPermissions(
- final Path file, final LinkOption... options) {
- final Set<PosixFilePermission> perms = EnumSet.of(
- PosixFilePermission.OWNER_READ,
- PosixFilePermission.OWNER_WRITE);
+ public void setFileAttribute(final SftpSubsystemProxy subsystem, final
Path file, final String view, final String attribute, final Object value,
+ final LinkOption... options) throws IOException {
+ if ("uid".equals(attribute) || "gid".equals(attribute)) {
+ // No-op: SSHD routes integer uid/gid through setFileAttribute
+ // (not setFileOwner/setGroupOwner). The default calls
+ // Files.setAttribute(file, "unix:uid", ...) which throws
+ // IOException on non-root Unix when the uid doesn't match.
+ return;
+ }
try {
- if (Files.isDirectory(file, options)) {
- perms.add(PosixFilePermission.OWNER_EXECUTE);
- }
- } catch (final Exception ignored) {
- // ignore
+ SftpFileSystemAccessor.super.setFileAttribute(subsystem, file,
view, attribute, value, options);
+ } catch (final IOException | RuntimeException ignored) {
+ // Silently swallow; covers platforms without native POSIX
+ // support and edge-cases where the rooted file system
+ // rejects certain attribute writes.
}
- return perms;
}
- static void clearCache() {
- permissionsCache.clear();
+ @Override
+ public void setFileOwner(final SftpSubsystemProxy subsystem, final
Path file, final Principal owner, final LinkOption... options) throws
IOException {
}
- }
- /**
- * Command factory for handling special commands needed by VFS tests.
- */
- private static class TestCommandFactory implements CommandFactory {
@Override
- public Command createCommand(final ChannelSession channel, final
String command) throws IOException {
- if (command.startsWith("id -u")) {
- return createIdCommand("1000");
- }
- if (command.startsWith("id -G")) {
- return createIdCommand("1000 1001 1002");
- }
- throw new IOException("Unknown command: " + command);
+ public void setFilePermissions(final SftpSubsystemProxy subsystem,
final Path file, final Set<PosixFilePermission> perms, final LinkOption...
options)
+ throws IOException {
+ permissionsCache.put(file.toAbsolutePath().toString(), new
HashSet<>(perms));
}
- private Command createIdCommand(final String output) {
- return new Command() {
- private ExitCallback callback;
- private OutputStream out;
+ @Override
+ public void setGroupOwner(final SftpSubsystemProxy subsystem, final
Path file, final Principal group, final LinkOption... options) throws
IOException {
+ // No-op: same rationale as setFileOwner.
+ }
+ }
- @Override
- public void destroy(final ChannelSession channel) throws
Exception {
- }
+ private static final String DEFAULT_USER = "testuser";
- @Override
- public void setErrorStream(final OutputStream err) {
- }
+ private static final int TEST_UID = 1000;
- @Override
- public void setExitCallback(final ExitCallback callback) {
- this.callback = callback;
- }
+ private static final int TEST_GID = 1000;
- @Override
- public void setInputStream(final InputStream in) {
- }
+ private static SshServer server;
- @Override
- public void setOutputStream(final OutputStream out) {
- this.out = out;
- }
+ private static String connectionUri;
- @Override
- public void start(final ChannelSession channel, final
Environment env) throws IOException {
- out.write((output + "\n").getBytes());
- out.flush();
- callback.onExit(0);
- }
- };
- }
+ /**
+ * Gets the connection URI for the embedded server.
+ *
+ * @return the connection URI, or null if server is not started
+ */
+ public static String getConnectionUri() {
+ return connectionUri;
+ }
+
+ /**
+ * Checks if the server is running.
+ *
+ * @return true if server is running
+ */
+ public static boolean isServerRunning() {
+ return server != null;
}
/**
@@ -258,36 +263,24 @@ public final class SftpTestServerHelper {
if (server != null) {
return;
}
-
final Path tmpDir = PathUtils.getTempDirectory();
server = SshServer.setUpDefaultServer();
server.setPort(0);
-
- final SimpleGeneratorHostKeyProvider keyProvider =
- new
SimpleGeneratorHostKeyProvider(tmpDir.resolve("hostkey.ser"));
+ final SimpleGeneratorHostKeyProvider keyProvider = new
SimpleGeneratorHostKeyProvider(tmpDir.resolve("hostkey.ser"));
keyProvider.setAlgorithm("RSA");
server.setKeyPairProvider(keyProvider);
-
final SftpSubsystemFactory sftpFactory = new SftpSubsystemFactory();
sftpFactory.setFileSystemAccessor(new TestSftpFileSystemAccessor());
server.setSubsystemFactories(Collections.singletonList(sftpFactory));
-
- server.setPasswordAuthenticator(
- (username, password, session) -> Strings.CS.equals(username,
password));
+ server.setPasswordAuthenticator((username, password, session) ->
Strings.CS.equals(username, password));
server.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
-
server.setCommandFactory(new TestCommandFactory());
-
final File homeDir = getTestDirectoryFile();
- server.setFileSystemFactory(
- new
VirtualFileSystemFactory(homeDir.toPath().toAbsolutePath()));
-
+ server.setFileSystemFactory(new
VirtualFileSystemFactory(homeDir.toPath().toAbsolutePath()));
server.start();
-
final List<UserAuthFactory> authFactories = new
ArrayList<>(server.getUserAuthFactories());
authFactories.add(UserAuthNoneFactory.INSTANCE);
server.setUserAuthFactories(authFactories);
-
final int socketPort = server.getPort();
connectionUri = String.format("sftp://%s@localhost:%d", DEFAULT_USER,
socketPort);
}
@@ -299,32 +292,13 @@ public final class SftpTestServerHelper {
*/
public static synchronized void stopServer() throws InterruptedException {
if (server != null) {
- try {
- server.close();
- } catch (final IOException e) {
- // ignore
- }
+ IOUtils.closeQuietly(server);
server = null;
connectionUri = null;
TestSftpFileSystemAccessor.clearCache();
}
}
- /**
- * Gets the connection URI for the embedded server.
- *
- * @return the connection URI, or null if server is not started
- */
- public static String getConnectionUri() {
- return connectionUri;
- }
-
- /**
- * Checks if the server is running.
- *
- * @return true if server is running
- */
- public static boolean isServerRunning() {
- return server != null;
+ private SftpTestServerHelper() {
}
}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4a4b4cee7..b152a0bf4 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -65,6 +65,8 @@ The <action> type attribute can be add,update,fix,remove.
<action type="fix" dev="ggregory" due-to="Gary Gregory, Ilan
Goldfeld">Fix FtpFileObject.exists() returning true for root-level folders
after connection drop #757.</action>
<action type="fix" dev="ggregory" due-to="Gary Gregory, Ilan
Goldfeld">Fix refresh() not clearing cached state when file was never attached
#758.</action>
<action type="fix" dev="ggregory" due-to="Ilan Goldfeld, Gary Gregory"
issue="VFS-862">Fix ON_RESOLVE triggering refresh on internal navigation
#761.</action>
+ <action type="fix" dev="ggregory" due-to="VaishKumbhar, Gary
Gregory">Add SFTP password authentication tests for Commons VFS2 #754.</action>
+ <action type="fix" dev="ggregory" due-to="VaishKumbhar, Gary
Gregory">Port SFTP tests from Apache SSHd 0.8.0 to 3.0.0-M2 #754.</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary Gregory">Add
org.apache.commons.vfs2.provider.ftp.FTPClientWrapper.sendOptions(String,
String).</action>
<action type="add" dev="ggregory" due-to="Gary Gregory">Add
FtpFileSystemConfigBuilder.getControlEncodingCharset(FileSystemOptions) and
deprecate getControlEncoding(FileSystemOptions).</action>