This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 024d0c14ff01 CAMEL-22379: Run username fallback tests in isolated JVM
(#21383)
024d0c14ff01 is described below
commit 024d0c14ff010c12ef3bd0984cd649ce353f0a85
Author: Luigi De Masi <[email protected]>
AuthorDate: Tue Feb 10 14:02:21 2026 +0100
CAMEL-22379: Run username fallback tests in isolated JVM (#21383)
Tests that modify PathUtils.userHomeFolderResolver require JVM isolation
because MINA SSHD caches the home folder path globally
- Add Username Resolution section to mina-sftp-component.adoc
- Use PathUtils.setUserHomeFolderResolver() in tests to avoid dependency on
user's ~/.ssh/config
- Add tests for SSH config and OS username fallback scenarios
---
components/camel-mina-sftp/pom.xml | 24 +++-
.../src/main/docs/mina-sftp-component.adoc | 74 ++++++++++++
.../MinaSftpConfigurationValidationIT.java | 128 +++++++++++++++++++--
3 files changed, 213 insertions(+), 13 deletions(-)
diff --git a/components/camel-mina-sftp/pom.xml
b/components/camel-mina-sftp/pom.xml
index 8c5b41061275..a7e618d7dc21 100644
--- a/components/camel-mina-sftp/pom.xml
+++ b/components/camel-mina-sftp/pom.xml
@@ -133,11 +133,32 @@
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
- <id>integration-test</id>
+ <id>standard-integration-tests</id>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
+ <configuration>
+ <excludedGroups>isolated</excludedGroups>
+ <!-- <forkCount>1</forkCount>
+ <reuseForks>true</reuseForks> -->
+ </configuration>
+ </execution>
+ <execution>
+ <id>default</id>
+ <phase>none</phase>
+ </execution>
+ <execution>
+ <id>isolated-integration-tests</id>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ <configuration>
+ <groups>isolated</groups>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ </configuration>
</execution>
</executions>
</plugin>
@@ -145,5 +166,4 @@
</build>
</profile>
</profiles>
-
</project>
\ No newline at end of file
diff --git a/components/camel-mina-sftp/src/main/docs/mina-sftp-component.adoc
b/components/camel-mina-sftp/src/main/docs/mina-sftp-component.adoc
index f96ff01a3066..2074806a7cd1 100644
--- a/components/camel-mina-sftp/src/main/docs/mina-sftp-component.adoc
+++ b/components/camel-mina-sftp/src/main/docs/mina-sftp-component.adoc
@@ -38,6 +38,80 @@ include::partial$component-endpoint-headers.adoc[]
// endpoint options: START
// endpoint options: END
+== Username Resolution
+
+When no username is specified in the URI, the mina-sftp component follows the
same username resolution order as the JSch-based camel-sftp component, matching
standard SSH client behavior.
+
+=== Resolution Priority Order
+
+[cols="1,2,3"]
+|===
+| Priority | Source | Description
+
+| 1 (highest)
+| URI parameter
+| Username specified directly in URI: `mina-sftp://myuser@host/path`
+
+| 2
+| SSH config file
+| `User` directive in `~/.ssh/config` for the target host
+
+| 3 (lowest)
+| OS username
+| `System.getProperty("user.name")` - current operating system user
+|===
+
+=== SSH Config Resolution
+
+The component reads the user's `~/.ssh/config` file (if it exists) and applies
matching `Host` entries. For example:
+
+[source]
+----
+# ~/.ssh/config
+Host myserver.example.com
+ User deployuser
+
+Host *.internal.example.com
+ User admin
+
+Host *
+ User defaultuser
+----
+
+With this configuration:
+* `mina-sftp://myserver.example.com/path` uses username `deployuser`
+* `mina-sftp://app.internal.example.com/path` uses username `admin`
+* `mina-sftp://other.example.com/path` uses username `defaultuser`
+
+=== OS Username Fallback
+
+If the SSH config file exists but does not contain a `User` directive for the
target host, the component falls back to the operating system username
(`System.getProperty("user.name")`).
+
+IMPORTANT: The OS username fallback only occurs when the SSH config file
exists. If no `~/.ssh/config` file exists at all, the connection will fail with
"No username specified when the session was created".
+
+=== Explicit Username Recommended
+
+For production deployments, always specify the username explicitly in the URI
to ensure predictable behavior across different environments:
+
+[source,java]
+----
+// Recommended: explicit username
+from("mina-sftp://deployuser@host/path?password=secret")
+ .to("file:local");
+
+// Not recommended: relies on SSH config or OS username
+from("mina-sftp://host/path?password=secret")
+ .to("file:local");
+----
+
+=== Compatibility with camel-sftp
+
+This username resolution behavior is identical to the JSch-based camel-sftp
component, ensuring seamless migration. Both components:
+
+* Check the URI for an explicit username first
+* Fall back to `~/.ssh/config` if no username in URI
+* Fall back to OS username if SSH config exists but has no `User` directive
+
== Authentication
The MINA SFTP component supports multiple authentication methods:
diff --git
a/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/integration/MinaSftpConfigurationValidationIT.java
b/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/integration/MinaSftpConfigurationValidationIT.java
index 9a4557dbf0ec..9b1e7a034893 100644
---
a/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/integration/MinaSftpConfigurationValidationIT.java
+++
b/components/camel-mina-sftp/src/test/java/org/apache/camel/component/file/remote/mina/integration/MinaSftpConfigurationValidationIT.java
@@ -16,12 +16,19 @@
*/
package org.apache.camel.component.file.remote.mina.integration;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
import org.apache.camel.CamelExecutionException;
import org.apache.camel.Exchange;
import org.apache.camel.ResolveEndpointFailedException;
import org.apache.camel.RuntimeCamelException;
-import org.apache.camel.component.file.GenericFileOperationFailedException;
+import org.apache.sshd.common.util.io.PathUtils;
+import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Timeout;
import org.junit.jupiter.api.condition.EnabledIf;
@@ -34,14 +41,72 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* Integration tests for MINA SFTP configuration validation and error messages.
+ * <p>
+ * <b>Why this test requires isolation:</b> This test class modifies the global
+ * {@code PathUtils.setUserHomeFolderResolver()} to test username resolution
fallback behavior. MINA SSHD's
+ * {@code PathUtils} lazily caches the home folder path on first access via
{@code DefaultConfigFileHostEntryResolver},
+ * and this cache cannot be reset once populated. Running this test in a
shared JVM with other tests would cause
+ * interference.
+ * <p>
+ * <b>How isolation is achieved:</b> The {@code @Tag("isolated")} annotation
marks this class for separate execution.
+ * The Maven Failsafe plugin is configured in pom.xml with two executions:
+ * <ul>
+ * <li>{@code standard-integration-tests} - runs all tests EXCEPT those tagged
"isolated"</li>
+ * <li>{@code isolated-integration-tests} - runs ONLY tests tagged "isolated"
with {@code reuseForks=false} to ensure a
+ * fresh JVM</li>
+ * </ul>
+ * <p>
+ * <b>Username resolution fallback:</b> When no username is specified in the
URI, MINA SSHD (like JSch/camel-sftp) uses
+ * this priority order:
+ * <ol>
+ * <li>URI parameter ({@code username=...})</li>
+ * <li>{@code ~/.ssh/config} User directive</li>
+ * <li>{@code System.getProperty("user.name")}</li>
+ * </ol>
+ *
+ * @see <a href=
+ *
"https://github.com/apache/mina-sshd/blob/master/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java">MINA
+ * SSHD HostConfigEntry</a>
*/
+@Tag("isolated")
@EnabledIf(value =
"org.apache.camel.test.infra.ftp.services.embedded.SftpUtil#hasRequiredAlgorithms('src/test/resources/hostkey.pem')")
public class MinaSftpConfigurationValidationIT extends
MinaSftpServerTestSupport {
private static final Logger log =
LoggerFactory.getLogger(MinaSftpConfigurationValidationIT.class);
+ private static final String SSH_CONFIG_USERNAME = "sshconfiguser";
+ private static Path testHomeDir;
+
+ // Static initializer to set up test home BEFORE any MINA SSHD code runs.
+ // This must happen at class load time because PathUtils caches the home
folder
+ // lazily on first access. If we wait until @BeforeAll, parent class
initialization
+ // may have already triggered the cache.
+ static {
+ try {
+ testHomeDir = Paths.get("target/test-home-" +
System.currentTimeMillis());
+ Path sshDir = testHomeDir.resolve(".ssh");
+ Files.createDirectories(sshDir);
+
+ // Create SSH config with a known username for testing
+ // Priority order: 1) URI username, 2) ~/.ssh/config, 3)
System.getProperty("user.name")
+ Files.writeString(sshDir.resolve("config"),
+ "Host *\n User " + SSH_CONFIG_USERNAME + "\n");
+
+ // Override MINA SSHD's home folder resolution BEFORE anything
else runs
+ PathUtils.setUserHomeFolderResolver(() -> testHomeDir);
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to set up test home directory",
e);
+ }
+ }
private String ftpRootDir;
+ @AfterAll
+ static void teardownTestHome() {
+ // Reset to default home folder resolution
+ PathUtils.setUserHomeFolderResolver(null);
+ log.info("Reset home directory resolution to default");
+ }
+
@BeforeEach
public void doPostSetup() {
service.getFtpRootDir().toFile().mkdirs();
@@ -195,19 +260,60 @@ public class MinaSftpConfigurationValidationIT extends
MinaSftpServerTestSupport
@Test
@Timeout(30)
- public void testMissingCredentials() {
- // No username or password - should fail with IllegalStateException
(username required)
+ public void testMissingUsernameUsesSshConfigFallback() {
+ // No username in URI - should fall back to ~/.ssh/config (which we
control via PathUtils)
+ // This matches JSch/camel-sftp behavior where username resolution
order is:
+ // 1) URI parameter, 2) ~/.ssh/config, 3)
System.getProperty("user.name")
+ // See:
https://github.com/apache/mina-sshd/blob/master/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/HostConfigEntry.java
String uri = "mina-sftp://localhost:" + service.getPort() + "/" +
ftpRootDir
- + "?strictHostKeyChecking=no&useUserKnownHostsFile=false";
+ +
"?password=admin&strictHostKeyChecking=no&useUserKnownHostsFile=false";
- CamelExecutionException exception = assertThrows(
- CamelExecutionException.class,
- () -> template.sendBodyAndHeader(uri, "Content",
Exchange.FILE_NAME, "no-creds.txt"));
+ // Should succeed - the username is resolved from our test SSH config
+ // The embedded server accepts any username
+ template.sendBodyAndHeader(uri, "Content", Exchange.FILE_NAME,
"ssh-config-user.txt");
- Throwable cause = exception.getCause();
- // When no username is provided at all, the component throws
IllegalStateException
- assertTrue(cause instanceof IllegalStateException || cause instanceof
GenericFileOperationFailedException,
- "Should be IllegalStateException or
GenericFileOperationFailedException but was: " + cause.getClass());
+ assertTrue(ftpFile("ssh-config-user.txt").toFile().exists(),
+ "File should be uploaded using username from SSH config ('" +
SSH_CONFIG_USERNAME + "')");
+ }
+
+ @Test
+ @Timeout(30)
+ public void testMissingUsernameUsesOsUserFallback() throws IOException {
+ // Test the third fallback: when no username in URI AND ~/.ssh/config
exists
+ // but has no User directive, it should fall back to
System.getProperty("user.name")
+ // Priority: 1) URI, 2) ~/.ssh/config User directive, 3)
System.getProperty("user.name")
+ //
+ // Note: The OS username fallback only happens when the SSH config
FILE EXISTS
+ // but doesn't contain a User directive. If the file doesn't exist at
all,
+ // MINA SSHD returns HostConfigEntryResolver.EMPTY and no fallback
occurs.
+ // See:
https://github.com/apache/mina-sshd/blob/master/sshd-common/src/main/java/org/apache/sshd/client/config/hosts/ConfigFileHostEntryResolver.java
+
+ // Create a temporary home directory with an SSH config that has NO
User directive
+ Path osUserTestHome = Paths.get("target/test-home-osuser-" +
System.currentTimeMillis());
+ Path sshDir = osUserTestHome.resolve(".ssh");
+ Files.createDirectories(sshDir);
+
+ // Create SSH config without User directive - this triggers OS
username fallback
+ Files.writeString(sshDir.resolve("config"),
+ "# SSH config without User directive\nHost *\n
StrictHostKeyChecking no\n");
+
+ try {
+ // Temporarily override home to use our test config
+ PathUtils.setUserHomeFolderResolver(() -> osUserTestHome);
+
+ String uri = "mina-sftp://localhost:" + service.getPort() + "/" +
ftpRootDir
+ +
"?password=admin&strictHostKeyChecking=no&useUserKnownHostsFile=false";
+
+ // Should succeed - username falls back to
System.getProperty("user.name")
+ // The embedded server accepts any username
+ template.sendBodyAndHeader(uri, "Content", Exchange.FILE_NAME,
"os-user-fallback.txt");
+
+ assertTrue(ftpFile("os-user-fallback.txt").toFile().exists(),
+ "File should be uploaded using OS username ('" +
System.getProperty("user.name") + "')");
+ } finally {
+ // Restore the original test home with SSH config
+ PathUtils.setUserHomeFolderResolver(() -> testHomeDir);
+ }
}
@Test