Skip to content

Commit 8c2faeb

Browse files
author
Vaishnavi Kumbhar
committed
Add SFTP password authentication tests for Commons VFS2
1 parent d6094e7 commit 8c2faeb

1 file changed

Lines changed: 259 additions & 0 deletions

File tree

Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.commons.vfs2.provider.sftp;
19+
20+
import static org.apache.commons.vfs2.VfsTestUtils.getTestDirectoryFile;
21+
import static org.junit.jupiter.api.Assertions.*;
22+
23+
import java.io.File;
24+
import java.security.KeyPair;
25+
import java.security.KeyPairGenerator;
26+
import java.time.Duration;
27+
import java.util.ArrayList;
28+
import java.util.List;
29+
30+
import org.apache.commons.vfs2.*;
31+
import org.apache.commons.vfs2.auth.StaticUserAuthenticator;
32+
import org.apache.commons.vfs2.impl.DefaultFileSystemConfigBuilder;
33+
import org.apache.commons.vfs2.impl.DefaultFileSystemManager;
34+
import org.apache.commons.vfs2.provider.local.DefaultLocalFileProvider;
35+
import org.apache.sshd.SshServer;
36+
import org.apache.sshd.common.KeyPairProvider;
37+
import org.apache.sshd.common.NamedFactory;
38+
import org.apache.sshd.common.Session;
39+
import org.apache.sshd.server.Command;
40+
import org.apache.sshd.server.FileSystemFactory;
41+
import org.apache.sshd.server.FileSystemView;
42+
import org.apache.sshd.server.SshFile;
43+
import org.apache.sshd.server.filesystem.NativeSshFile;
44+
import org.apache.sshd.server.sftp.SftpSubsystem;
45+
import org.junit.jupiter.api.AfterAll;
46+
import org.junit.jupiter.api.BeforeAll;
47+
import org.junit.jupiter.api.MethodOrderer;
48+
import org.junit.jupiter.api.Order;
49+
import org.junit.jupiter.api.Test;
50+
import org.junit.jupiter.api.TestMethodOrder;
51+
52+
import com.jcraft.jsch.TestIdentityRepositoryFactory;
53+
54+
/**
55+
* Tests SFTP password authentication using {@link StaticUserAuthenticator}.
56+
* <p>
57+
* Verifies that credentials supplied via {@link DefaultFileSystemConfigBuilder#setUserAuthenticator}
58+
* are correctly propagated to the SFTP server.
59+
* </p>
60+
* <p>
61+
* Uses SSHD 0.8.0 with an explicit RSA {@link KeyPairProvider} for Java 17 compatibility
62+
* (the default DSA key generation is disabled on modern JDKs).
63+
* </p>
64+
*/
65+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
66+
public class SftpPasswordAuthTest {
67+
68+
private static final String TEST_USERNAME = "testuser";
69+
private static final String TEST_PASSWORD = "testpass";
70+
71+
private static SshServer sshServer;
72+
private static int serverPort;
73+
private static DefaultFileSystemManager manager;
74+
75+
@BeforeAll
76+
static void setUp() throws Exception {
77+
sshServer = SshServer.setUpDefaultServer();
78+
sshServer.setPort(0);
79+
80+
final KeyPairGenerator hostKeyGen = KeyPairGenerator.getInstance("RSA");
81+
hostKeyGen.initialize(2048);
82+
final KeyPair hostKey = hostKeyGen.generateKeyPair();
83+
sshServer.setKeyPairProvider(new KeyPairProvider() {
84+
@Override
85+
public KeyPair loadKey(final String type) {
86+
return KeyPairProvider.SSH_RSA.equals(type) ? hostKey : null;
87+
}
88+
@Override
89+
public String getKeyTypes() {
90+
return KeyPairProvider.SSH_RSA;
91+
}
92+
});
93+
94+
sshServer.setPasswordAuthenticator(
95+
(user, pass, session) -> TEST_USERNAME.equals(user) && TEST_PASSWORD.equals(pass));
96+
97+
final List<NamedFactory<Command>> subsystems = new ArrayList<>();
98+
subsystems.add(new NamedFactory<Command>() {
99+
@Override
100+
public Command create() { return new SftpSubsystem(); }
101+
@Override
102+
public String getName() { return "sftp"; }
103+
});
104+
sshServer.setSubsystemFactories(subsystems);
105+
106+
sshServer.setFileSystemFactory(new TestFileSystemFactory());
107+
sshServer.start();
108+
109+
serverPort = sshServer.getPort();
110+
111+
manager = new DefaultFileSystemManager();
112+
manager.addProvider("sftp", new SftpFileProvider());
113+
manager.addProvider("file", new DefaultLocalFileProvider());
114+
manager.init();
115+
}
116+
117+
@AfterAll
118+
static void tearDown() throws Exception {
119+
if (manager != null) {
120+
try {
121+
manager.close();
122+
} catch (final Exception e) {
123+
// ignore
124+
}
125+
}
126+
if (sshServer != null) {
127+
stopServerWithTimeout(5000);
128+
}
129+
}
130+
131+
private static void stopServerWithTimeout(final long timeoutMs) {
132+
final Thread stopThread = new Thread(() -> {
133+
try {
134+
sshServer.stop(true);
135+
} catch (final Exception e) {
136+
// ignore
137+
}
138+
}, "sshd-stop");
139+
stopThread.setDaemon(true);
140+
stopThread.start();
141+
try {
142+
stopThread.join(timeoutMs);
143+
} catch (final InterruptedException e) {
144+
Thread.currentThread().interrupt();
145+
}
146+
}
147+
148+
private static String baseUri() {
149+
return String.format("sftp://%s@localhost:%d", TEST_USERNAME, serverPort);
150+
}
151+
152+
private FileSystemOptions authOptions() throws FileSystemException {
153+
final FileSystemOptions opts = new FileSystemOptions();
154+
final StaticUserAuthenticator auth = new StaticUserAuthenticator(null, TEST_USERNAME, TEST_PASSWORD);
155+
DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, auth);
156+
configureSftpOptions(opts);
157+
return opts;
158+
}
159+
160+
private static void configureSftpOptions(final FileSystemOptions opts) throws FileSystemException {
161+
final SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();
162+
builder.setStrictHostKeyChecking(opts, "no");
163+
builder.setUserInfo(opts, new TrustEveryoneUserInfo());
164+
builder.setIdentityRepositoryFactory(opts, new TestIdentityRepositoryFactory());
165+
builder.setConnectTimeout(opts, Duration.ofSeconds(60));
166+
builder.setSessionTimeout(opts, Duration.ofSeconds(60));
167+
}
168+
169+
@Test
170+
@Order(1)
171+
void testResolveFile() throws FileSystemException {
172+
final FileSystemOptions opts = authOptions();
173+
try (FileObject file = manager.resolveFile(baseUri() + "/read-tests/file1.txt", opts)) {
174+
assertNotNull(file);
175+
assertTrue(file.exists(), "file1.txt should exist");
176+
assertEquals(FileType.FILE, file.getType());
177+
assertNotNull(file.getContent(), "Content should be readable when credentials are correct");
178+
}
179+
}
180+
181+
@Test
182+
@Order(2)
183+
void testResolveFolder() throws FileSystemException {
184+
final FileSystemOptions opts = authOptions();
185+
try (FileObject folder = manager.resolveFile(baseUri() + "/read-tests", opts)) {
186+
assertNotNull(folder);
187+
assertTrue(folder.exists(), "read-tests folder should exist");
188+
}
189+
}
190+
191+
@Test
192+
@Order(3)
193+
void testResolveFolderWithTrailingSlash() throws FileSystemException {
194+
final FileSystemOptions opts = authOptions();
195+
try (FileObject folder = manager.resolveFile(baseUri() + "/read-tests/", opts)) {
196+
assertNotNull(folder);
197+
assertTrue(folder.exists(), "read-tests/ folder should exist");
198+
}
199+
}
200+
201+
@Test
202+
@Order(10)
203+
void testWrongCredentialsThrowsException() throws FileSystemException {
204+
final FileSystemOptions opts = new FileSystemOptions();
205+
final StaticUserAuthenticator auth = new StaticUserAuthenticator(null, "wronguser", "wrongpassword");
206+
DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, auth);
207+
configureSftpOptions(opts);
208+
209+
final String wrongUserUri = String.format("sftp://wronguser@localhost:%d/read-tests/file1.txt", serverPort);
210+
assertThrows(FileSystemException.class, () -> {
211+
try (FileObject file = manager.resolveFile(wrongUserUri, opts)) {
212+
file.exists();
213+
}
214+
}, "Expected FileSystemException when accessing a resource with wrong credentials");
215+
}
216+
217+
// ============================
218+
// Inner classes
219+
// ============================
220+
221+
private static class TestFileSystemFactory implements FileSystemFactory {
222+
@Override
223+
public FileSystemView createFileSystemView(final Session session) {
224+
final String home = getTestDirectoryFile().getAbsolutePath();
225+
final String user = session.getUsername();
226+
return new FileSystemView() {
227+
@Override
228+
public SshFile getFile(final SshFile baseDir, final String file) {
229+
return getFile(baseDir.getAbsolutePath(), file);
230+
}
231+
@Override
232+
public SshFile getFile(final String file) {
233+
return getFile(home, file);
234+
}
235+
private SshFile getFile(final String dir, final String file) {
236+
final String normalized = NativeSshFile.normalizeSeparateChar(file);
237+
final String homeNorm = NativeSshFile.normalizeSeparateChar(home);
238+
final String prefix = removePrefix(homeNorm);
239+
String userFile = removePrefix(normalized);
240+
final File f = userFile.startsWith(prefix)
241+
? new File(userFile)
242+
: new File(prefix, userFile);
243+
userFile = removePrefix(NativeSshFile.normalizeSeparateChar(f.getAbsolutePath()));
244+
return new AccessibleNativeSshFile(userFile, f, user);
245+
}
246+
private String removePrefix(final String s) {
247+
final int idx = s.indexOf('/');
248+
return idx < 1 ? s : s.substring(idx);
249+
}
250+
};
251+
}
252+
}
253+
254+
private static class AccessibleNativeSshFile extends NativeSshFile {
255+
AccessibleNativeSshFile(final String fileName, final File file, final String userName) {
256+
super(fileName, file, userName);
257+
}
258+
}
259+
}

0 commit comments

Comments
 (0)