Skip to content

Commit ff5b8da

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

1 file changed

Lines changed: 247 additions & 0 deletions

File tree

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
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.Test;
48+
49+
import com.jcraft.jsch.TestIdentityRepositoryFactory;
50+
51+
/**
52+
* Tests SFTP password authentication using {@link StaticUserAuthenticator}.
53+
* <p>
54+
* Verifies that credentials supplied via {@link DefaultFileSystemConfigBuilder#setUserAuthenticator}
55+
* are correctly propagated to the SFTP server.
56+
* </p>
57+
* <p>
58+
* Uses SSHD 0.8.0 with an explicit RSA {@link KeyPairProvider} for Java 17 compatibility
59+
* (the default DSA key generation is disabled on modern JDKs).
60+
* </p>
61+
*/
62+
public class SftpPasswordAuthTest {
63+
64+
private static final String TEST_USERNAME = "testuser";
65+
private static final String TEST_PASSWORD = "testpass";
66+
67+
private static SshServer sshServer;
68+
private static int serverPort;
69+
private static DefaultFileSystemManager manager;
70+
71+
@BeforeAll
72+
static void setUp() throws Exception {
73+
sshServer = SshServer.setUpDefaultServer();
74+
sshServer.setPort(0);
75+
76+
final KeyPairGenerator hostKeyGen = KeyPairGenerator.getInstance("RSA");
77+
hostKeyGen.initialize(2048);
78+
final KeyPair hostKey = hostKeyGen.generateKeyPair();
79+
sshServer.setKeyPairProvider(new KeyPairProvider() {
80+
@Override
81+
public KeyPair loadKey(final String type) {
82+
return KeyPairProvider.SSH_RSA.equals(type) ? hostKey : null;
83+
}
84+
@Override
85+
public String getKeyTypes() {
86+
return KeyPairProvider.SSH_RSA;
87+
}
88+
});
89+
90+
sshServer.setPasswordAuthenticator(
91+
(user, pass, session) -> TEST_USERNAME.equals(user) && TEST_PASSWORD.equals(pass));
92+
93+
final List<NamedFactory<Command>> subsystems = new ArrayList<>();
94+
subsystems.add(new NamedFactory<Command>() {
95+
@Override
96+
public Command create() { return new SftpSubsystem(); }
97+
@Override
98+
public String getName() { return "sftp"; }
99+
});
100+
sshServer.setSubsystemFactories(subsystems);
101+
102+
sshServer.setFileSystemFactory(new TestFileSystemFactory());
103+
sshServer.start();
104+
105+
serverPort = sshServer.getPort();
106+
107+
manager = new DefaultFileSystemManager();
108+
manager.addProvider("sftp", new SftpFileProvider());
109+
manager.addProvider("file", new DefaultLocalFileProvider());
110+
manager.init();
111+
}
112+
113+
@AfterAll
114+
static void tearDown() throws Exception {
115+
if (manager != null) {
116+
try {
117+
manager.close();
118+
} catch (final Exception e) {
119+
// ignore
120+
}
121+
}
122+
if (sshServer != null) {
123+
stopServerWithTimeout(5000);
124+
}
125+
}
126+
127+
private static void stopServerWithTimeout(final long timeoutMs) {
128+
final Thread stopThread = new Thread(() -> {
129+
try {
130+
sshServer.stop(true);
131+
} catch (final Exception e) {
132+
// ignore
133+
}
134+
}, "sshd-stop");
135+
stopThread.setDaemon(true);
136+
stopThread.start();
137+
try {
138+
stopThread.join(timeoutMs);
139+
} catch (final InterruptedException e) {
140+
Thread.currentThread().interrupt();
141+
}
142+
}
143+
144+
private static String baseUri() {
145+
return String.format("sftp://%s@localhost:%d", TEST_USERNAME, serverPort);
146+
}
147+
148+
private FileSystemOptions authOptions() throws FileSystemException {
149+
final FileSystemOptions opts = new FileSystemOptions();
150+
final StaticUserAuthenticator auth = new StaticUserAuthenticator(null, TEST_USERNAME, TEST_PASSWORD);
151+
DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, auth);
152+
configureSftpOptions(opts);
153+
return opts;
154+
}
155+
156+
private static void configureSftpOptions(final FileSystemOptions opts) throws FileSystemException {
157+
final SftpFileSystemConfigBuilder builder = SftpFileSystemConfigBuilder.getInstance();
158+
builder.setStrictHostKeyChecking(opts, "no");
159+
builder.setUserInfo(opts, new TrustEveryoneUserInfo());
160+
builder.setIdentityRepositoryFactory(opts, new TestIdentityRepositoryFactory());
161+
builder.setConnectTimeout(opts, Duration.ofSeconds(60));
162+
builder.setSessionTimeout(opts, Duration.ofSeconds(60));
163+
}
164+
165+
@Test
166+
void testResolveFile() throws FileSystemException {
167+
final FileSystemOptions opts = authOptions();
168+
try (FileObject file = manager.resolveFile(baseUri() + "/read-tests/file1.txt", opts)) {
169+
assertNotNull(file);
170+
assertTrue(file.exists(), "file1.txt should exist");
171+
assertEquals(FileType.FILE, file.getType());
172+
assertNotNull(file.getContent(), "Content should be readable when credentials are correct");
173+
}
174+
}
175+
176+
@Test
177+
void testResolveFolder() throws FileSystemException {
178+
final FileSystemOptions opts = authOptions();
179+
try (FileObject folder = manager.resolveFile(baseUri() + "/read-tests", opts)) {
180+
assertNotNull(folder);
181+
assertTrue(folder.exists(), "read-tests folder should exist");
182+
}
183+
}
184+
185+
@Test
186+
void testResolveFolderWithTrailingSlash() throws FileSystemException {
187+
final FileSystemOptions opts = authOptions();
188+
try (FileObject folder = manager.resolveFile(baseUri() + "/read-tests/", opts)) {
189+
assertNotNull(folder);
190+
assertTrue(folder.exists(), "read-tests/ folder should exist");
191+
}
192+
}
193+
194+
@Test
195+
void testWrongCredentialsThrowsException() throws FileSystemException {
196+
final FileSystemOptions opts = new FileSystemOptions();
197+
final StaticUserAuthenticator auth = new StaticUserAuthenticator(null, "wronguser", "wrongpassword");
198+
DefaultFileSystemConfigBuilder.getInstance().setUserAuthenticator(opts, auth);
199+
configureSftpOptions(opts);
200+
201+
final String wrongUserUri = String.format("sftp://wronguser@localhost:%d/read-tests/file1.txt", serverPort);
202+
assertThrows(FileSystemException.class, () -> {
203+
try (FileObject file = manager.resolveFile(wrongUserUri, opts)) {
204+
file.exists();
205+
}
206+
}, "Expected FileSystemException when accessing a resource with wrong credentials");
207+
}
208+
209+
private static class TestFileSystemFactory implements FileSystemFactory {
210+
@Override
211+
public FileSystemView createFileSystemView(final Session session) {
212+
final String home = getTestDirectoryFile().getAbsolutePath();
213+
final String user = session.getUsername();
214+
return new FileSystemView() {
215+
@Override
216+
public SshFile getFile(final SshFile baseDir, final String file) {
217+
return getFile(baseDir.getAbsolutePath(), file);
218+
}
219+
@Override
220+
public SshFile getFile(final String file) {
221+
return getFile(home, file);
222+
}
223+
private SshFile getFile(final String dir, final String file) {
224+
final String normalized = NativeSshFile.normalizeSeparateChar(file);
225+
final String homeNorm = NativeSshFile.normalizeSeparateChar(home);
226+
final String prefix = removePrefix(homeNorm);
227+
String userFile = removePrefix(normalized);
228+
final File f = userFile.startsWith(prefix)
229+
? new File(userFile)
230+
: new File(prefix, userFile);
231+
userFile = removePrefix(NativeSshFile.normalizeSeparateChar(f.getAbsolutePath()));
232+
return new AccessibleNativeSshFile(userFile, f, user);
233+
}
234+
private String removePrefix(final String s) {
235+
final int idx = s.indexOf('/');
236+
return idx < 1 ? s : s.substring(idx);
237+
}
238+
};
239+
}
240+
}
241+
242+
private static class AccessibleNativeSshFile extends NativeSshFile {
243+
AccessibleNativeSshFile(final String fileName, final File file, final String userName) {
244+
super(fileName, file, userName);
245+
}
246+
}
247+
}

0 commit comments

Comments
 (0)