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

Reply via email to