GH-3572: Migrate SFTP from jsch to sshd-sftp (#3892)
* GH-3572: Migrate SFTP from `jsch` to `sshd-sftp` Fixes https://github.com/spring-projects/spring-integration/issues/3572 * Rework SFTP module from the JSch API to more modern `sshd-sftp` * Migrate generics of most API from `ChannelSftp.LsEntry` to the `SftpClient.DirEntry` * Rework `DefaultSftpSessionFactory` to deal with an `SshClient` and create `SftpClient` wrapped to the `SftpSession` * Implement a `ResourceKnownHostsServerKeyVerifier` to load `known-hosts` from any possible resource * Implement an expected `SftpSession.list()` with just file name to take or pattern matching * Remove some unused tests and their config * Remove tests for custom `UserInfo` since we don't provide any custom out-of-the-box * Test a new `ResourceKnownHostsServerKeyVerifier` against default `known-hosts` file * * Some tests improvements * * Improve generics handling for `FileUtils`
This commit is contained in:
@@ -19,6 +19,8 @@ package org.springframework.integration.sftp.dsl;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.MessageSessionCallback;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
@@ -27,9 +29,6 @@ import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.sftp.gateway.SftpOutboundGateway;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* The factory for SFTP components.
|
||||
*
|
||||
@@ -46,7 +45,7 @@ public final class Sftp {
|
||||
* @param sessionFactory the session factory.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory) {
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
return inboundAdapter(sessionFactory, null);
|
||||
}
|
||||
|
||||
@@ -56,7 +55,7 @@ public final class Sftp {
|
||||
* @param receptionOrderComparator the comparator.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpInboundChannelAdapterSpec inboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
Comparator<File> receptionOrderComparator) {
|
||||
|
||||
return new SftpInboundChannelAdapterSpec(sessionFactory, receptionOrderComparator);
|
||||
@@ -69,7 +68,7 @@ public final class Sftp {
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpStreamingInboundChannelAdapterSpec inboundStreamingAdapter(
|
||||
RemoteFileTemplate<LsEntry> remoteFileTemplate) {
|
||||
RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate) {
|
||||
|
||||
return inboundStreamingAdapter(remoteFileTemplate, null);
|
||||
}
|
||||
@@ -82,8 +81,8 @@ public final class Sftp {
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpStreamingInboundChannelAdapterSpec inboundStreamingAdapter(
|
||||
RemoteFileTemplate<LsEntry> remoteFileTemplate,
|
||||
Comparator<LsEntry> receptionOrderComparator) {
|
||||
RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
Comparator<SftpClient.DirEntry> receptionOrderComparator) {
|
||||
|
||||
return new SftpStreamingInboundChannelAdapterSpec(remoteFileTemplate, receptionOrderComparator);
|
||||
}
|
||||
@@ -93,7 +92,7 @@ public final class Sftp {
|
||||
* @param sessionFactory the session factory.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory) {
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
return new SftpMessageHandlerSpec(sessionFactory);
|
||||
}
|
||||
|
||||
@@ -103,7 +102,7 @@ public final class Sftp {
|
||||
* @param fileExistsMode the file exists mode.
|
||||
* @return the spec.
|
||||
*/
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpMessageHandlerSpec outboundAdapter(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
FileExistsMode fileExistsMode) {
|
||||
|
||||
return outboundAdapter(new SftpRemoteFileTemplate(sessionFactory), fileExistsMode);
|
||||
@@ -141,7 +140,7 @@ public final class Sftp {
|
||||
* @param expression the remoteFilePath SpEL expression.
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
AbstractRemoteFileOutboundGateway.Command command, String expression) {
|
||||
|
||||
return outboundGateway(sessionFactory, command.getCommand(), expression);
|
||||
@@ -157,7 +156,7 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see RemoteFileTemplate
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
String command, String expression) {
|
||||
|
||||
return new SftpOutboundGatewaySpec(new SftpOutboundGateway(sessionFactory, command, expression));
|
||||
@@ -172,7 +171,7 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see RemoteFileTemplate
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<ChannelSftp.LsEntry> remoteFileTemplate,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
AbstractRemoteFileOutboundGateway.Command command, String expression) {
|
||||
|
||||
return outboundGateway(remoteFileTemplate, command.getCommand(), expression);
|
||||
@@ -187,7 +186,7 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see RemoteFileTemplate
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<ChannelSftp.LsEntry> remoteFileTemplate,
|
||||
public static SftpOutboundGatewaySpec outboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
String command, String expression) {
|
||||
|
||||
return new SftpOutboundGatewaySpec(new SftpOutboundGateway(remoteFileTemplate, command, expression));
|
||||
@@ -201,8 +200,8 @@ public final class Sftp {
|
||||
* @return the {@link SftpOutboundGatewaySpec}
|
||||
* @see MessageSessionCallback
|
||||
*/
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
MessageSessionCallback<ChannelSftp.LsEntry, ?> messageSessionCallback) {
|
||||
public static SftpOutboundGatewaySpec outboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
MessageSessionCallback<SftpClient.DirEntry, ?> messageSessionCallback) {
|
||||
|
||||
return new SftpOutboundGatewaySpec(new SftpOutboundGateway(sessionFactory, messageSessionCallback));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2020 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,8 @@ package org.springframework.integration.sftp.dsl;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.RemoteFileInboundChannelAdapterSpec;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
@@ -30,8 +32,6 @@ import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFil
|
||||
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizer;
|
||||
import org.springframework.integration.sftp.inbound.SftpInboundFileSynchronizingMessageSource;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* A {@link RemoteFileInboundChannelAdapterSpec} for an {@link SftpInboundFileSynchronizingMessageSource}.
|
||||
*
|
||||
@@ -40,10 +40,10 @@ import com.jcraft.jsch.ChannelSftp;
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SftpInboundChannelAdapterSpec
|
||||
extends RemoteFileInboundChannelAdapterSpec<ChannelSftp.LsEntry, SftpInboundChannelAdapterSpec,
|
||||
SftpInboundFileSynchronizingMessageSource> {
|
||||
extends RemoteFileInboundChannelAdapterSpec<SftpClient.DirEntry, SftpInboundChannelAdapterSpec,
|
||||
SftpInboundFileSynchronizingMessageSource> {
|
||||
|
||||
protected SftpInboundChannelAdapterSpec(SessionFactory<ChannelSftp.LsEntry> sessionFactory,
|
||||
protected SftpInboundChannelAdapterSpec(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
Comparator<File> comparator) {
|
||||
|
||||
super(new SftpInboundFileSynchronizer(sessionFactory));
|
||||
@@ -70,9 +70,10 @@ public class SftpInboundChannelAdapterSpec
|
||||
return filter(composeFilters(new SftpRegexPatternFileListFilter(regex)));
|
||||
}
|
||||
|
||||
private CompositeFileListFilter<ChannelSftp.LsEntry> composeFilters(FileListFilter<ChannelSftp.LsEntry>
|
||||
private CompositeFileListFilter<SftpClient.DirEntry> composeFilters(FileListFilter<SftpClient.DirEntry>
|
||||
fileListFilter) {
|
||||
CompositeFileListFilter<ChannelSftp.LsEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
|
||||
CompositeFileListFilter<SftpClient.DirEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
compositeFileListFilter.addFilters(fileListFilter,
|
||||
new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpMessageSource"));
|
||||
return compositeFileListFilter;
|
||||
|
||||
@@ -16,14 +16,14 @@
|
||||
|
||||
package org.springframework.integration.sftp.dsl;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.FileTransferringMessageHandlerSpec;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.sftp.outbound.SftpMessageHandler;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Joaquin Santana
|
||||
@@ -32,9 +32,9 @@ import com.jcraft.jsch.ChannelSftp;
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SftpMessageHandlerSpec
|
||||
extends FileTransferringMessageHandlerSpec<ChannelSftp.LsEntry, SftpMessageHandlerSpec> {
|
||||
extends FileTransferringMessageHandlerSpec<SftpClient.DirEntry, SftpMessageHandlerSpec> {
|
||||
|
||||
protected SftpMessageHandlerSpec(SessionFactory<ChannelSftp.LsEntry> sessionFactory) {
|
||||
protected SftpMessageHandlerSpec(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
this.target = new SftpMessageHandler(sessionFactory);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package org.springframework.integration.sftp.dsl;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.RemoteFileOutboundGatewaySpec;
|
||||
import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOutboundGateway;
|
||||
import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilter;
|
||||
import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
@@ -30,10 +30,10 @@ import com.jcraft.jsch.ChannelSftp;
|
||||
* @since 5.0
|
||||
*/
|
||||
public class SftpOutboundGatewaySpec
|
||||
extends RemoteFileOutboundGatewaySpec<ChannelSftp.LsEntry, SftpOutboundGatewaySpec> {
|
||||
extends RemoteFileOutboundGatewaySpec<SftpClient.DirEntry, SftpOutboundGatewaySpec> {
|
||||
|
||||
|
||||
protected SftpOutboundGatewaySpec(AbstractRemoteFileOutboundGateway<ChannelSftp.LsEntry> outboundGateway) {
|
||||
protected SftpOutboundGatewaySpec(AbstractRemoteFileOutboundGateway<SftpClient.DirEntry> outboundGateway) {
|
||||
super(outboundGateway);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors.
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,8 @@ package org.springframework.integration.sftp.dsl;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.dsl.RemoteFileStreamingInboundChannelAdapterSpec;
|
||||
import org.springframework.integration.file.filters.CompositeFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
@@ -28,8 +30,6 @@ import org.springframework.integration.sftp.filters.SftpRegexPatternFileListFilt
|
||||
import org.springframework.integration.sftp.filters.SftpSimplePatternFileListFilter;
|
||||
import org.springframework.integration.sftp.inbound.SftpStreamingMessageSource;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
*
|
||||
@@ -37,11 +37,11 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
*
|
||||
*/
|
||||
public class SftpStreamingInboundChannelAdapterSpec
|
||||
extends RemoteFileStreamingInboundChannelAdapterSpec<LsEntry, SftpStreamingInboundChannelAdapterSpec,
|
||||
SftpStreamingMessageSource> {
|
||||
extends RemoteFileStreamingInboundChannelAdapterSpec<SftpClient.DirEntry,
|
||||
SftpStreamingInboundChannelAdapterSpec, SftpStreamingMessageSource> {
|
||||
|
||||
protected SftpStreamingInboundChannelAdapterSpec(RemoteFileTemplate<LsEntry> remoteFileTemplate,
|
||||
Comparator<LsEntry> comparator) {
|
||||
protected SftpStreamingInboundChannelAdapterSpec(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
Comparator<SftpClient.DirEntry> comparator) {
|
||||
|
||||
this.target = new SftpStreamingMessageSource(remoteFileTemplate, comparator);
|
||||
}
|
||||
@@ -68,8 +68,10 @@ public class SftpStreamingInboundChannelAdapterSpec
|
||||
return filter(composeFilters(new SftpRegexPatternFileListFilter(regex)));
|
||||
}
|
||||
|
||||
private CompositeFileListFilter<LsEntry> composeFilters(FileListFilter<LsEntry> fileListFilter) {
|
||||
CompositeFileListFilter<LsEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
private CompositeFileListFilter<SftpClient.DirEntry> composeFilters(
|
||||
FileListFilter<SftpClient.DirEntry> fileListFilter) {
|
||||
|
||||
CompositeFileListFilter<SftpClient.DirEntry> compositeFileListFilter = new CompositeFileListFilter<>();
|
||||
compositeFileListFilter.addFilters(fileListFilter,
|
||||
new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpStreamingMessageSource"));
|
||||
return compositeFileListFilter;
|
||||
|
||||
@@ -16,40 +16,42 @@
|
||||
|
||||
package org.springframework.integration.sftp.filters;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractPersistentAcceptOnceFileListFilter;
|
||||
import org.springframework.integration.metadata.ConcurrentMetadataStore;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Persistent file list filter using the server's file timestamp to detect if we've already
|
||||
* 'seen' this file.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author David Liu
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
public class SftpPersistentAcceptOnceFileListFilter extends AbstractPersistentAcceptOnceFileListFilter<LsEntry> {
|
||||
public class SftpPersistentAcceptOnceFileListFilter
|
||||
extends AbstractPersistentAcceptOnceFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpPersistentAcceptOnceFileListFilter(ConcurrentMetadataStore store, String prefix) {
|
||||
super(store, prefix);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long modified(LsEntry file) {
|
||||
return ((long) file.getAttrs().getMTime()) * 1000; // NOSONAR magic number
|
||||
protected long modified(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String fileName(LsEntry file) {
|
||||
protected String fileName(SftpClient.DirEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,19 +18,20 @@ package org.springframework.integration.sftp.filters;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import org.springframework.integration.file.filters.AbstractRegexPatternFileListFilter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AbstractRegexPatternFileListFilter} for SFTP.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpRegexPatternFileListFilter extends AbstractRegexPatternFileListFilter<ChannelSftp.LsEntry> {
|
||||
public class SftpRegexPatternFileListFilter extends AbstractRegexPatternFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpRegexPatternFileListFilter(String pattern) {
|
||||
super(pattern);
|
||||
@@ -42,13 +43,13 @@ public class SftpRegexPatternFileListFilter extends AbstractRegexPatternFileList
|
||||
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry entry) {
|
||||
protected String getFilename(SftpClient.DirEntry entry) {
|
||||
return (entry != null) ? entry.getFilename() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,19 +16,20 @@
|
||||
|
||||
package org.springframework.integration.sftp.filters;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import org.springframework.integration.file.filters.AbstractSimplePatternFileListFilter;
|
||||
|
||||
/**
|
||||
* Implementation of {@link AbstractSimplePatternFileListFilter} for SFTP.
|
||||
*
|
||||
* @author Mark Fisher
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpSimplePatternFileListFilter extends AbstractSimplePatternFileListFilter<ChannelSftp.LsEntry> {
|
||||
public class SftpSimplePatternFileListFilter extends AbstractSimplePatternFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpSimplePatternFileListFilter(String pattern) {
|
||||
super(pattern);
|
||||
@@ -36,13 +37,13 @@ public class SftpSimplePatternFileListFilter extends AbstractSimplePatternFileLi
|
||||
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry entry) {
|
||||
protected String getFilename(SftpClient.DirEntry entry) {
|
||||
return (entry != null) ? entry.getFilename() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -19,40 +19,45 @@ package org.springframework.integration.sftp.filters;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.filters.AbstractMarkerFilePresentFileListFilter;
|
||||
import org.springframework.integration.file.filters.FileListFilter;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* SFTP implementation of {@link AbstractMarkerFilePresentFileListFilter}.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
public class SftpSystemMarkerFilePresentFileListFilter extends AbstractMarkerFilePresentFileListFilter<LsEntry> {
|
||||
public class SftpSystemMarkerFilePresentFileListFilter
|
||||
extends AbstractMarkerFilePresentFileListFilter<SftpClient.DirEntry> {
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<LsEntry> filter) {
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<SftpClient.DirEntry> filter) {
|
||||
super(filter);
|
||||
}
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<LsEntry> filter, String suffix) {
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<SftpClient.DirEntry> filter, String suffix) {
|
||||
super(filter, suffix);
|
||||
}
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<LsEntry> filter,
|
||||
public SftpSystemMarkerFilePresentFileListFilter(FileListFilter<SftpClient.DirEntry> filter,
|
||||
Function<String, String> function) {
|
||||
|
||||
super(filter, function);
|
||||
}
|
||||
|
||||
public SftpSystemMarkerFilePresentFileListFilter(
|
||||
Map<FileListFilter<LsEntry>, Function<String, String>> filtersAndFunctions) {
|
||||
Map<FileListFilter<SftpClient.DirEntry>, Function<String, String>> filtersAndFunctions) {
|
||||
|
||||
super(filtersAndFunctions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry file) {
|
||||
protected String getFilename(SftpClient.DirEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,11 +16,14 @@
|
||||
|
||||
package org.springframework.integration.sftp.gateway;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
|
||||
import org.springframework.integration.file.remote.MessageSessionCallback;
|
||||
@@ -30,12 +33,6 @@ import org.springframework.integration.file.remote.gateway.AbstractRemoteFileOut
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
import org.springframework.integration.sftp.support.GeneralSftpException;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* Outbound Gateway for performing remote file operations via SFTP.
|
||||
@@ -45,16 +42,7 @@ import com.jcraft.jsch.SftpException;
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEntry> {
|
||||
|
||||
private static final Method LS_ENTRY_SET_FILENAME_METHOD;
|
||||
|
||||
static {
|
||||
LS_ENTRY_SET_FILENAME_METHOD = ReflectionUtils.findMethod(LsEntry.class, "setFilename", String.class);
|
||||
if (LS_ENTRY_SET_FILENAME_METHOD != null) {
|
||||
ReflectionUtils.makeAccessible(LS_ENTRY_SET_FILENAME_METHOD);
|
||||
}
|
||||
}
|
||||
public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* Construct an instance using the provided session factory and callback for
|
||||
@@ -62,8 +50,8 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param sessionFactory the session factory.
|
||||
* @param messageSessionCallback the callback.
|
||||
*/
|
||||
public SftpOutboundGateway(SessionFactory<LsEntry> sessionFactory,
|
||||
MessageSessionCallback<LsEntry, ?> messageSessionCallback) {
|
||||
public SftpOutboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory,
|
||||
MessageSessionCallback<SftpClient.DirEntry, ?> messageSessionCallback) {
|
||||
|
||||
this(new SftpRemoteFileTemplate(sessionFactory), messageSessionCallback);
|
||||
remoteFileTemplateExplicitlySet(false);
|
||||
@@ -75,8 +63,8 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param remoteFileTemplate the remote file template.
|
||||
* @param messageSessionCallback the callback.
|
||||
*/
|
||||
public SftpOutboundGateway(RemoteFileTemplate<LsEntry> remoteFileTemplate,
|
||||
MessageSessionCallback<LsEntry, ?> messageSessionCallback) {
|
||||
public SftpOutboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate,
|
||||
MessageSessionCallback<SftpClient.DirEntry, ?> messageSessionCallback) {
|
||||
|
||||
super(remoteFileTemplate, messageSessionCallback);
|
||||
}
|
||||
@@ -88,7 +76,7 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param command the command.
|
||||
* @param expression the filename expression.
|
||||
*/
|
||||
public SftpOutboundGateway(SessionFactory<LsEntry> sessionFactory, String command, String expression) {
|
||||
public SftpOutboundGateway(SessionFactory<SftpClient.DirEntry> sessionFactory, String command, String expression) {
|
||||
this(new SftpRemoteFileTemplate(sessionFactory), command, expression);
|
||||
remoteFileTemplateExplicitlySet(false);
|
||||
}
|
||||
@@ -100,46 +88,46 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
* @param command the command.
|
||||
* @param expression the filename expression.
|
||||
*/
|
||||
public SftpOutboundGateway(RemoteFileTemplate<LsEntry> remoteFileTemplate, String command, String expression) {
|
||||
public SftpOutboundGateway(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate, String command, String expression) {
|
||||
super(remoteFileTemplate, command, expression);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isLink(LsEntry file) {
|
||||
return file.getAttrs().isLink();
|
||||
protected boolean isLink(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().isSymbolicLink();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry file) {
|
||||
protected String getFilename(SftpClient.DirEntry file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(AbstractFileInfo<LsEntry> file) {
|
||||
protected String getFilename(AbstractFileInfo<SftpClient.DirEntry> file) {
|
||||
return file.getFilename();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<LsEntry>> asFileInfoList(Collection<LsEntry> files) {
|
||||
protected List<AbstractFileInfo<SftpClient.DirEntry>> asFileInfoList(Collection<SftpClient.DirEntry> files) {
|
||||
return files.stream()
|
||||
.map(SftpFileInfo::new)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getModified(LsEntry file) {
|
||||
return ((long) file.getAttrs().getMTime()) * 1000; // NOSONAR magic number
|
||||
protected long getModified(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LsEntry enhanceNameWithSubDirectory(LsEntry file, String directory) {
|
||||
ReflectionUtils.invokeMethod(LS_ENTRY_SET_FILENAME_METHOD, file, directory + file.getFilename());
|
||||
return file;
|
||||
protected SftpClient.DirEntry enhanceNameWithSubDirectory(SftpClient.DirEntry file, String directory) {
|
||||
return new SftpClient.DirEntry(directory + file.getFilename(), directory + file.getFilename(),
|
||||
file.getAttributes());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -153,17 +141,18 @@ public class SftpOutboundGateway extends AbstractRemoteFileOutboundGateway<LsEnt
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileOperations<LsEntry> remoteFileOperations, final String path, final int chmod) {
|
||||
remoteFileOperations
|
||||
.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
|
||||
}
|
||||
});
|
||||
protected void doChmod(RemoteFileOperations<SftpClient.DirEntry> remoteFileOperations, String path, int chmod) {
|
||||
remoteFileOperations.executeWithClient((ClientCallbackWithoutResult<SftpClient>) client -> {
|
||||
try {
|
||||
SftpClient.Attributes attributes = client.stat(path);
|
||||
attributes.setPermissions(chmod);
|
||||
client.setStat(path, attributes);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
|
||||
package org.springframework.integration.sftp.inbound;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Handles the synchronization between a remote SFTP directory and a local mount.
|
||||
*
|
||||
@@ -33,30 +33,30 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer<LsEntry> {
|
||||
public class SftpInboundFileSynchronizer extends AbstractInboundFileSynchronizer<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* Create a synchronizer with the {@code SessionFactory} used to acquire {@code Session} instances.
|
||||
* @param sessionFactory The session factory.
|
||||
*/
|
||||
public SftpInboundFileSynchronizer(SessionFactory<LsEntry> sessionFactory) {
|
||||
public SftpInboundFileSynchronizer(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
super(sessionFactory);
|
||||
doSetFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpMessageSource"));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isFile(LsEntry file) {
|
||||
return (file != null && file.getAttrs() != null && !file.getAttrs().isDir() && !file.getAttrs().isLink());
|
||||
protected boolean isFile(SftpClient.DirEntry file) {
|
||||
return file != null && file.getAttributes().isRegularFile();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getFilename(LsEntry file) {
|
||||
return (file != null ? file.getFilename() : null);
|
||||
protected String getFilename(SftpClient.DirEntry file) {
|
||||
return file != null ? file.getFilename() : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected long getModified(LsEntry file) {
|
||||
return (long) file.getAttrs().getMTime() * 1000; // NOSONAR magic number
|
||||
protected long getModified(SftpClient.DirEntry file) {
|
||||
return file.getAttributes().getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -19,31 +19,34 @@ package org.springframework.integration.sftp.inbound;
|
||||
import java.io.File;
|
||||
import java.util.Comparator;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizer;
|
||||
import org.springframework.integration.file.remote.synchronizer.AbstractInboundFileSynchronizingMessageSource;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.integration.core.MessageSource} implementation for SFTP
|
||||
* that delegates to an InboundFileSynchronizer.
|
||||
*
|
||||
* @author Josh Long
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpInboundFileSynchronizingMessageSource extends AbstractInboundFileSynchronizingMessageSource<ChannelSftp.LsEntry> {
|
||||
public class SftpInboundFileSynchronizingMessageSource
|
||||
extends AbstractInboundFileSynchronizingMessageSource<SftpClient.DirEntry> {
|
||||
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<LsEntry> synchronizer) {
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<SftpClient.DirEntry> synchronizer) {
|
||||
super(synchronizer);
|
||||
}
|
||||
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<LsEntry> synchronizer, Comparator<File> comparator) {
|
||||
public SftpInboundFileSynchronizingMessageSource(AbstractInboundFileSynchronizer<SftpClient.DirEntry> synchronizer,
|
||||
Comparator<File> comparator) {
|
||||
|
||||
super(synchronizer, comparator);
|
||||
}
|
||||
|
||||
|
||||
public String getComponentType() {
|
||||
return "sftp:inbound-channel-adapter";
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.integration.file.remote.AbstractRemoteFileStreamingMessageSource;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
@@ -28,8 +30,6 @@ import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
import org.springframework.integration.sftp.filters.SftpPersistentAcceptOnceFileListFilter;
|
||||
import org.springframework.integration.sftp.session.SftpFileInfo;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Message source for streaming SFTP remote file contents.
|
||||
*
|
||||
@@ -39,13 +39,13 @@ import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessageSource<LsEntry> {
|
||||
public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessageSource<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* Construct an instance with the supplied template.
|
||||
* @param template the template.
|
||||
*/
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<LsEntry> template) {
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<SftpClient.DirEntry> template) {
|
||||
this(template, null);
|
||||
}
|
||||
|
||||
@@ -56,8 +56,9 @@ public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessa
|
||||
* @param template the template.
|
||||
* @param comparator the comparator.
|
||||
*/
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<LsEntry> template,
|
||||
Comparator<LsEntry> comparator) {
|
||||
public SftpStreamingMessageSource(RemoteFileTemplate<SftpClient.DirEntry> template,
|
||||
Comparator<SftpClient.DirEntry> comparator) {
|
||||
|
||||
super(template, comparator);
|
||||
doSetFilter(new SftpPersistentAcceptOnceFileListFilter(new SimpleMetadataStore(), "sftpStreamingMessageSource"));
|
||||
}
|
||||
@@ -68,17 +69,17 @@ public class SftpStreamingMessageSource extends AbstractRemoteFileStreamingMessa
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<AbstractFileInfo<LsEntry>> asFileInfoList(Collection<LsEntry> files) {
|
||||
List<AbstractFileInfo<LsEntry>> canonicalFiles = new ArrayList<AbstractFileInfo<LsEntry>>();
|
||||
for (LsEntry file : files) {
|
||||
protected List<AbstractFileInfo<SftpClient.DirEntry>> asFileInfoList(Collection<SftpClient.DirEntry> files) {
|
||||
List<AbstractFileInfo<SftpClient.DirEntry>> canonicalFiles = new ArrayList<>();
|
||||
for (SftpClient.DirEntry file : files) {
|
||||
canonicalFiles.add(new SftpFileInfo(file));
|
||||
}
|
||||
return canonicalFiles;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isDirectory(LsEntry file) {
|
||||
return file != null && file.getAttrs().isDir();
|
||||
protected boolean isDirectory(SftpClient.DirEntry file) {
|
||||
return file != null && file.getAttributes().isDirectory();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,26 +16,28 @@
|
||||
|
||||
package org.springframework.integration.sftp.outbound;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.ClientCallbackWithoutResult;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.support.FileExistsMode;
|
||||
import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
import org.springframework.integration.sftp.support.GeneralSftpException;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* Subclass of {@link FileTransferringMessageHandler} for SFTP.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artme Bilan
|
||||
*
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class SftpMessageHandler extends FileTransferringMessageHandler<LsEntry> {
|
||||
public class SftpMessageHandler extends FileTransferringMessageHandler<SftpClient.DirEntry> {
|
||||
|
||||
/**
|
||||
* @param remoteFileTemplate the template.
|
||||
@@ -62,7 +64,7 @@ public class SftpMessageHandler extends FileTransferringMessageHandler<LsEntry>
|
||||
* @see FileTransferringMessageHandler#FileTransferringMessageHandler
|
||||
* (SessionFactory)
|
||||
*/
|
||||
public SftpMessageHandler(SessionFactory<LsEntry> sessionFactory) {
|
||||
public SftpMessageHandler(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
this(new SftpRemoteFileTemplate(sessionFactory));
|
||||
}
|
||||
|
||||
@@ -72,14 +74,16 @@ public class SftpMessageHandler extends FileTransferringMessageHandler<LsEntry>
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doChmod(RemoteFileTemplate<LsEntry> remoteFileTemplate, final String path, final int chmod) {
|
||||
remoteFileTemplate.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
protected void doChmod(RemoteFileTemplate<SftpClient.DirEntry> remoteFileTemplate, String path, int chmod) {
|
||||
remoteFileTemplate.executeWithClient((ClientCallbackWithoutResult<SftpClient>) client -> {
|
||||
try {
|
||||
client.chmod(chmod, path);
|
||||
SftpClient.Attributes attributes = client.stat(path);
|
||||
attributes.setPermissions(chmod);
|
||||
client.setStat(path, attributes);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
throw new GeneralSftpException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", e);
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException(
|
||||
"Failed to execute 'chmod " + Integer.toOctalString(chmod) + " " + path + "'", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,31 +17,38 @@
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyPair;
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Properties;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.sshd.client.SshClient;
|
||||
import org.apache.sshd.client.auth.keyboard.UserInteraction;
|
||||
import org.apache.sshd.client.auth.password.PasswordIdentityProvider;
|
||||
import org.apache.sshd.client.config.hosts.HostConfigEntry;
|
||||
import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier;
|
||||
import org.apache.sshd.client.keyverifier.RejectAllServerKeyVerifier;
|
||||
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.SshConstants;
|
||||
import org.apache.sshd.common.config.keys.FilePasswordProvider;
|
||||
import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
|
||||
import org.apache.sshd.common.util.io.resource.AbstractIoResource;
|
||||
import org.apache.sshd.common.util.io.resource.IoResource;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
import org.apache.sshd.common.util.security.SecurityUtils;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.client.SftpClientFactory;
|
||||
import org.apache.sshd.sftp.client.SftpVersionSelector;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.JavaUtils;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.file.remote.session.SharedSessionCapable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.Proxy;
|
||||
import com.jcraft.jsch.SocketFactory;
|
||||
import com.jcraft.jsch.UIKeyboardInteractive;
|
||||
import com.jcraft.jsch.UserInfo;
|
||||
|
||||
/**
|
||||
* Factory for creating {@link SftpSession} instances.
|
||||
@@ -57,62 +64,43 @@ import com.jcraft.jsch.UserInfo;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, SharedSessionCapable {
|
||||
public class DefaultSftpSessionFactory implements SessionFactory<SftpClient.DirEntry>, SharedSessionCapable {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(DefaultSftpSessionFactory.class);
|
||||
private final SshClient sshClient;
|
||||
|
||||
static {
|
||||
JSch.setLogger(new JschLogger());
|
||||
}
|
||||
|
||||
private final UserInfo userInfoWrapper = new UserInfoWrapper();
|
||||
|
||||
private final JSch jsch;
|
||||
private final AtomicBoolean initialized = new AtomicBoolean();
|
||||
|
||||
private final boolean isSharedSession;
|
||||
|
||||
private final Lock sharedSessionLock;
|
||||
|
||||
private boolean isInnerClient = false;
|
||||
|
||||
private String host;
|
||||
|
||||
private int port = 22; // NOSONAR magic number. The default
|
||||
private int port = SshConstants.DEFAULT_PORT;
|
||||
|
||||
private String user;
|
||||
|
||||
private String password;
|
||||
|
||||
private HostConfigEntry hostConfig;
|
||||
|
||||
private Resource knownHosts;
|
||||
|
||||
private Resource privateKey;
|
||||
|
||||
private String privateKeyPassphrase;
|
||||
|
||||
private Properties sessionConfig;
|
||||
|
||||
private Proxy proxy;
|
||||
|
||||
private SocketFactory socketFactory;
|
||||
|
||||
private Integer timeout;
|
||||
|
||||
private String clientVersion;
|
||||
|
||||
private String hostKeyAlias;
|
||||
|
||||
private Integer serverAliveInterval;
|
||||
|
||||
private Integer serverAliveCountMax;
|
||||
|
||||
private Boolean enableDaemonThread;
|
||||
|
||||
private UserInfo userInfo;
|
||||
private UserInteraction userInteraction;
|
||||
|
||||
private boolean allowUnknownKeys = false;
|
||||
|
||||
private Duration channelConnectTimeout;
|
||||
private Integer timeout;
|
||||
|
||||
private volatile JSchSessionWrapper sharedJschSession;
|
||||
private SftpVersionSelector sftpVersionSelector = SftpVersionSelector.CURRENT;
|
||||
|
||||
private volatile SftpClient sharedSftpClient;
|
||||
|
||||
public DefaultSftpSessionFactory() {
|
||||
this(false);
|
||||
@@ -122,16 +110,18 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* @param isSharedSession true if the session is to be shared.
|
||||
*/
|
||||
public DefaultSftpSessionFactory(boolean isSharedSession) {
|
||||
this(new JSch(), isSharedSession);
|
||||
this(SshClient.setUpDefaultClient(), isSharedSession);
|
||||
this.isInnerClient = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intended for use in tests so the jsch can be mocked.
|
||||
* @param jsch The jsch instance.
|
||||
* @param sshClient The SshClient instance.
|
||||
* @param isSharedSession true if the session is to be shared.
|
||||
*/
|
||||
public DefaultSftpSessionFactory(JSch jsch, boolean isSharedSession) {
|
||||
this.jsch = jsch;
|
||||
public DefaultSftpSessionFactory(SshClient sshClient, boolean isSharedSession) {
|
||||
Assert.notNull(sshClient, "'sshClient' must not be null");
|
||||
this.sshClient = sshClient;
|
||||
this.isSharedSession = isSharedSession;
|
||||
if (this.isSharedSession) {
|
||||
this.sharedSessionLock = new ReentrantLock();
|
||||
@@ -142,9 +132,9 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
}
|
||||
|
||||
/**
|
||||
* The url of the host you want connect to. This is a mandatory property.
|
||||
* The url of the host you want to connect to. This is a mandatory property.
|
||||
* @param host The host.
|
||||
* @see JSch#getSession(String, String, int)
|
||||
* @see SshClient#connect(String, String, int)
|
||||
*/
|
||||
public void setHost(String host) {
|
||||
this.host = host;
|
||||
@@ -155,7 +145,7 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* this value defaults to <code>22</code>. If specified, this properties must
|
||||
* be a positive number.
|
||||
* @param port The port.
|
||||
* @see JSch#getSession(String, String, int)
|
||||
* @see SshClient#connect(String, String, int)
|
||||
*/
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
@@ -164,7 +154,7 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
/**
|
||||
* The remote user to use. This is a mandatory property.
|
||||
* @param user The user.
|
||||
* @see JSch#getSession(String, String, int)
|
||||
* @see SshClient#connect(String, String, int)
|
||||
*/
|
||||
public void setUser(String user) {
|
||||
this.user = user;
|
||||
@@ -174,23 +164,36 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* The password to authenticate against the remote host. If a password is
|
||||
* not provided, then a {@link DefaultSftpSessionFactory#setPrivateKey(Resource) privateKey} is
|
||||
* mandatory.
|
||||
* Not allowed if {@link #setUserInfo(UserInfo) userInfo} is provided - the password is obtained
|
||||
* from that object.
|
||||
* @param password The password.
|
||||
* @see com.jcraft.jsch.Session#setPassword(String)
|
||||
* @see SshClient#setPasswordIdentityProvider(PasswordIdentityProvider)
|
||||
*/
|
||||
public void setPassword(String password) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A password must be configured on the externally provided SshClient instance");
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the filename that will be used for a host key repository.
|
||||
* The file has the same format as OpenSSH's known_hosts file.
|
||||
* Provide a {@link HostConfigEntry} as an alternative for the user/host/port options.
|
||||
* Can be configured with a proxy jump property.
|
||||
* @param hostConfig the {@link HostConfigEntry} for connection.
|
||||
* @since 6.0
|
||||
* @see SshClient#connect(HostConfigEntry)
|
||||
*/
|
||||
public void setHostConfig(HostConfigEntry hostConfig) {
|
||||
this.hostConfig = hostConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a {@link Resource} that will be used for a host key repository.
|
||||
* The data has to have the same format as OpenSSH's known_hosts file.
|
||||
* @param knownHosts the resource for known hosts.
|
||||
* @since 5.2.5
|
||||
* @see JSch#setKnownHosts(java.io.InputStream)
|
||||
* @see SshClient#setServerKeyVerifier(ServerKeyVerifier)
|
||||
*/
|
||||
public void setKnownHostsResource(Resource knownHosts) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"Known hosts must be configured on the externally provided SshClient instance");
|
||||
this.knownHosts = knownHosts;
|
||||
}
|
||||
|
||||
@@ -198,152 +201,43 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* Allows you to set a {@link Resource}, which represents the location of the
|
||||
* private key used for authenticating against the remote host. If the privateKey
|
||||
* is not provided, then the {@link DefaultSftpSessionFactory#setPassword(String) password}
|
||||
* property is mandatory (or {@link #setUserInfo(UserInfo) userInfo} that returns a
|
||||
* password.
|
||||
* property is mandatory.
|
||||
* @param privateKey The private key.
|
||||
* @see JSch#addIdentity(String)
|
||||
* @see JSch#addIdentity(String, String)
|
||||
* @see SshClient#setKeyIdentityProvider(KeyIdentityProvider)
|
||||
*/
|
||||
public void setPrivateKey(Resource privateKey) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A private key auth must be configured on the externally provided SshClient instance");
|
||||
this.privateKey = privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* The password for the private key. Optional.
|
||||
* Not allowed if {@link #setUserInfo(UserInfo) userInfo} is provided - the passphrase is obtained
|
||||
* from that object.
|
||||
* @param privateKeyPassphrase The private key passphrase.
|
||||
* @see JSch#addIdentity(String, String)
|
||||
* @see SshClient#setKeyIdentityProvider(KeyIdentityProvider)
|
||||
*/
|
||||
public void setPrivateKeyPassphrase(String privateKeyPassphrase) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A private key auth must be configured on the externally provided SshClient instance");
|
||||
this.privateKeyPassphrase = privateKeyPassphrase;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using {@link Properties}, you can set additional configuration settings on
|
||||
* the underlying JSch {@link com.jcraft.jsch.Session}.
|
||||
* @param sessionConfig The session configuration properties.
|
||||
* @see com.jcraft.jsch.Session#setConfig(Properties)
|
||||
*/
|
||||
public void setSessionConfig(Properties sessionConfig) {
|
||||
this.sessionConfig = sessionConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows for specifying a JSch-based {@link Proxy}. If set, then the proxy
|
||||
* object is used to create the connection to the remote host.
|
||||
* @param proxy The proxy.
|
||||
* @see com.jcraft.jsch.Session#setProxy(Proxy)
|
||||
*/
|
||||
public void setProxy(Proxy proxy) {
|
||||
this.proxy = proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to pass in a {@link SocketFactory}. The socket factory is used
|
||||
* to create a socket to the target host. When a {@link Proxy} is used, the
|
||||
* socket factory is passed to the proxy. By default plain TCP sockets are used.
|
||||
* @param socketFactory The socket factory.
|
||||
* @see com.jcraft.jsch.Session#setSocketFactory(SocketFactory)
|
||||
*/
|
||||
public void setSocketFactory(SocketFactory socketFactory) {
|
||||
this.socketFactory = socketFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* The timeout property is used as the socket timeout parameter, as well as
|
||||
* the default connection timeout. Defaults to <code>0</code>, which means,
|
||||
* that no timeout will occur.
|
||||
* @param timeout The timeout.
|
||||
* @see com.jcraft.jsch.Session#setTimeout(int)
|
||||
*/
|
||||
public void setTimeout(Integer timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows you to set the client version property. It's default depends on the
|
||||
* underlying JSch version but it will look like <code>SSH-2.0-JSCH-0.1.45</code>
|
||||
* @param clientVersion The client version.
|
||||
* @see com.jcraft.jsch.Session#setClientVersion(String)
|
||||
*/
|
||||
public void setClientVersion(String clientVersion) {
|
||||
this.clientVersion = clientVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the host key alias, used when comparing the host key to the known
|
||||
* hosts list.
|
||||
* @param hostKeyAlias The host key alias.
|
||||
* @see com.jcraft.jsch.Session#setHostKeyAlias(String)
|
||||
*/
|
||||
public void setHostKeyAlias(String hostKeyAlias) {
|
||||
this.hostKeyAlias = hostKeyAlias;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the timeout interval (milliseconds) before a server alive message is
|
||||
* sent, in case no message is received from the server.
|
||||
* @param serverAliveInterval The server alive interval.
|
||||
* @see com.jcraft.jsch.Session#setServerAliveInterval(int)
|
||||
*/
|
||||
public void setServerAliveInterval(Integer serverAliveInterval) {
|
||||
this.serverAliveInterval = serverAliveInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the number of server-alive messages, which will be sent without
|
||||
* any reply from the server before disconnecting. If not set, this property
|
||||
* defaults to <code>1</code>.
|
||||
* @param serverAliveCountMax The server alive count max.
|
||||
* @see com.jcraft.jsch.Session#setServerAliveCountMax(int)
|
||||
*/
|
||||
public void setServerAliveCountMax(Integer serverAliveCountMax) {
|
||||
this.serverAliveCountMax = serverAliveCountMax;
|
||||
}
|
||||
|
||||
/**
|
||||
* If true, all threads will be daemon threads. If set to <code>false</code>,
|
||||
* normal non-daemon threads will be used. This property will be set on the
|
||||
* underlying {@link com.jcraft.jsch.Session} using
|
||||
* {@link com.jcraft.jsch.Session#setDaemonThread(boolean)}. There, this
|
||||
* property will default to <code>false</code>, if not explicitly set.
|
||||
* @param enableDaemonThread true to enable a daemon thread.
|
||||
* @see com.jcraft.jsch.Session#setDaemonThread(boolean)
|
||||
*/
|
||||
public void setEnableDaemonThread(Boolean enableDaemonThread) {
|
||||
this.enableDaemonThread = enableDaemonThread;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a {@link UserInfo} which exposes control over dealing with new keys or key
|
||||
* Provide a {@link UserInteraction} which exposes control over dealing with new keys or key
|
||||
* changes. As Spring Integration will not normally allow user interaction, the
|
||||
* implementation must respond to Jsch calls in a suitable way.
|
||||
* <p>
|
||||
* Jsch calls {@link UserInfo#promptYesNo(String)} when connecting to an unknown host,
|
||||
* or when a known host's key has changed (see {@link #setKnownHostsResource(Resource)}
|
||||
* knownHosts}). Generally, it should return false as returning true will accept all
|
||||
* new keys or key changes.
|
||||
* <p>
|
||||
* If no {@link UserInfo} is provided, the behavior is defined by
|
||||
* {@link #setAllowUnknownKeys(boolean) allowUnknownKeys}.
|
||||
* <p>
|
||||
* If {@link #setPassword(String) setPassword} is invoked with a non-null password, it will
|
||||
* override any password in the supplied {@link UserInfo}.
|
||||
* <p>
|
||||
* <b>NOTE: When this is provided, the {@link #setPassword(String) password} and
|
||||
* {@link #setPrivateKeyPassphrase(String) passphrase} are not allowed because those values
|
||||
* will be obtained from the {@link UserInfo}.</b>
|
||||
* @param userInfo the UserInfo.
|
||||
* implementation must respond to SSH protocol calls in a suitable way.
|
||||
* @param userInteraction the UserInteraction.
|
||||
* @since 4.1.7
|
||||
* @see com.jcraft.jsch.Session#setUserInfo(com.jcraft.jsch.UserInfo)
|
||||
* @see SshClient#setUserInteraction(UserInteraction)
|
||||
*/
|
||||
public void setUserInfo(UserInfo userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
public void setUserInteraction(UserInteraction userInteraction) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"A `UserInteraction` must be configured on the externally provided SshClient instance");
|
||||
this.userInteraction = userInteraction;
|
||||
}
|
||||
|
||||
/**
|
||||
* When no {@link UserInfo} has been provided, set to true to unconditionally allow
|
||||
* When no {@link #knownHosts} has been provided, set to true to unconditionally allow
|
||||
* connecting to an unknown host or when a host's key has changed (see
|
||||
* {@link #setKnownHostsResource(Resource) knownHosts}). Default false (since 4.2).
|
||||
* Set to true if a knownHosts file is not provided.
|
||||
@@ -351,17 +245,25 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
* @since 4.1.7
|
||||
*/
|
||||
public void setAllowUnknownKeys(boolean allowUnknownKeys) {
|
||||
Assert.state(this.isInnerClient,
|
||||
"An `AcceptAllServerKeyVerifier` must be configured on the externally provided SshClient instance");
|
||||
this.allowUnknownKeys = allowUnknownKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connection timeout.
|
||||
* @param timeout the timeout to set.
|
||||
* @since 5.2
|
||||
* The timeout property is used as the socket timeout parameter, as well as
|
||||
* the default connection timeout. Defaults to <code>0</code>, which means,
|
||||
* that no timeout will occur.
|
||||
* @param timeout The timeout.
|
||||
* @see org.apache.sshd.client.future.ConnectFuture#verify(long)
|
||||
*/
|
||||
public void setChannelConnectTimeout(Duration timeout) {
|
||||
Assert.notNull(timeout, "'connectTimeout' cannot be null");
|
||||
this.channelConnectTimeout = timeout;
|
||||
public void setTimeout(Integer timeout) {
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public void setSftpVersionSelector(SftpVersionSelector sftpVersionSelector) {
|
||||
Assert.notNull(sftpVersionSelector, "'sftpVersionSelector' must noy be null");
|
||||
this.sftpVersionSelector = sftpVersionSelector;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -370,18 +272,19 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
if (this.sharedSessionLock != null) {
|
||||
this.sharedSessionLock.lock();
|
||||
}
|
||||
JSchSessionWrapper jschSession = this.sharedJschSession;
|
||||
SftpClient sftpClient = this.sharedSftpClient;
|
||||
try {
|
||||
boolean freshJschSession = false;
|
||||
if (jschSession == null || !jschSession.isConnected()) {
|
||||
jschSession = new JSchSessionWrapper(initJschSession());
|
||||
freshJschSession = true;
|
||||
boolean freshSftpClient = false;
|
||||
if (sftpClient == null || !sftpClient.isOpen()) {
|
||||
sftpClient =
|
||||
SftpClientFactory.instance()
|
||||
.createSftpClient(initClientSession(), this.sftpVersionSelector);
|
||||
freshSftpClient = true;
|
||||
}
|
||||
sftpSession = new SftpSession(jschSession);
|
||||
JavaUtils.INSTANCE.acceptIfNotNull(this.channelConnectTimeout, sftpSession::setChannelConnectTimeout);
|
||||
sftpSession = new SftpSession(sftpClient);
|
||||
sftpSession.connect();
|
||||
if (this.isSharedSession && freshJschSession) {
|
||||
this.sharedJschSession = jschSession;
|
||||
if (this.isSharedSession && freshSftpClient) {
|
||||
this.sharedSftpClient = sftpClient;
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
@@ -392,60 +295,68 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
this.sharedSessionLock.unlock();
|
||||
}
|
||||
}
|
||||
jschSession.addChannel();
|
||||
return sftpSession;
|
||||
}
|
||||
|
||||
private com.jcraft.jsch.Session initJschSession() throws JSchException, IOException {
|
||||
private ClientSession initClientSession() throws IOException {
|
||||
Assert.hasText(this.host, "host must not be empty");
|
||||
Assert.hasText(this.user, "user must not be empty");
|
||||
Assert.isTrue(StringUtils.hasText(this.userInfoWrapper.getPassword()) || this.privateKey != null,
|
||||
"either a password or a private key is required");
|
||||
|
||||
if (this.port <= 0) {
|
||||
this.port = 22; // NOSONAR magic number
|
||||
}
|
||||
if (this.knownHosts != null) {
|
||||
this.jsch.setKnownHosts(this.knownHosts.getInputStream());
|
||||
}
|
||||
initClient();
|
||||
|
||||
// private key
|
||||
if (this.privateKey != null) {
|
||||
byte[] keyByteArray = FileCopyUtils.copyToByteArray(this.privateKey.getInputStream());
|
||||
String passphrase = this.userInfoWrapper.getPassphrase();
|
||||
if (StringUtils.hasText(passphrase)) {
|
||||
this.jsch.addIdentity(this.user, keyByteArray, null, passphrase.getBytes());
|
||||
}
|
||||
else {
|
||||
this.jsch.addIdentity(this.user, keyByteArray, null, null);
|
||||
}
|
||||
Duration verifyTimeout = this.timeout != null ? Duration.ofMillis(this.timeout) : null;
|
||||
HostConfigEntry hostConfig = this.hostConfig;
|
||||
if (hostConfig == null) {
|
||||
hostConfig =
|
||||
new HostConfigEntry(SshdSocketAddress.isIPv6Address(this.host) ? "" : this.host, this.host,
|
||||
this.port, this.user);
|
||||
}
|
||||
com.jcraft.jsch.Session jschSession = this.jsch.getSession(this.user, this.host, this.port);
|
||||
JavaUtils.INSTANCE
|
||||
.acceptIfNotNull(this.sessionConfig, jschSession::setConfig)
|
||||
.acceptIfHasText(this.userInfoWrapper.getPassword(), jschSession::setPassword);
|
||||
jschSession.setUserInfo(this.userInfoWrapper);
|
||||
ClientSession clientSession =
|
||||
this.sshClient.connect(hostConfig)
|
||||
.verify(verifyTimeout)
|
||||
.getSession();
|
||||
|
||||
try {
|
||||
if (this.timeout != null) {
|
||||
jschSession.setTimeout(this.timeout);
|
||||
clientSession.auth().verify(verifyTimeout);
|
||||
|
||||
return clientSession;
|
||||
}
|
||||
|
||||
private void initClient() throws IOException {
|
||||
if (this.initialized.compareAndSet(false, true)) {
|
||||
if (this.port <= 0) {
|
||||
this.port = SshConstants.DEFAULT_PORT;
|
||||
}
|
||||
if (this.serverAliveInterval != null) {
|
||||
jschSession.setServerAliveInterval(this.serverAliveInterval);
|
||||
ServerKeyVerifier serverKeyVerifier =
|
||||
this.allowUnknownKeys ? AcceptAllServerKeyVerifier.INSTANCE : RejectAllServerKeyVerifier.INSTANCE;
|
||||
if (this.knownHosts != null) {
|
||||
serverKeyVerifier = new ResourceKnownHostsServerKeyVerifier(this.knownHosts);
|
||||
}
|
||||
JavaUtils.INSTANCE
|
||||
.acceptIfNotNull(this.proxy, jschSession::setProxy)
|
||||
.acceptIfNotNull(this.socketFactory, jschSession::setSocketFactory)
|
||||
.acceptIfHasText(this.clientVersion, jschSession::setClientVersion)
|
||||
.acceptIfHasText(this.hostKeyAlias, jschSession::setHostKeyAlias)
|
||||
.acceptIfNotNull(this.serverAliveCountMax, jschSession::setServerAliveCountMax)
|
||||
.acceptIfNotNull(this.enableDaemonThread, jschSession::setDaemonThread);
|
||||
this.sshClient.setServerKeyVerifier(serverKeyVerifier);
|
||||
|
||||
this.sshClient.setPasswordIdentityProvider(PasswordIdentityProvider.wrapPasswords(this.password));
|
||||
if (this.privateKey != null) {
|
||||
IoResource<Resource> privateKeyResource =
|
||||
new AbstractIoResource<>(Resource.class, this.privateKey) {
|
||||
|
||||
@Override
|
||||
public InputStream openInputStream() throws IOException {
|
||||
return getResourceValue().getInputStream();
|
||||
}
|
||||
};
|
||||
try {
|
||||
Collection<KeyPair> keys =
|
||||
SecurityUtils.getKeyPairResourceParser()
|
||||
.loadKeyPairs(null, privateKeyResource,
|
||||
FilePasswordProvider.of(this.privateKeyPassphrase));
|
||||
this.sshClient.setKeyIdentityProvider(KeyIdentityProvider.wrapKeyPairs(keys));
|
||||
}
|
||||
catch (GeneralSecurityException ex) {
|
||||
throw new IOException("Cannot load private key: " + this.privateKey.getFilename(), ex);
|
||||
}
|
||||
}
|
||||
this.sshClient.setUserInteraction(this.userInteraction);
|
||||
this.sshClient.start();
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new BeanCreationException("Attempt to set additional properties of " +
|
||||
"the com.jcraft.jsch.Session resulted in error: " + e.getMessage(), e);
|
||||
}
|
||||
return jschSession;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -456,130 +367,7 @@ public class DefaultSftpSessionFactory implements SessionFactory<LsEntry>, Share
|
||||
@Override
|
||||
public void resetSharedSession() {
|
||||
Assert.state(this.isSharedSession, "Shared sessions are not being used");
|
||||
this.sharedJschSession = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class will delegate calls to a configured {@link UserInfo}, providing
|
||||
* sensible defaults if null. As the password is configured in this Factory, the
|
||||
* wrapper will return the factory's configured password and only delegate to the
|
||||
* UserInfo if null.
|
||||
*
|
||||
* @since 4.1.7
|
||||
*/
|
||||
private class UserInfoWrapper implements UserInfo, UIKeyboardInteractive {
|
||||
|
||||
UserInfoWrapper() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience to check whether enclosing factory's UserInfo is configured.
|
||||
* @return true if there's a delegate.
|
||||
*/
|
||||
private boolean hasDelegate() {
|
||||
return getDelegate() != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience to retrieve enclosing factory's UserInfo.
|
||||
* @return the {@link #userInfo} or null if not present.
|
||||
*/
|
||||
private UserInfo getDelegate() {
|
||||
return DefaultSftpSessionFactory.this.userInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassphrase() {
|
||||
if (hasDelegate()) {
|
||||
Assert.state(!StringUtils.hasText(DefaultSftpSessionFactory.this.privateKeyPassphrase),
|
||||
"When a 'UserInfo' is provided, 'privateKeyPassphrase' is not allowed");
|
||||
return getDelegate().getPassphrase();
|
||||
}
|
||||
else {
|
||||
return DefaultSftpSessionFactory.this.privateKeyPassphrase;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
if (hasDelegate()) {
|
||||
Assert.state(!StringUtils.hasText(DefaultSftpSessionFactory.this.password),
|
||||
"When a 'UserInfo' is provided, 'password' is not allowed");
|
||||
return getDelegate().getPassword();
|
||||
}
|
||||
else {
|
||||
return DefaultSftpSessionFactory.this.password;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean promptPassword(String message) {
|
||||
if (hasDelegate()) {
|
||||
return getDelegate().promptPassword(message);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UserInfo provided - " + message + ", returning: true");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean promptPassphrase(String message) {
|
||||
if (hasDelegate()) {
|
||||
return getDelegate().promptPassphrase(message);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UserInfo provided - " + message + ", returning: true");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean promptYesNo(String message) {
|
||||
LOGGER.info(message);
|
||||
if (hasDelegate()) {
|
||||
return getDelegate().promptYesNo(message);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UserInfo provided - " + message + ", returning: "
|
||||
+ DefaultSftpSessionFactory.this.allowUnknownKeys);
|
||||
}
|
||||
return DefaultSftpSessionFactory.this.allowUnknownKeys;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showMessage(String message) {
|
||||
if (hasDelegate()) {
|
||||
getDelegate().showMessage(message);
|
||||
}
|
||||
else {
|
||||
LOGGER.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] promptKeyboardInteractive(String destination, String name, String instruction, String[] prompt,
|
||||
boolean[] echo) {
|
||||
|
||||
if (hasDelegate() && getDelegate() instanceof UIKeyboardInteractive) {
|
||||
return ((UIKeyboardInteractive) getDelegate()).promptKeyboardInteractive(destination, name,
|
||||
instruction, prompt, echo);
|
||||
}
|
||||
else {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("No UIKeyboardInteractive provided - " + destination + ":" + name + ":" + instruction
|
||||
+ ":" + Arrays.asList(prompt) + ":" + Arrays.toString(echo));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
this.sharedSftpClient = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import com.jcraft.jsch.Session;
|
||||
|
||||
/**
|
||||
* A wrapper for a JSch session that maintains a channel count and
|
||||
* physically disconnects when the last channel is closed.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 3.0
|
||||
*
|
||||
*/
|
||||
class JSchSessionWrapper {
|
||||
|
||||
private final Session session;
|
||||
|
||||
private final AtomicInteger channels = new AtomicInteger();
|
||||
|
||||
JSchSessionWrapper(Session session) {
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
public void addChannel() {
|
||||
this.channels.incrementAndGet();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (this.channels.decrementAndGet() <= 0) {
|
||||
this.session.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
public final Session getSession() {
|
||||
return this.session;
|
||||
}
|
||||
|
||||
public boolean isConnected() {
|
||||
return this.session.isConnected();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import com.jcraft.jsch.Logger;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 2.0.1
|
||||
*/
|
||||
class JschLogger implements Logger {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog("com.jcraft.jsch");
|
||||
|
||||
public boolean isEnabled(int level) {
|
||||
switch (level) {
|
||||
case Logger.INFO:
|
||||
return LOGGER.isInfoEnabled();
|
||||
case Logger.WARN:
|
||||
return LOGGER.isWarnEnabled();
|
||||
case Logger.DEBUG:
|
||||
return LOGGER.isDebugEnabled();
|
||||
case Logger.ERROR:
|
||||
return LOGGER.isErrorEnabled();
|
||||
case Logger.FATAL:
|
||||
return LOGGER.isFatalEnabled();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void log(int level, String message) {
|
||||
switch (level) {
|
||||
case Logger.INFO:
|
||||
LOGGER.info(message);
|
||||
break;
|
||||
case Logger.WARN:
|
||||
LOGGER.warn(message);
|
||||
break;
|
||||
case Logger.DEBUG:
|
||||
LOGGER.debug(message);
|
||||
break;
|
||||
case Logger.ERROR:
|
||||
LOGGER.error(message);
|
||||
break;
|
||||
case Logger.FATAL:
|
||||
LOGGER.fatal(message);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import org.springframework.beans.factory.config.AbstractFactoryBean;
|
||||
|
||||
import com.jcraft.jsch.Proxy;
|
||||
import com.jcraft.jsch.ProxyHTTP;
|
||||
import com.jcraft.jsch.ProxySOCKS4;
|
||||
import com.jcraft.jsch.ProxySOCKS5;
|
||||
|
||||
/**
|
||||
* Spring-friendly factory bean to create Jsch {@link Proxy} objects.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class JschProxyFactoryBean extends AbstractFactoryBean<Proxy> {
|
||||
|
||||
public enum Type {
|
||||
HTTP, SOCKS4, SOCKS5
|
||||
}
|
||||
|
||||
private final Type type;
|
||||
|
||||
private final String host;
|
||||
|
||||
private final int port;
|
||||
|
||||
private final String user;
|
||||
|
||||
private final String password;
|
||||
|
||||
public JschProxyFactoryBean(Type type, String host, int port, String user, String password) {
|
||||
this.type = type;
|
||||
this.host = host;
|
||||
this.port = port;
|
||||
this.user = user;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
switch (this.type) {
|
||||
case SOCKS5:
|
||||
return ProxySOCKS5.class;
|
||||
case SOCKS4:
|
||||
return ProxySOCKS4.class;
|
||||
case HTTP:
|
||||
return ProxyHTTP.class;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid type:" + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Proxy createInstance() {
|
||||
switch (this.type) {
|
||||
case SOCKS5:
|
||||
ProxySOCKS5 socks5proxy = new ProxySOCKS5(this.host, this.port);
|
||||
socks5proxy.setUserPasswd(this.user, this.password);
|
||||
return socks5proxy;
|
||||
case SOCKS4:
|
||||
ProxySOCKS4 socks4proxy = new ProxySOCKS4(this.host, this.port);
|
||||
socks4proxy.setUserPasswd(this.user, this.password);
|
||||
return socks4proxy;
|
||||
case HTTP:
|
||||
ProxyHTTP httpProxy = new ProxyHTTP(this.host, this.port);
|
||||
httpProxy.setUserPasswd(this.user, this.password);
|
||||
return httpProxy;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid type:" + this.type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.SocketAddress;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.sshd.client.config.hosts.KnownHostEntry;
|
||||
import org.apache.sshd.client.keyverifier.KnownHostsServerKeyVerifier;
|
||||
import org.apache.sshd.client.keyverifier.ServerKeyVerifier;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
|
||||
import org.apache.sshd.common.config.keys.KeyUtils;
|
||||
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
|
||||
import org.apache.sshd.common.util.GenericUtils;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ServerKeyVerifier} implementation for a {@link Resource} abstraction.
|
||||
* The logic is similar to the {@link KnownHostsServerKeyVerifier}, but in read-only mode.
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.0
|
||||
*
|
||||
* @see KnownHostsServerKeyVerifier
|
||||
*/
|
||||
public class ResourceKnownHostsServerKeyVerifier implements ServerKeyVerifier {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(ResourceKnownHostsServerKeyVerifier.class);
|
||||
|
||||
private final Supplier<Collection<KnownHostsServerKeyVerifier.HostEntryPair>> keysSupplier;
|
||||
|
||||
public ResourceKnownHostsServerKeyVerifier(Resource knownHostsResource) {
|
||||
Assert.notNull(knownHostsResource, "'knownHostsResource' must not be null");
|
||||
this.keysSupplier = GenericUtils.memoizeLock(getKnownHostSupplier(knownHostsResource));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyServerKey(ClientSession clientSession, SocketAddress remoteAddress, PublicKey serverKey) {
|
||||
Collection<KnownHostsServerKeyVerifier.HostEntryPair> knownHosts = this.keysSupplier.get();
|
||||
KnownHostsServerKeyVerifier.HostEntryPair match = findKnownHostEntry(clientSession, remoteAddress, knownHosts);
|
||||
if (match == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
KnownHostEntry entry = match.getHostEntry();
|
||||
PublicKey expected = match.getServerKey();
|
||||
if (KeyUtils.compareKeys(expected, serverKey)) {
|
||||
return !"revoked".equals(entry.getMarker());
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Supplier<Collection<KnownHostsServerKeyVerifier.HostEntryPair>> getKnownHostSupplier(
|
||||
Resource knownHostsResource) {
|
||||
|
||||
return () -> {
|
||||
try {
|
||||
Collection<KnownHostEntry> entries =
|
||||
KnownHostEntry.readKnownHostEntries(knownHostsResource.getInputStream(), true);
|
||||
List<KnownHostsServerKeyVerifier.HostEntryPair> keys = new ArrayList<>(entries.size());
|
||||
for (KnownHostEntry entry : entries) {
|
||||
keys.add(new KnownHostsServerKeyVerifier.HostEntryPair(entry, resolveHostKey(entry)));
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.warn("Known hosts cannot be loaded from the: " + knownHostsResource, ex);
|
||||
return Collections.emptyList();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static PublicKey resolveHostKey(KnownHostEntry entry) throws IOException, GeneralSecurityException {
|
||||
AuthorizedKeyEntry authEntry = entry.getKeyEntry();
|
||||
Assert.notNull(authEntry, () -> "No key extracted from " + entry);
|
||||
return authEntry.resolvePublicKey(null, PublicKeyEntryResolver.IGNORING);
|
||||
}
|
||||
|
||||
private static KnownHostsServerKeyVerifier.HostEntryPair findKnownHostEntry(
|
||||
ClientSession clientSession, SocketAddress remoteAddress,
|
||||
Collection<KnownHostsServerKeyVerifier.HostEntryPair> knownHosts) {
|
||||
|
||||
Collection<SshdSocketAddress> candidates = resolveHostNetworkIdentities(clientSession, remoteAddress);
|
||||
|
||||
if (GenericUtils.isEmpty(candidates)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (KnownHostsServerKeyVerifier.HostEntryPair match : knownHosts) {
|
||||
KnownHostEntry entry = match.getHostEntry();
|
||||
for (SshdSocketAddress host : candidates) {
|
||||
if (entry.isHostMatch(host.getHostName(), host.getPort())) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null; // no match found
|
||||
}
|
||||
|
||||
private static Collection<SshdSocketAddress> resolveHostNetworkIdentities(
|
||||
ClientSession clientSession, SocketAddress remoteAddress) {
|
||||
/*
|
||||
* NOTE !!! we do not resolve the fully-qualified name to avoid long DNS timeouts. Instead we use the reported
|
||||
* peer address and the original connection target host
|
||||
*/
|
||||
Collection<SshdSocketAddress> candidates = new TreeSet<>(SshdSocketAddress.BY_HOST_AND_PORT);
|
||||
candidates.add(SshdSocketAddress.toSshdSocketAddress(remoteAddress));
|
||||
SocketAddress connectAddress = clientSession.getConnectAddress();
|
||||
candidates.add(SshdSocketAddress.toSshdSocketAddress(connectAddress));
|
||||
return candidates;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,12 +16,14 @@
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpHelper;
|
||||
|
||||
import org.springframework.integration.file.remote.AbstractFileInfo;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* A {@link org.springframework.integration.file.remote.FileInfo} implementation for SFTP.
|
||||
*
|
||||
@@ -30,37 +32,37 @@ import com.jcraft.jsch.SftpATTRS;
|
||||
*
|
||||
* @since 2.1
|
||||
*/
|
||||
public class SftpFileInfo extends AbstractFileInfo<LsEntry> {
|
||||
public class SftpFileInfo extends AbstractFileInfo<SftpClient.DirEntry> {
|
||||
|
||||
private final LsEntry lsEntry;
|
||||
private final SftpClient.DirEntry lsEntry;
|
||||
|
||||
private final SftpATTRS attrs;
|
||||
private final SftpClient.Attributes attrs;
|
||||
|
||||
|
||||
public SftpFileInfo(LsEntry lsEntry) {
|
||||
public SftpFileInfo(SftpClient.DirEntry lsEntry) {
|
||||
Assert.notNull(lsEntry, "'lsEntry' must not be null");
|
||||
this.lsEntry = lsEntry;
|
||||
this.attrs = lsEntry.getAttrs();
|
||||
this.attrs = lsEntry.getAttributes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#isDir()
|
||||
* @see SftpClient.Attributes#isDirectory()
|
||||
*/
|
||||
@Override
|
||||
public boolean isDirectory() {
|
||||
return this.attrs.isDir();
|
||||
return this.attrs.isDirectory();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#isLink()
|
||||
* @see SftpClient.Attributes#isSymbolicLink()
|
||||
*/
|
||||
@Override
|
||||
public boolean isLink() {
|
||||
return this.attrs.isLink();
|
||||
return this.attrs.isSymbolicLink();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#getSize()
|
||||
* @see SftpClient.Attributes#getSize()
|
||||
*/
|
||||
@Override
|
||||
public long getSize() {
|
||||
@@ -68,15 +70,15 @@ public class SftpFileInfo extends AbstractFileInfo<LsEntry> {
|
||||
}
|
||||
|
||||
/**
|
||||
* @see SftpATTRS#getMTime()
|
||||
* @see SftpClient.Attributes#getModifyTime()
|
||||
*/
|
||||
@Override
|
||||
public long getModified() {
|
||||
return ((long) this.attrs.getMTime()) * 1000; // NOSONAR magic number
|
||||
return this.attrs.getModifyTime().toMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see LsEntry#getFilename()
|
||||
* @see SftpClient.DirEntry#getFilename()
|
||||
*/
|
||||
@Override
|
||||
public String getFilename() {
|
||||
@@ -85,11 +87,11 @@ public class SftpFileInfo extends AbstractFileInfo<LsEntry> {
|
||||
|
||||
@Override
|
||||
public String getPermissions() {
|
||||
return this.attrs.getPermissionsString();
|
||||
return PosixFilePermissions.toString(SftpHelper.permissionsToAttributes(this.attrs.getPermissions()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LsEntry getFileInfo() {
|
||||
public SftpClient.DirEntry getFileInfo() {
|
||||
return this.lsEntry;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2019 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,35 +16,36 @@
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
import org.springframework.integration.file.remote.ClientCallback;
|
||||
import org.springframework.integration.file.remote.RemoteFileTemplate;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* SFTP version of {@code RemoteFileTemplate} providing type-safe access to
|
||||
* the underlying ChannelSftp object.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @author Artme Bilan
|
||||
*
|
||||
* @since 4.1
|
||||
*
|
||||
*/
|
||||
public class SftpRemoteFileTemplate extends RemoteFileTemplate<LsEntry> {
|
||||
public class SftpRemoteFileTemplate extends RemoteFileTemplate<SftpClient.DirEntry> {
|
||||
|
||||
public SftpRemoteFileTemplate(SessionFactory<LsEntry> sessionFactory) {
|
||||
public SftpRemoteFileTemplate(SessionFactory<SftpClient.DirEntry> sessionFactory) {
|
||||
super(sessionFactory);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T, C> T executeWithClient(final ClientCallback<C, T> callback) {
|
||||
return doExecuteWithClient((ClientCallback<ChannelSftp, T>) callback);
|
||||
return doExecuteWithClient((ClientCallback<SftpClient, T>) callback);
|
||||
}
|
||||
|
||||
protected <T> T doExecuteWithClient(final ClientCallback<ChannelSftp, T> callback) {
|
||||
return execute(session -> callback.doWithClient((ChannelSftp) session.getClientInstance()));
|
||||
protected <T> T doExecuteWithClient(final ClientCallback<SftpClient, T> callback) {
|
||||
return execute(session -> callback.doWithClient((SftpClient) session.getClientInstance()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,22 +21,19 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.apache.sshd.sftp.SftpModuleProperties;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpConstants;
|
||||
import org.apache.sshd.sftp.common.SftpException;
|
||||
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
import org.springframework.util.PatternMatchUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Default SFTP {@link Session} implementation. Wraps a JSCH session instance.
|
||||
@@ -50,115 +47,66 @@ import com.jcraft.jsch.SftpException;
|
||||
*
|
||||
* @since 2.0
|
||||
*/
|
||||
public class SftpSession implements Session<LsEntry> {
|
||||
public class SftpSession implements Session<SftpClient.DirEntry> {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(SftpSession.class);
|
||||
private final SftpClient sftpClient;
|
||||
|
||||
private static final String SESSION_IS_NOT_CONNECTED = "session is not connected";
|
||||
|
||||
private static final Duration DEFAULT_CHANNEL_CONNECT_TIMEOUT = Duration.ofSeconds(5);
|
||||
|
||||
private final com.jcraft.jsch.Session jschSession;
|
||||
|
||||
private final JSchSessionWrapper wrapper;
|
||||
|
||||
private int channelConnectTimeout = (int) DEFAULT_CHANNEL_CONNECT_TIMEOUT.toMillis();
|
||||
|
||||
private volatile ChannelSftp channel;
|
||||
|
||||
private volatile boolean closed;
|
||||
|
||||
|
||||
public SftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
Assert.notNull(jschSession, "jschSession must not be null");
|
||||
this.jschSession = jschSession;
|
||||
this.wrapper = null;
|
||||
}
|
||||
|
||||
public SftpSession(JSchSessionWrapper wrapper) {
|
||||
Assert.notNull(wrapper, "wrapper must not be null");
|
||||
this.jschSession = wrapper.getSession();
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the connect timeout.
|
||||
* @param timeout the timeout to set.
|
||||
* @since 5.2
|
||||
*/
|
||||
public void setChannelConnectTimeout(Duration timeout) {
|
||||
Assert.notNull(timeout, "'timeout' cannot be null");
|
||||
this.channelConnectTimeout = (int) timeout.toMillis();
|
||||
public SftpSession(SftpClient sftpClient) {
|
||||
Assert.notNull(sftpClient, "'sftpClient' must not be null");
|
||||
this.sftpClient = sftpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(String path) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
this.channel.rm(path);
|
||||
return true;
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("Failed to remove file.", ex);
|
||||
}
|
||||
this.sftpClient.remove(path);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public LsEntry[] list(String path) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
Vector<?> lsEntries = this.channel.ls(path); // NOSONAR (Vector)
|
||||
if (lsEntries != null) {
|
||||
LsEntry[] entries = new LsEntry[lsEntries.size()];
|
||||
for (int i = 0; i < lsEntries.size(); i++) {
|
||||
Object next = lsEntries.get(i);
|
||||
Assert.state(next instanceof LsEntry, "expected only LsEntry instances from channel.ls()");
|
||||
entries[i] = (LsEntry) next;
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("Failed to list files", ex);
|
||||
}
|
||||
return new LsEntry[0];
|
||||
public SftpClient.DirEntry[] list(String path) throws IOException {
|
||||
return doList(path)
|
||||
.toArray(SftpClient.DirEntry[]::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] listNames(String path) throws IOException {
|
||||
LsEntry[] entries = this.list(path);
|
||||
List<String> names = new ArrayList<>();
|
||||
for (LsEntry entry : entries) {
|
||||
String fileName = entry.getFilename();
|
||||
SftpATTRS attrs = entry.getAttrs();
|
||||
if (!attrs.isDir() && !attrs.isLink()) {
|
||||
names.add(fileName);
|
||||
}
|
||||
}
|
||||
return names.toArray(new String[0]);
|
||||
return doList(path)
|
||||
.map(SftpClient.DirEntry::getFilename)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public Stream<SftpClient.DirEntry> doList(String path) throws IOException {
|
||||
String remotePath = StringUtils.trimTrailingCharacter(StringUtils.trimLeadingCharacter(path, '/'), '/');
|
||||
String remoteDir = remotePath;
|
||||
int lastIndex = remotePath.lastIndexOf('/');
|
||||
if (lastIndex > 0) {
|
||||
remoteDir = remoteDir.substring(0, lastIndex);
|
||||
}
|
||||
String remoteFile = lastIndex > 0 ? remotePath.substring(lastIndex + 1) : null;
|
||||
boolean isPattern = remoteFile != null && remoteFile.contains("*");
|
||||
|
||||
if (!isPattern && remoteFile != null) {
|
||||
SftpClient.Attributes attributes = this.sftpClient.lstat(path);
|
||||
if (!attributes.isDirectory()) {
|
||||
return Stream.of(new SftpClient.DirEntry(remoteFile, path, attributes));
|
||||
}
|
||||
else {
|
||||
remoteDir = remotePath;
|
||||
}
|
||||
}
|
||||
return StreamSupport.stream(this.sftpClient.readDir(remoteDir).spliterator(), false)
|
||||
.filter((entry) -> !isPattern || PatternMatchUtils.simpleMatch(remoteFile, entry.getFilename()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void read(String source, OutputStream os) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
InputStream is = this.channel.get(source);
|
||||
FileCopyUtils.copy(is, os);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to read file " + source, ex);
|
||||
}
|
||||
InputStream is = this.sftpClient.read(source);
|
||||
FileCopyUtils.copy(is, os);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream readRaw(String source) throws IOException {
|
||||
try {
|
||||
return this.channel.get(source);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to read file " + source, ex);
|
||||
}
|
||||
return this.sftpClient.read(source);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -168,149 +116,91 @@ public class SftpSession implements Session<LsEntry> {
|
||||
|
||||
@Override
|
||||
public void write(InputStream inputStream, String destination) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
this.channel.put(inputStream, destination);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to write file", ex);
|
||||
synchronized (this.sftpClient) {
|
||||
OutputStream outputStream = this.sftpClient.write(destination);
|
||||
FileCopyUtils.copy(inputStream, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void append(InputStream inputStream, String destination) throws IOException {
|
||||
Assert.state(this.channel != null, SESSION_IS_NOT_CONNECTED);
|
||||
try {
|
||||
this.channel.put(inputStream, destination, ChannelSftp.APPEND);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to write file", ex);
|
||||
synchronized (this.sftpClient) {
|
||||
OutputStream outputStream =
|
||||
this.sftpClient.write(destination, SftpClient.OpenMode.Create, SftpClient.OpenMode.Append);
|
||||
FileCopyUtils.copy(inputStream, outputStream);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
this.closed = true;
|
||||
if (this.wrapper != null) {
|
||||
if (this.channel != null) {
|
||||
this.channel.disconnect();
|
||||
}
|
||||
this.wrapper.close();
|
||||
try {
|
||||
this.sftpClient.close();
|
||||
}
|
||||
else {
|
||||
if (this.jschSession.isConnected()) {
|
||||
this.jschSession.disconnect();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new UncheckedIOException("failed to close an SFTP client", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOpen() {
|
||||
return !this.closed && this.jschSession.isConnected();
|
||||
return this.sftpClient.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rename(String pathFrom, String pathTo) throws IOException {
|
||||
try {
|
||||
this.channel.rename(pathFrom, pathTo);
|
||||
}
|
||||
catch (SftpException sftpex) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Initial File rename failed, possibly because file already exists. " +
|
||||
"Will attempt to delete file: " + pathTo + " and execute rename again.");
|
||||
}
|
||||
try {
|
||||
remove(pathTo);
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("Delete file: " + pathTo + " succeeded. Will attempt rename again");
|
||||
}
|
||||
}
|
||||
catch (IOException ioex) {
|
||||
IOException exception = new IOException("Failed to delete file " + pathTo, sftpex);
|
||||
exception.addSuppressed(ioex);
|
||||
throw exception;
|
||||
}
|
||||
try {
|
||||
// attempt to rename again
|
||||
this.channel.rename(pathFrom, pathTo);
|
||||
}
|
||||
catch (SftpException sftpex2) {
|
||||
IOException exception =
|
||||
new IOException("failed to rename from " + pathFrom + " to " + pathTo, sftpex);
|
||||
exception.addSuppressed(sftpex2);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug("File: " + pathFrom + " was successfully renamed to " + pathTo);
|
||||
}
|
||||
this.sftpClient.rename(pathFrom, pathTo, SftpClient.CopyMode.Overwrite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean mkdir(String remoteDirectory) throws IOException {
|
||||
try {
|
||||
this.channel.mkdir(remoteDirectory);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
if (ex.id != ChannelSftp.SSH_FX_FAILURE || !exists(remoteDirectory)) {
|
||||
throw new IOException("failed to create remote directory '" + remoteDirectory + "'.", ex);
|
||||
}
|
||||
}
|
||||
this.sftpClient.mkdir(remoteDirectory);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean rmdir(String remoteDirectory) throws IOException {
|
||||
try {
|
||||
this.channel.rmdir(remoteDirectory);
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
throw new IOException("failed to remove remote directory '" + remoteDirectory + "'.", ex);
|
||||
}
|
||||
this.sftpClient.rmdir(remoteDirectory);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean exists(String path) {
|
||||
try {
|
||||
this.channel.lstat(path);
|
||||
this.sftpClient.lstat(path);
|
||||
return true;
|
||||
}
|
||||
catch (SftpException ex) {
|
||||
if (ex.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
|
||||
catch (IOException ex) {
|
||||
if (ex instanceof SftpException sftpException &&
|
||||
SftpConstants.SSH_FX_NO_SUCH_FILE == sftpException.getStatus()) {
|
||||
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
throw new UncheckedIOException("Cannot check 'lstat' for path " + path,
|
||||
new IOException(ex));
|
||||
}
|
||||
throw new UncheckedIOException("Cannot check 'lstat' for path " + path, ex);
|
||||
}
|
||||
}
|
||||
|
||||
void connect() {
|
||||
try {
|
||||
if (!this.jschSession.isConnected()) {
|
||||
this.jschSession.connect();
|
||||
}
|
||||
this.channel = (ChannelSftp) this.jschSession.openChannel("sftp");
|
||||
if (this.channel != null && !this.channel.isConnected()) {
|
||||
this.channel.connect(this.channelConnectTimeout);
|
||||
if (!this.sftpClient.isOpen()) {
|
||||
Duration initializationTimeout =
|
||||
SftpModuleProperties.SFTP_CHANNEL_OPEN_TIMEOUT.getRequired(this.sftpClient.getSession());
|
||||
this.sftpClient.getClientChannel().open().verify(initializationTimeout);
|
||||
}
|
||||
}
|
||||
catch (JSchException e) {
|
||||
this.close();
|
||||
throw new IllegalStateException("failed to connect", e);
|
||||
catch (IOException ex) {
|
||||
close();
|
||||
throw new UncheckedIOException("failed to connect an SFTP client", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelSftp getClientInstance() {
|
||||
return this.channel;
|
||||
public SftpClient getClientInstance() {
|
||||
return this.sftpClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getHostPort() {
|
||||
return this.jschSession.getHost() + ':' + this.jschSession.getPort();
|
||||
return this.sftpClient.getSession().getConnectAddress().toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -320,10 +210,10 @@ public class SftpSession implements Session<LsEntry> {
|
||||
|
||||
private boolean doTest() {
|
||||
try {
|
||||
this.channel.lstat(this.channel.getHome());
|
||||
this.sftpClient.canonicalPath("");
|
||||
return true;
|
||||
}
|
||||
catch (@SuppressWarnings("unused") Exception e) {
|
||||
catch (@SuppressWarnings("unused") Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.support;
|
||||
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
|
||||
/**
|
||||
* Simple runtime exception to wrap an SftpException.
|
||||
*
|
||||
* @author Gary Russell
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class GeneralSftpException extends NestedRuntimeException {
|
||||
|
||||
public GeneralSftpException(String msg, Throwable cause) {
|
||||
super(msg, cause);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/**
|
||||
* Provides general support classes for sftp.
|
||||
*/
|
||||
package org.springframework.integration.sftp.support;
|
||||
@@ -1,30 +0,0 @@
|
||||
JSch 0.0.* was released under the GNU LGPL license. Later, we have switched
|
||||
over to a BSD-style license.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
Copyright (c) 2002,2003,2004,2005,2006,2007,2008 Atsuhiko Yamanaka, JCraft,Inc.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in
|
||||
the documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. The names of the authors may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
|
||||
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
|
||||
INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
|
||||
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2016-2021 the original author or authors.
|
||||
* Copyright 2016-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
@@ -32,8 +33,6 @@ import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.server.ApacheMinaSftpEventListener;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* Provides an embedded SFTP Server for test cases.
|
||||
*
|
||||
@@ -77,14 +76,16 @@ public class SftpTestSupport extends RemoteFileTestSupport {
|
||||
port = server.getPort();
|
||||
}
|
||||
|
||||
public static SessionFactory<LsEntry> sessionFactory() {
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(true);
|
||||
public static SessionFactory<SftpClient.DirEntry> sessionFactory() {
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(false);
|
||||
factory.setHost("localhost");
|
||||
factory.setPort(port);
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("foo");
|
||||
factory.setAllowUnknownKeys(true);
|
||||
return new CachingSessionFactory<>(factory);
|
||||
CachingSessionFactory<SftpClient.DirEntry> cachingSessionFactory = new CachingSessionFactory<>(factory);
|
||||
cachingSessionFactory.setTestSession(true);
|
||||
return cachingSessionFactory;
|
||||
}
|
||||
|
||||
public static ApacheMinaSftpEventListener eventListener() {
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
<beans:property name="password" value="hello"/>
|
||||
<beans:property name="port" value="2222"/>
|
||||
<beans:property name="user" value="oleg"/>
|
||||
<beans:property name="sessionConfig" ref="sessionConfig"/>
|
||||
</beans:bean>
|
||||
|
||||
<beans:bean id="csf" class="org.springframework.integration.file.remote.session.CachingSessionFactory">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,6 @@ package org.springframework.integration.sftp.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -38,22 +36,23 @@ import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
@DirtiesContext
|
||||
public class InboundChannelAdapterParserCachingTests {
|
||||
|
||||
@Autowired private Object cachingAdapter;
|
||||
@Autowired
|
||||
private Object cachingAdapter;
|
||||
|
||||
@Autowired private Object nonCachingAdapter;
|
||||
@Autowired
|
||||
private Object nonCachingAdapter;
|
||||
|
||||
@Test
|
||||
public void cachingAdapter() {
|
||||
Object sessionFactory = TestUtils.getPropertyValue(cachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
Object sessionFactory =
|
||||
TestUtils.getPropertyValue(cachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
assertThat(sessionFactory.getClass()).isEqualTo(CachingSessionFactory.class);
|
||||
Properties sessionConfig = TestUtils.getPropertyValue(sessionFactory, "sessionFactory.sessionConfig", Properties.class);
|
||||
assertThat(sessionConfig).isNotNull();
|
||||
assertThat(sessionConfig.getProperty("StrictHostKeyChecking")).isEqualTo("no");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonCachingAdapter() {
|
||||
Object sessionFactory = TestUtils.getPropertyValue(nonCachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
Object sessionFactory =
|
||||
TestUtils.getPropertyValue(nonCachingAdapter, "source.synchronizer.remoteFileTemplate.sessionFactory");
|
||||
assertThat(sessionFactory.getClass()).isEqualTo(DefaultSftpSessionFactory.class);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
|
||||
|
||||
|
||||
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<channel id="requestChannel"/>
|
||||
|
||||
<beans:bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.SimpleSftpSessionFactory">
|
||||
<beans:property name="host" value="localhost"/>
|
||||
<beans:property name="knownHostsResource" value="local, foo.com, bar.foo"/>
|
||||
<beans:property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftpTest"/>
|
||||
<beans:property name="privateKeyPassphrase" value="ghj"/>
|
||||
<beans:property name="password" value="hello"/>
|
||||
<beans:property name="port" value="2222"/>
|
||||
<beans:property name="user" value="oleg"/>
|
||||
</beans:bean>
|
||||
|
||||
<sftp:inbound-channel-adapter id="sftpAdapterNoAutoCreate"
|
||||
channel="requestChannel"
|
||||
session-factory="sftpSessionFactory"
|
||||
filter="filter"
|
||||
filename-pattern="."
|
||||
auto-startup="false"
|
||||
remote-directory="/foo"
|
||||
local-directory="file:local-test-dir"
|
||||
auto-create-local-directory="false"
|
||||
auto-delete-remote-files-on-sync="false">
|
||||
<poller fixed-rate="1000"/>
|
||||
</sftp:inbound-channel-adapter>
|
||||
|
||||
<beans:bean id="filter" class="org.springframework.integration.sftp.filters.SftpPatternMatchingFileListFilter">
|
||||
<beans:constructor-arg value="."/>
|
||||
</beans:bean>
|
||||
|
||||
</beans:beans>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -29,7 +29,6 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanCreationException;
|
||||
import org.springframework.beans.factory.BeanDefinitionStoreException;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.Lifecycle;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
@@ -126,16 +125,6 @@ public class InboundChannelAdapterParserTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
//exactly one of 'filename-pattern' or 'filter' is allowed on SFTP inbound adapter
|
||||
public void testFailWithFilePatternAndFilter() {
|
||||
assertThat(!new File("target/bar").exists()).isTrue();
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext("InboundChannelAdapterParserTests-context-fail.xml",
|
||||
getClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLocalDirAutoCreated() {
|
||||
assertThat(new File("foo").exists()).isFalse();
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans:beans
|
||||
xmlns="http://www.springframework.org/schema/integration"
|
||||
xmlns:beans="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<channel id="inboundFilesChannel"/>
|
||||
|
||||
<beans:bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<beans:property name="host" value="localhost"/>
|
||||
<beans:property name="knownHostsResource"
|
||||
value="#{ new org.springframework.core.io.ByteArrayResource('local, foo.com, bar.foo'.bytes)}"/>
|
||||
<beans:property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftpTest"/>
|
||||
<beans:property name="privateKeyPassphrase" value="ghj"/>
|
||||
<beans:property name="password" value="hello"/>
|
||||
<beans:property name="port" value="2222"/>
|
||||
<beans:property name="user" value="oleg"/>
|
||||
</beans:bean>
|
||||
|
||||
<sftp:inbound-channel-adapter id="sftpAdapter"
|
||||
session-factory="sftpSessionFactory"
|
||||
auto-startup="false"
|
||||
remote-directory="/hello"
|
||||
local-directory="file:local-test-dir"
|
||||
channel="inboundFilesChannel"
|
||||
filename-pattern=".*?jpg"
|
||||
auto-create-local-directory="true"
|
||||
delete-remote-files="true">
|
||||
<poller fixed-rate="1000" />
|
||||
</sftp:inbound-channel-adapter>
|
||||
|
||||
</beans:beans>
|
||||
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.SimpleSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="knownHostsResource" value="local, foo.com, bar.foo"/>
|
||||
<property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftpTest"/>
|
||||
<property name="privateKeyPassphrase" value="ghj"/>
|
||||
<property name="password" value="hello"/>
|
||||
<property name="port" value="2222"/>
|
||||
<property name="user" value="oleg"/>
|
||||
</bean>
|
||||
|
||||
<int:channel id="inputChannel"/>
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapterWithExpression"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="inputChannel"
|
||||
charset="UTF-8"
|
||||
remote-filename-generator="fileNameGenerator"
|
||||
remote-directory-expression="'foo' + '/' + 'bar'"
|
||||
remote-filename-generator-expression="payload.getName() + '-foo'"/>
|
||||
|
||||
|
||||
<bean id="fileNameGenerator" class="org.mockito.Mockito" factory-method="mock">
|
||||
<constructor-arg value="org.springframework.integration.file.FileNameGenerator"/>
|
||||
</bean>
|
||||
</beans>
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -157,15 +157,6 @@ public class OutboundChannelAdapterParserTests {
|
||||
.withMessageContaining("Only one of 'remote-directory'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailWithFileExpressionAndFileGenerator() {
|
||||
assertThatExceptionOfType(BeanDefinitionStoreException.class)
|
||||
.isThrownBy(() ->
|
||||
new ClassPathXmlApplicationContext(
|
||||
"OutboundChannelAdapterParserTests-context-fail-fileFileGen.xml",
|
||||
getClass()));
|
||||
}
|
||||
|
||||
public static class FooAdvice extends AbstractRequestHandlerAdvice {
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousy
|
||||
* @author Gunnar Hillert
|
||||
*
|
||||
*/
|
||||
public class SftpInboundOutboundSanitySample {
|
||||
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testInbound() throws Exception {
|
||||
File fileA = new File("local-test-dir/a.test");
|
||||
if (fileA.exists()) {
|
||||
fileA.delete();
|
||||
}
|
||||
File fileB = new File("local-test-dir/b.test");
|
||||
if (fileB.exists()) {
|
||||
fileB.delete();
|
||||
}
|
||||
fileA = new File("remote-target-dir/a.test-foo");
|
||||
if (fileA.exists()) {
|
||||
fileA.delete();
|
||||
}
|
||||
fileB = new File("remote-target-dir/b.test-foo");
|
||||
if (fileB.exists()) {
|
||||
fileB.delete();
|
||||
}
|
||||
|
||||
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
|
||||
"SftpInboundReceiveSample-ignored.xml", this.getClass());
|
||||
Thread.sleep(5000);
|
||||
fileA = new File("local-test-dir/a.test");
|
||||
fileB = new File("local-test-dir/b.test");
|
||||
assertThat(fileA.exists()).isTrue();
|
||||
assertThat(fileB.exists()).isTrue();
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testOutbound() throws Exception {
|
||||
ClassPathXmlApplicationContext ac =
|
||||
new ClassPathXmlApplicationContext("SftpOutboundTransferSample-ignored.xml", this.getClass());
|
||||
File fileA = new File("local-test-dir/a.test");
|
||||
File fileB = new File("local-test-dir/b.test");
|
||||
MessageChannel ftpChannel = ac.getBean("ftpChannel", MessageChannel.class);
|
||||
ftpChannel.send(new GenericMessage<File>(fileA));
|
||||
ftpChannel.send(new GenericMessage<File>(fileB));
|
||||
Thread.sleep(6000);
|
||||
fileA = new File("remote-target-dir/a.test-foo");
|
||||
fileB = new File("remote-target-dir/b.test-foo");
|
||||
assertThat(fileA.exists()).isTrue();
|
||||
assertThat(fileB.exists()).isTrue();
|
||||
ac.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xmlns:task="http://www.springframework.org/schema/task"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/task https://www.springframework.org/schema/task/spring-task.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftp_rsa"/>
|
||||
<property name="privateKeyPassphrase" value="springintegration"/>
|
||||
<property name="port" value="22"/>
|
||||
<property name="user" value="ozhurakousky"/>
|
||||
</bean>
|
||||
|
||||
<int-sftp:inbound-channel-adapter id="sftpInbondAdapter"
|
||||
channel="receiveChannel"
|
||||
session-factory="sftpSessionFactory"
|
||||
local-directory="file:local-test-dir"
|
||||
remote-directory="/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/remote-test-dir"
|
||||
auto-startup="true"
|
||||
temporary-file-suffix=".foo"
|
||||
delete-remote-files="false"
|
||||
filename-regex=".*\.test$">
|
||||
<int:poller fixed-rate="3000" max-messages-per-poll="1" />
|
||||
</int-sftp:inbound-channel-adapter>
|
||||
|
||||
<int:channel id="receiveChannel">
|
||||
<int:queue/>
|
||||
</int:channel>
|
||||
|
||||
<task:executor id="executor" pool-size="15"/>
|
||||
|
||||
</beans>
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.config;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.endpoint.SourcePollingChannelAdapter;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gunnar Hillert
|
||||
* @author Gary Russell
|
||||
*/
|
||||
public class SftpMessageHistoryTests {
|
||||
|
||||
@Test
|
||||
public void testMessageHistoryCompliance() {
|
||||
ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("MessageHistory-context.xml",
|
||||
SftpMessageHistoryTests.class);
|
||||
SourcePollingChannelAdapter spca = ac.getBean("sftpAdapter", SourcePollingChannelAdapter.class);
|
||||
assertThat(spca.getComponentName()).isEqualTo("sftpAdapter");
|
||||
assertThat(spca.getComponentType()).isEqualTo("sftp:inbound-channel-adapter");
|
||||
ac.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:int="http://www.springframework.org/schema/integration"
|
||||
xmlns:int-sftp="http://www.springframework.org/schema/integration/sftp"
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/integration https://www.springframework.org/schema/integration/spring-integration.xsd
|
||||
http://www.springframework.org/schema/integration/sftp https://www.springframework.org/schema/integration/sftp/spring-integration-sftp.xsd">
|
||||
|
||||
<bean id="sftpSessionFactory" class="org.springframework.integration.sftp.session.DefaultSftpSessionFactory">
|
||||
<property name="host" value="localhost"/>
|
||||
<!-- <property name="privateKey" value="classpath:org/springframework/integration/sftp/config/sftp_rsa"/> -->
|
||||
<!-- <property name="privateKeyPassphrase" value="springintegration"/> -->
|
||||
<property name="port" value="22"/>
|
||||
<property name="user" value="ozhurakousky"/>
|
||||
<property name="password" value="password"/>
|
||||
</bean>
|
||||
|
||||
<int:channel id="ftpChannel"/>
|
||||
|
||||
<int-sftp:outbound-channel-adapter id="sftpOutboundAdapter"
|
||||
session-factory="sftpSessionFactory"
|
||||
channel="ftpChannel"
|
||||
charset="UTF-8"
|
||||
temporary-file-suffix=".foo"
|
||||
remote-filename-generator-expression="payload.getName() + '-foo'"
|
||||
auto-create-directory="true"
|
||||
remote-directory="spring-integration-sftp/remote-target-dir/bar/baz"/>
|
||||
|
||||
</beans>
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.config;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.integration.support.MessageBuilder;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
*
|
||||
*/
|
||||
public class SftpOutboundTransferSample {
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testOutbound() throws Exception {
|
||||
ClassPathXmlApplicationContext ac =
|
||||
new ClassPathXmlApplicationContext("SftpOutboundTransferSample-ignored.xml", SftpOutboundTransferSample.class);
|
||||
ac.start();
|
||||
File file = new File("/Users/ozhurakousky/workspace-sts-2.3.3.M2/si/spring-integration/spring-integration-sftp/local-test-dir/foo.txt");
|
||||
if (file.exists()) {
|
||||
Message<File> message = MessageBuilder.withPayload(file).build();
|
||||
MessageChannel inputChannel = ac.getBean("inputChannel", MessageChannel.class);
|
||||
inputChannel.send(message);
|
||||
Thread.sleep(2000);
|
||||
}
|
||||
ac.close();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,10 +20,14 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpHelper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.DisabledOnOs;
|
||||
import org.junit.jupiter.api.condition.OS;
|
||||
@@ -50,8 +54,6 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
@@ -68,7 +70,6 @@ public class SftpTests extends SftpTestSupport {
|
||||
private IntegrationFlowContext flowContext;
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testSftpInboundFlow() {
|
||||
QueueChannel out = new QueueChannel();
|
||||
IntegrationFlow flow = IntegrationFlow
|
||||
@@ -78,7 +79,7 @@ public class SftpTests extends SftpTestSupport {
|
||||
.regexFilter(".*\\.txt$")
|
||||
.localFilenameExpression("#this.toUpperCase() + '.a'")
|
||||
.localDirectory(getTargetLocalDirectory())
|
||||
.remoteComparator(Comparator.naturalOrder()),
|
||||
.remoteComparator(Comparator.comparing(SftpClient.DirEntry::getFilename)),
|
||||
e -> e.id("sftpInboundAdapter").poller(Pollers.fixedDelay(100)))
|
||||
.channel(out)
|
||||
.get();
|
||||
@@ -122,7 +123,7 @@ public class SftpTests extends SftpTestSupport {
|
||||
assertThat(message).isNotNull();
|
||||
assertThat(message.getPayload()).isInstanceOf(InputStream.class);
|
||||
assertThat(message.getHeaders().get(FileHeaders.REMOTE_FILE)).isIn(" sftpSource1.txt", "sftpSource2.txt");
|
||||
assertThat(message.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost:");
|
||||
assertThat(message.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost");
|
||||
((InputStream) message.getPayload()).close();
|
||||
new IntegrationMessageHeaderAccessor(message).getCloseableResource().close();
|
||||
|
||||
@@ -141,11 +142,11 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
RemoteFileTemplate<ChannelSftp.LsEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
ChannelSftp.LsEntry[] files = template.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
RemoteFileTemplate<SftpClient.DirEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
SftpClient.DirEntry[] files =
|
||||
template.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(3);
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(3);
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
@@ -163,10 +164,10 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
ChannelSftp.LsEntry[] files = sftpTemplate.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
SftpClient.DirEntry[] files =
|
||||
sftpTemplate.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(3);
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(3);
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
@@ -187,10 +188,10 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
ChannelSftp.LsEntry[] files = sftpTemplate.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
SftpClient.DirEntry[] files =
|
||||
sftpTemplate.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(6);
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(6);
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
@@ -210,16 +211,17 @@ public class SftpTests extends SftpTestSupport {
|
||||
.setHeader(FileHeaders.FILENAME, fileName)
|
||||
.build());
|
||||
|
||||
RemoteFileTemplate<ChannelSftp.LsEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
ChannelSftp.LsEntry[] files = template.execute(session ->
|
||||
session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
RemoteFileTemplate<SftpClient.DirEntry> template = new RemoteFileTemplate<>(sessionFactory());
|
||||
SftpClient.DirEntry[] files =
|
||||
template.execute(session -> session.list(getTargetRemoteDirectory().getName() + "/" + fileName));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(3);
|
||||
String[] permissions = files[0].getAttrs().getPermissionsString().substring(1).replaceAll("--", "-").split("-");
|
||||
assertThat(permissions[0]).isEqualTo("rw");
|
||||
assertThat(permissions[1]).isEqualTo("r");
|
||||
assertThat(permissions[2]).isEqualTo("r");
|
||||
|
||||
assertThat(files[0].getAttributes().getSize()).isEqualTo(3);
|
||||
int permissionFlags = files[0].getAttributes().getPermissions();
|
||||
Set<PosixFilePermission> posixFilePermissions = SftpHelper.permissionsToAttributes(permissionFlags);
|
||||
assertThat(posixFilePermissions)
|
||||
.contains(PosixFilePermission.OWNER_READ, PosixFilePermission.OWNER_WRITE,
|
||||
PosixFilePermission.GROUP_READ, PosixFilePermission.OTHERS_READ)
|
||||
.doesNotContain(PosixFilePermission.GROUP_WRITE, PosixFilePermission.OTHERS_WRITE);
|
||||
registration.destroy();
|
||||
}
|
||||
|
||||
@@ -264,9 +266,9 @@ public class SftpTests extends SftpTestSupport {
|
||||
Message<?> receive = out.receive(10_000);
|
||||
assertThat(receive).isNotNull();
|
||||
Object payload = receive.getPayload();
|
||||
assertThat(payload).isInstanceOf(ChannelSftp.LsEntry[].class);
|
||||
assertThat(payload).isInstanceOf(SftpClient.DirEntry[].class);
|
||||
|
||||
assertThat(((ChannelSftp.LsEntry[]) payload).length > 0).isTrue();
|
||||
assertThat(((SftpClient.DirEntry[]) payload).length > 0).isTrue();
|
||||
|
||||
registration.destroy();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2017-2020 the original author or authors.
|
||||
* Copyright 2017-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,6 +21,7 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -32,10 +33,10 @@ import org.springframework.integration.sftp.session.SftpRemoteFileTemplate;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 5.0
|
||||
*
|
||||
*/
|
||||
@@ -50,9 +51,9 @@ public class SftpFileListFilterTests extends SftpTestSupport {
|
||||
public void testMarkerFile() throws Exception {
|
||||
SftpSystemMarkerFilePresentFileListFilter filter = new SftpSystemMarkerFilePresentFileListFilter(
|
||||
new SftpSimplePatternFileListFilter("*.txt"));
|
||||
LsEntry[] files = template.list("sftpSource");
|
||||
SftpClient.DirEntry[] files = template.list("sftpSource");
|
||||
assertThat(files.length).isGreaterThan(0);
|
||||
List<LsEntry> filtered = filter.filterFiles(files);
|
||||
List<SftpClient.DirEntry> filtered = filter.filterFiles(files);
|
||||
assertThat(filtered.size()).isEqualTo(0);
|
||||
File remoteDir = getSourceRemoteDirectory();
|
||||
File marker = new File(remoteDir, "sftpSource2.txt.complete");
|
||||
@@ -68,12 +69,12 @@ public class SftpFileListFilterTests extends SftpTestSupport {
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
|
||||
return SftpFileListFilterTests.sessionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SftpRemoteFileTemplate remoteFileTempalte() {
|
||||
public SftpRemoteFileTemplate remoteFileTemplate() {
|
||||
return new SftpRemoteFileTemplate(sftpSessionFactory());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2020 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,23 +17,22 @@
|
||||
package org.springframework.integration.sftp.filters;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.time.Instant;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.metadata.SimpleMetadataStore;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author David Liu
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.0.4
|
||||
*
|
||||
*/
|
||||
@@ -43,18 +42,15 @@ public class SftpPersistentAcceptOnceFileListFilterTests {
|
||||
public void testRollback() throws Exception {
|
||||
SftpPersistentAcceptOnceFileListFilter filter = new SftpPersistentAcceptOnceFileListFilter(
|
||||
new SimpleMetadataStore(), "rollback:");
|
||||
ChannelSftp channel = new ChannelSftp();
|
||||
SftpATTRS attrs = mock(SftpATTRS.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Constructor<LsEntry> ctor = (Constructor<LsEntry>) LsEntry.class.getDeclaredConstructors()[0];
|
||||
ctor.setAccessible(true);
|
||||
LsEntry sftpFile1 = ctor.newInstance(channel, "foo", "foo", attrs);
|
||||
LsEntry sftpFile2 = ctor.newInstance(channel, "bar", "bar", attrs);
|
||||
LsEntry ftpFile3 = ctor.newInstance(channel, "baz", "baz", attrs);
|
||||
LsEntry[] files = new LsEntry[] {sftpFile1, sftpFile2, ftpFile3};
|
||||
List<LsEntry> passed = filter.filterFiles(files);
|
||||
SftpClient.Attributes attrs = new SftpClient.Attributes();
|
||||
attrs.setModifyTime(FileTime.from(Instant.now()));
|
||||
SftpClient.DirEntry sftpFile1 = new SftpClient.DirEntry("foo", "foo", attrs);
|
||||
SftpClient.DirEntry sftpFile2 = new SftpClient.DirEntry("bar", "bar", attrs);
|
||||
SftpClient.DirEntry sftpFile3 = new SftpClient.DirEntry("baz", "baz", attrs);
|
||||
SftpClient.DirEntry[] files = new SftpClient.DirEntry[]{ sftpFile1, sftpFile2, sftpFile3 };
|
||||
List<SftpClient.DirEntry> passed = filter.filterFiles(files);
|
||||
assertThat(Arrays.equals(files, passed.toArray())).isTrue();
|
||||
List<LsEntry> now = filter.filterFiles(files);
|
||||
List<SftpClient.DirEntry> now = filter.filterFiles(files);
|
||||
assertThat(now.size()).isEqualTo(0);
|
||||
filter.rollback(passed.get(1), passed);
|
||||
now = filter.filterFiles(files);
|
||||
@@ -70,15 +66,12 @@ public class SftpPersistentAcceptOnceFileListFilterTests {
|
||||
public void testKeyUsingFileName() throws Exception {
|
||||
SftpPersistentAcceptOnceFileListFilter filter = new SftpPersistentAcceptOnceFileListFilter(
|
||||
new SimpleMetadataStore(), "rollback:");
|
||||
ChannelSftp channel = new ChannelSftp();
|
||||
SftpATTRS attrs = mock(SftpATTRS.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Constructor<LsEntry> ctor = (Constructor<LsEntry>) LsEntry.class.getDeclaredConstructors()[0];
|
||||
ctor.setAccessible(true);
|
||||
LsEntry sftpFile1 = ctor.newInstance(channel, "foo", "same", attrs);
|
||||
LsEntry sftpFile2 = ctor.newInstance(channel, "bar", "same", attrs);
|
||||
LsEntry[] files = new LsEntry[] {sftpFile1, sftpFile2};
|
||||
List<LsEntry> now = filter.filterFiles(files);
|
||||
SftpClient.Attributes attrs = new SftpClient.Attributes();
|
||||
attrs.setModifyTime(FileTime.from(Instant.now()));
|
||||
SftpClient.DirEntry sftpFile1 = new SftpClient.DirEntry("foo", "same", attrs);
|
||||
SftpClient.DirEntry sftpFile2 = new SftpClient.DirEntry("bar", "same", attrs);
|
||||
SftpClient.DirEntry[] files = new SftpClient.DirEntry[]{ sftpFile1, sftpFile2 };
|
||||
List<SftpClient.DirEntry> now = filter.filterFiles(files);
|
||||
assertThat(now.size()).isEqualTo(2);
|
||||
assertThat(now.get(0).getFilename()).isEqualTo("foo");
|
||||
assertThat(now.get(1).getFilename()).isEqualTo("bar");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2015-2020 the original author or authors.
|
||||
* Copyright 2015-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -34,11 +35,10 @@ import org.springframework.integration.sftp.SftpTestSupport;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 4.1.7
|
||||
*
|
||||
*/
|
||||
@@ -85,12 +85,13 @@ public class RollbackLocalFilterTests extends SftpTestSupport {
|
||||
this.file = in;
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory() {
|
||||
return RollbackLocalFilterTests.sessionFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,7 @@
|
||||
package org.springframework.integration.sftp.inbound;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -27,13 +27,15 @@ import static org.mockito.Mockito.when;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.net.URI;
|
||||
import java.nio.file.attribute.FileTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -53,10 +55,6 @@ import org.springframework.integration.sftp.session.SftpTestSessionFactory;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
import org.springframework.messaging.Message;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gunnar Hillert
|
||||
@@ -68,14 +66,9 @@ import com.jcraft.jsch.SftpATTRS;
|
||||
*/
|
||||
public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
|
||||
private static final com.jcraft.jsch.Session jschSession = mock(com.jcraft.jsch.Session.class);
|
||||
|
||||
@BeforeEach
|
||||
@AfterEach
|
||||
public void cleanup() {
|
||||
willReturn("::1")
|
||||
.given(jschSession)
|
||||
.getHost();
|
||||
File file = new File("test");
|
||||
if (file.exists()) {
|
||||
String[] files = file.list();
|
||||
@@ -104,12 +97,11 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
PropertiesPersistingMetadataStore store = spy(new PropertiesPersistingMetadataStore());
|
||||
store.setBaseDirectory("test");
|
||||
store.afterPropertiesSet();
|
||||
SftpPersistentAcceptOnceFileListFilter persistFilter =
|
||||
new SftpPersistentAcceptOnceFileListFilter(store, "foo");
|
||||
List<FileListFilter<LsEntry>> filters = new ArrayList<>();
|
||||
SftpPersistentAcceptOnceFileListFilter persistFilter = new SftpPersistentAcceptOnceFileListFilter(store, "foo");
|
||||
List<FileListFilter<SftpClient.DirEntry>> filters = new ArrayList<>();
|
||||
filters.add(persistFilter);
|
||||
filters.add(patternFilter);
|
||||
CompositeFileListFilter<LsEntry> filter = new CompositeFileListFilter<>(filters);
|
||||
CompositeFileListFilter<SftpClient.DirEntry> filter = new CompositeFileListFilter<>(filters);
|
||||
synchronizer.setFilter(filter);
|
||||
synchronizer.setBeanFactory(mock(BeanFactory.class));
|
||||
synchronizer.afterPropertiesSet();
|
||||
@@ -140,7 +132,7 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
TestUtils.getPropertyValue(synchronizer, "remoteFileMetadataStore.metadata", Map.class);
|
||||
|
||||
String next = remoteFileMetadataStore.values().iterator().next();
|
||||
assertThat(URI.create(next).getHost()).isEqualTo("[::1]");
|
||||
assertThat(URI.create(next).getHost()).isEqualTo("mock.sftp.host");
|
||||
|
||||
Message<File> btestFile = ms.receive();
|
||||
assertThat(btestFile).isNotNull();
|
||||
@@ -172,43 +164,40 @@ public class SftpInboundRemoteFileSystemSynchronizerTests {
|
||||
|
||||
public static class TestSftpSessionFactory extends DefaultSftpSessionFactory {
|
||||
|
||||
private final Vector<LsEntry> sftpEntries = new Vector<>();
|
||||
private List<SftpClient.DirEntry> sftpEntries;
|
||||
|
||||
private void init() {
|
||||
String[] files = new File("remote-test-dir").list();
|
||||
for (String fileName : files) {
|
||||
LsEntry lsEntry = mock(LsEntry.class);
|
||||
SftpATTRS attributes = mock(SftpATTRS.class);
|
||||
when(lsEntry.getAttrs()).thenReturn(attributes);
|
||||
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
when(lsEntry.getAttrs().getMTime())
|
||||
.thenReturn(Long.valueOf(calendar.getTimeInMillis() / 1000).intValue());
|
||||
when(lsEntry.getFilename()).thenReturn(fileName);
|
||||
when(lsEntry.getLongname()).thenReturn(fileName);
|
||||
sftpEntries.add(lsEntry);
|
||||
}
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DATE, 1);
|
||||
this.sftpEntries =
|
||||
Arrays.stream(files)
|
||||
.map((file) -> {
|
||||
SftpClient.Attributes attributes = spy(new SftpClient.Attributes());
|
||||
attributes.setModifyTime(FileTime.fromMillis(calendar.getTimeInMillis()));
|
||||
given(attributes.isRegularFile()).willReturn(true);
|
||||
return new SftpClient.DirEntry(file, file, attributes);
|
||||
})
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SftpSession getSession() {
|
||||
if (this.sftpEntries.size() == 0) {
|
||||
this.init();
|
||||
if (this.sftpEntries == null) {
|
||||
init();
|
||||
}
|
||||
|
||||
try {
|
||||
ChannelSftp channel = mock(ChannelSftp.class);
|
||||
SftpClient sftpClient = mock(SftpClient.class);
|
||||
|
||||
String[] files = new File("remote-test-dir").list();
|
||||
for (String fileName : files) {
|
||||
when(channel.get("remote-test-dir/" + fileName))
|
||||
when(sftpClient.read("remote-test-dir/" + fileName))
|
||||
.thenReturn(new FileInputStream("remote-test-dir/" + fileName));
|
||||
}
|
||||
when(channel.ls("remote-test-dir")).thenReturn(sftpEntries);
|
||||
when(sftpClient.readDir("remote-test-dir")).thenReturn(this.sftpEntries);
|
||||
|
||||
when(jschSession.openChannel("sftp")).thenReturn(channel);
|
||||
return SftpTestSessionFactory.createSftpSession(jschSession);
|
||||
return SftpTestSessionFactory.createSftpSession(sftpClient);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create mock sftp session", e);
|
||||
|
||||
@@ -25,6 +25,7 @@ import java.util.Comparator;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -53,8 +54,6 @@ import org.springframework.scheduling.support.PeriodicTrigger;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
@@ -120,7 +119,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
received = (Message<byte[]>) this.data.receive(10000);
|
||||
assertThat(received).isNotNull();
|
||||
assertThat(received.getHeaders().get(FileHeaders.REMOTE_FILE_INFO)).isInstanceOf(SftpFileInfo.class);
|
||||
assertThat(received.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost:");
|
||||
assertThat(received.getHeaders().get(FileHeaders.REMOTE_HOST_PORT, String.class)).contains("localhost");
|
||||
this.adapter.stop();
|
||||
}
|
||||
|
||||
@@ -172,7 +171,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
private SftpStreamingMessageSource buildSource() {
|
||||
SftpStreamingMessageSource messageSource =
|
||||
new SftpStreamingMessageSource(this.config.template(),
|
||||
Comparator.comparing(LsEntry::getFilename));
|
||||
Comparator.comparing(SftpClient.DirEntry::getFilename));
|
||||
messageSource.setRemoteDirectory("sftpSource/");
|
||||
messageSource.setBeanFactory(this.context);
|
||||
return messageSource;
|
||||
@@ -205,7 +204,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
@InboundChannelAdapter(channel = "stream", autoStartup = "false")
|
||||
public MessageSource<InputStream> sftpMessageSource() {
|
||||
SftpStreamingMessageSource messageSource = new SftpStreamingMessageSource(template(),
|
||||
Comparator.comparing(LsEntry::getFilename));
|
||||
Comparator.comparing(SftpClient.DirEntry::getFilename));
|
||||
messageSource.setFilter(
|
||||
new SftpPersistentAcceptOnceFileListFilter(
|
||||
new SimpleMetadataStore(metadataMap()), "testStreaming"));
|
||||
@@ -225,7 +224,7 @@ public class SftpStreamingMessageSourceTests extends SftpTestSupport {
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> ftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> ftpSessionFactory() {
|
||||
return SftpStreamingMessageSourceTests.sessionFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,41 +22,42 @@ import static org.mockito.AdditionalMatchers.and;
|
||||
import static org.mockito.AdditionalMatchers.not;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.willAnswer;
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.BDDMockito.willThrow;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.apache.sshd.common.SshConstants;
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.common.SftpConstants;
|
||||
import org.apache.sshd.sftp.common.SftpException;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.beans.DirectFieldAccessor;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.expression.common.LiteralExpression;
|
||||
import org.springframework.integration.file.DefaultFileNameGenerator;
|
||||
import org.springframework.integration.file.remote.FileInfo;
|
||||
import org.springframework.integration.file.remote.handler.FileTransferringMessageHandler;
|
||||
import org.springframework.integration.file.remote.session.CachingSessionFactory;
|
||||
import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.integration.file.remote.session.SessionFactory;
|
||||
import org.springframework.integration.sftp.session.DefaultSftpSessionFactory;
|
||||
@@ -70,13 +71,6 @@ import org.springframework.messaging.PollableChannel;
|
||||
import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.JSch;
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
@@ -85,19 +79,18 @@ import com.jcraft.jsch.SftpException;
|
||||
*/
|
||||
public class SftpOutboundTests {
|
||||
|
||||
private static final com.jcraft.jsch.Session jschSession = mock(com.jcraft.jsch.Session.class);
|
||||
|
||||
@Test
|
||||
public void testHandleFileMessage() throws Exception {
|
||||
File targetDir = new File("remote-target-dir");
|
||||
assertThat(targetDir.exists()).as("target directory does not exist: " + targetDir.getName()).isTrue();
|
||||
|
||||
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression(targetDir.getName()));
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setBeanFactory(mock(BeanFactory.class));
|
||||
fGenerator.setExpression("payload + '.test'");
|
||||
fGenerator.setExpression("payload.name + '.test'");
|
||||
handler.setFileNameGenerator(fGenerator);
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.afterPropertiesSet();
|
||||
@@ -118,8 +111,9 @@ public class SftpOutboundTests {
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setBeanFactory(mock(BeanFactory.class));
|
||||
fGenerator.setExpression("'foo.txt'");
|
||||
@@ -128,7 +122,7 @@ public class SftpOutboundTests {
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
handler.afterPropertiesSet();
|
||||
|
||||
handler.handleMessage(new GenericMessage<String>("String data"));
|
||||
handler.handleMessage(new GenericMessage<>("String data"));
|
||||
assertThat(new File("remote-target-dir", "foo.txt").exists()).isTrue();
|
||||
byte[] inFile = FileCopyUtils.copyToByteArray(file);
|
||||
assertThat(new String(inFile)).isEqualTo("String data");
|
||||
@@ -141,8 +135,9 @@ public class SftpOutboundTests {
|
||||
if (file.exists()) {
|
||||
file.delete();
|
||||
}
|
||||
SessionFactory<LsEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = new TestSftpSessionFactory();
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
DefaultFileNameGenerator fGenerator = new DefaultFileNameGenerator();
|
||||
fGenerator.setBeanFactory(mock(BeanFactory.class));
|
||||
fGenerator.setExpression("'foo.txt'");
|
||||
@@ -174,7 +169,7 @@ public class SftpOutboundTests {
|
||||
|
||||
MessageChannel channel = context.getBean("outboundChannelAdapterInsideChain", MessageChannel.class);
|
||||
|
||||
channel.send(new GenericMessage<File>(srcFile));
|
||||
channel.send(new GenericMessage<>(srcFile));
|
||||
assertThat(destFile.exists()).as("destination file was not created").isTrue();
|
||||
context.close();
|
||||
}
|
||||
@@ -203,15 +198,15 @@ public class SftpOutboundTests {
|
||||
context.close();
|
||||
}
|
||||
|
||||
@Test //INT-2954
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
public void testMkDir() throws Exception {
|
||||
@SuppressWarnings("unchecked")
|
||||
Session<LsEntry> session = mock(Session.class);
|
||||
Session<SftpClient.DirEntry> session = mock(Session.class);
|
||||
when(session.exists(anyString())).thenReturn(Boolean.FALSE);
|
||||
@SuppressWarnings("unchecked")
|
||||
SessionFactory<LsEntry> sessionFactory = mock(SessionFactory.class);
|
||||
SessionFactory<SftpClient.DirEntry> sessionFactory = mock(SessionFactory.class);
|
||||
when(sessionFactory.getSession()).thenReturn(session);
|
||||
FileTransferringMessageHandler<LsEntry> handler = new FileTransferringMessageHandler<>(sessionFactory);
|
||||
FileTransferringMessageHandler<SftpClient.DirEntry> handler =
|
||||
new FileTransferringMessageHandler<>(sessionFactory);
|
||||
handler.setAutoCreateDirectory(true);
|
||||
handler.setRemoteDirectoryExpression(new LiteralExpression("/foo/bar/baz"));
|
||||
handler.setBeanFactory(mock(BeanFactory.class));
|
||||
@@ -228,186 +223,56 @@ public class SftpOutboundTests {
|
||||
assertThat(madeDirs.get(2)).isEqualTo("/foo/bar/baz");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSharedSession() throws Exception {
|
||||
JSch jsch = spy(new JSch());
|
||||
Constructor<com.jcraft.jsch.Session> ctor = com.jcraft.jsch.Session.class.getDeclaredConstructor(JSch.class,
|
||||
String.class, String.class, int.class);
|
||||
ctor.setAccessible(true);
|
||||
com.jcraft.jsch.Session jschSession1 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
com.jcraft.jsch.Session jschSession2 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
@ParameterizedTest
|
||||
@ValueSource(booleans = { true, false })
|
||||
public void testSharedSession(boolean sharedSession) throws IOException {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPasswordAuthenticator((arg0, arg1, arg2) -> true);
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
server.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||
final String pathname = System.getProperty("java.io.tmpdir") + File.separator + "sftptest" + File.separator;
|
||||
new File(pathname).mkdirs();
|
||||
server.setFileSystemFactory(new VirtualFileSystemFactory(Paths.get(pathname)));
|
||||
server.start();
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession1).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession1)
|
||||
.connect();
|
||||
DefaultSftpSessionFactory f = new DefaultSftpSessionFactory(sharedSession);
|
||||
f.setHost("localhost");
|
||||
f.setPort(server.getPort());
|
||||
f.setUser("user");
|
||||
f.setPassword("pass");
|
||||
f.setAllowUnknownKeys(true);
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession2).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession2)
|
||||
.connect();
|
||||
|
||||
when(jsch.getSession("foo", "host", 22)).thenReturn(jschSession1, jschSession2);
|
||||
final ChannelSftp channel1 = spy(new ChannelSftp());
|
||||
doReturn("channel1").when(channel1).toString();
|
||||
final ChannelSftp channel2 = spy(new ChannelSftp());
|
||||
doReturn("channel2").when(channel2).toString();
|
||||
new DirectFieldAccessor(channel1).setPropertyValue("session", jschSession1);
|
||||
new DirectFieldAccessor(channel2).setPropertyValue("session", jschSession1);
|
||||
// Can't use when(session.open()) with a spy
|
||||
final AtomicInteger n = new AtomicInteger();
|
||||
doAnswer(invocation -> n.getAndIncrement() == 0 ? channel1 : channel2).when(jschSession1).openChannel("sftp");
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(jsch, true);
|
||||
factory.setHost("host");
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("bar");
|
||||
noopConnect(channel1);
|
||||
noopConnect(channel2);
|
||||
Session<LsEntry> s1 = factory.getSession();
|
||||
Session<LsEntry> s2 = factory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "jschSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "jschSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s1, "channel")).isSameAs(channel1);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "channel")).isSameAs(channel2);
|
||||
Session<SftpClient.DirEntry> s1 = f.getSession();
|
||||
Session<SftpClient.DirEntry> s2 = f.getSession();
|
||||
if (sharedSession) {
|
||||
assertThat(TestUtils.getPropertyValue(s2, "sftpClient"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "sftpClient"));
|
||||
}
|
||||
else {
|
||||
assertThat(TestUtils.getPropertyValue(s2, "sftpClient"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s1, "sftpClient"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotSharedSession() throws Exception {
|
||||
JSch jsch = spy(new JSch());
|
||||
Constructor<com.jcraft.jsch.Session> ctor =
|
||||
com.jcraft.jsch.Session.class.getDeclaredConstructor(JSch.class, String.class, String.class,
|
||||
int.class);
|
||||
ctor.setAccessible(true);
|
||||
com.jcraft.jsch.Session jschSession1 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
com.jcraft.jsch.Session jschSession2 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
new DirectFieldAccessor(jschSession1).setPropertyValue("isConnected", true);
|
||||
new DirectFieldAccessor(jschSession2).setPropertyValue("isConnected", true);
|
||||
when(jsch.getSession("foo", "host", 22)).thenReturn(jschSession1, jschSession2);
|
||||
ChannelSftp channel1 = spy(new ChannelSftp());
|
||||
ChannelSftp channel2 = spy(new ChannelSftp());
|
||||
new DirectFieldAccessor(channel1).setPropertyValue("session", jschSession1);
|
||||
new DirectFieldAccessor(channel2).setPropertyValue("session", jschSession1);
|
||||
doReturn(channel1).when(jschSession1).openChannel("sftp");
|
||||
doReturn(channel2).when(jschSession2).openChannel("sftp");
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(jsch, false);
|
||||
factory.setHost("host");
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("bar");
|
||||
noopConnect(channel1);
|
||||
noopConnect(channel2);
|
||||
Session<LsEntry> s1 = factory.getSession();
|
||||
Session<LsEntry> s2 = factory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "jschSession"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s1, "jschSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s1, "channel")).isSameAs(channel1);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "channel")).isSameAs(channel2);
|
||||
}
|
||||
public void testExists() throws IOException {
|
||||
SftpClient sftpClient = mock(SftpClient.class);
|
||||
|
||||
@Test
|
||||
public void testSharedSessionCachedReset() throws Exception {
|
||||
JSch jsch = spy(new JSch());
|
||||
Constructor<com.jcraft.jsch.Session> ctor =
|
||||
com.jcraft.jsch.Session.class.getDeclaredConstructor(JSch.class, String.class, String.class,
|
||||
int.class);
|
||||
ctor.setAccessible(true);
|
||||
com.jcraft.jsch.Session jschSession1 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
com.jcraft.jsch.Session jschSession2 = spy(ctor.newInstance(jsch, "foo", "host", 22));
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession1).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession1)
|
||||
.connect();
|
||||
|
||||
willAnswer(invocation -> {
|
||||
new DirectFieldAccessor(jschSession2).setPropertyValue("isConnected", true);
|
||||
return null;
|
||||
})
|
||||
.given(jschSession2)
|
||||
.connect();
|
||||
|
||||
when(jsch.getSession("foo", "host", 22)).thenReturn(jschSession1, jschSession2);
|
||||
final ChannelSftp channel1 = spy(new ChannelSftp());
|
||||
doReturn("channel1").when(channel1).toString();
|
||||
final ChannelSftp channel2 = spy(new ChannelSftp());
|
||||
doReturn("channel2").when(channel2).toString();
|
||||
final ChannelSftp channel3 = spy(new ChannelSftp());
|
||||
doReturn("channel3").when(channel3).toString();
|
||||
final ChannelSftp channel4 = spy(new ChannelSftp());
|
||||
doReturn("channel4").when(channel4).toString();
|
||||
new DirectFieldAccessor(channel1).setPropertyValue("session", jschSession1);
|
||||
new DirectFieldAccessor(channel2).setPropertyValue("session", jschSession1);
|
||||
// Can't use when(session.open()) with a spy
|
||||
final AtomicInteger n = new AtomicInteger();
|
||||
doAnswer(invocation -> n.getAndIncrement() == 0 ? channel1 : channel2).when(jschSession1).openChannel("sftp");
|
||||
doAnswer(invocation -> n.getAndIncrement() < 3 ? channel3 : channel4).when(jschSession2).openChannel("sftp");
|
||||
DefaultSftpSessionFactory factory = new DefaultSftpSessionFactory(jsch, true);
|
||||
factory.setHost("host");
|
||||
factory.setUser("foo");
|
||||
factory.setPassword("bar");
|
||||
CachingSessionFactory<LsEntry> cachedFactory = new CachingSessionFactory<LsEntry>(factory);
|
||||
noopConnect(channel1);
|
||||
noopConnect(channel2);
|
||||
noopConnect(channel3);
|
||||
noopConnect(channel4);
|
||||
Session<LsEntry> s1 = cachedFactory.getSession();
|
||||
Session<LsEntry> s2 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.jschSession")).isSameAs(jschSession1);
|
||||
assertThat(TestUtils.getPropertyValue(s1, "targetSession.channel")).isSameAs(channel1);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.channel")).isSameAs(channel2);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.jschSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "targetSession.jschSession"));
|
||||
s1.close();
|
||||
Session<LsEntry> s3 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(s1, "targetSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession.channel")).isSameAs(channel1);
|
||||
s3.close();
|
||||
cachedFactory.resetCache();
|
||||
verify(jschSession1, never()).disconnect();
|
||||
s3 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession.jschSession")).isSameAs(jschSession2);
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s1, "targetSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s3, "targetSession.channel")).isSameAs(channel3);
|
||||
s2.close();
|
||||
verify(jschSession1).disconnect();
|
||||
s2 = cachedFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.jschSession")).isSameAs(jschSession2);
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession"))
|
||||
.isNotSameAs(TestUtils.getPropertyValue(s3, "targetSession"));
|
||||
assertThat(TestUtils.getPropertyValue(s2, "targetSession.channel")).isSameAs(channel4);
|
||||
s2.close();
|
||||
s3.close();
|
||||
verify(jschSession2, never()).disconnect();
|
||||
cachedFactory.resetCache();
|
||||
verify(jschSession2).disconnect();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExists() throws SftpException, IOException {
|
||||
ChannelSftp channelSftp = mock(ChannelSftp.class);
|
||||
|
||||
willReturn(mock(SftpATTRS.class))
|
||||
.given(channelSftp)
|
||||
willReturn(new SftpClient.Attributes())
|
||||
.given(sftpClient)
|
||||
.lstat(eq("exist"));
|
||||
|
||||
willThrow(new SftpException(ChannelSftp.SSH_FX_NO_SUCH_FILE, "Path does not exist."))
|
||||
.given(channelSftp)
|
||||
willThrow(new SftpException(SftpConstants.SSH_FX_NO_SUCH_FILE, "notExist"))
|
||||
.given(sftpClient)
|
||||
.lstat(eq("notExist"));
|
||||
|
||||
willThrow(new SftpException(ChannelSftp.SSH_FX_CONNECTION_LOST, "Connection lost."))
|
||||
.given(channelSftp)
|
||||
willThrow(new SshException(SshConstants.SSH_OPEN_CONNECT_FAILED, "Connection lost."))
|
||||
.given(sftpClient)
|
||||
.lstat(and(not(eq("exist")), not(eq("notExist"))));
|
||||
|
||||
SftpSession sftpSession = new SftpSession(mock(com.jcraft.jsch.Session.class));
|
||||
DirectFieldAccessor fieldAccessor = new DirectFieldAccessor(sftpSession);
|
||||
fieldAccessor.setPropertyValue("channel", channelSftp);
|
||||
SftpSession sftpSession = new SftpSession(sftpClient);
|
||||
|
||||
assertThat(sftpSession.exists("exist")).isTrue();
|
||||
|
||||
@@ -417,23 +282,18 @@ public class SftpOutboundTests {
|
||||
isThrownBy(() -> sftpSession.exists("foo"));
|
||||
}
|
||||
|
||||
private void noopConnect(ChannelSftp channel1) throws JSchException {
|
||||
doNothing().when(channel1).connect(5000);
|
||||
}
|
||||
|
||||
public static class TestSftpSessionFactory extends DefaultSftpSessionFactory {
|
||||
|
||||
@Override
|
||||
public SftpSession getSession() {
|
||||
try {
|
||||
ChannelSftp channel = mock(ChannelSftp.class);
|
||||
SftpClient sftpClient = mock(SftpClient.class);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
File file = new File((String) invocation.getArgument(1));
|
||||
File file = new File((String) invocation.getArgument(0));
|
||||
assertThat(file.getName()).endsWith(".writing");
|
||||
FileCopyUtils.copy((InputStream) invocation.getArgument(0), new FileOutputStream(file));
|
||||
return null;
|
||||
}).when(channel).put(Mockito.any(InputStream.class), Mockito.anyString());
|
||||
return new FileOutputStream(file);
|
||||
}).when(sftpClient).write(Mockito.anyString());
|
||||
|
||||
doAnswer(invocation -> {
|
||||
File file = new File((String) invocation.getArgument(0));
|
||||
@@ -441,21 +301,16 @@ public class SftpOutboundTests {
|
||||
File renameToFile = new File((String) invocation.getArgument(1));
|
||||
file.renameTo(renameToFile);
|
||||
return null;
|
||||
}).when(channel).rename(Mockito.anyString(), Mockito.anyString());
|
||||
}).when(sftpClient).rename(Mockito.anyString(), Mockito.anyString(), eq(SftpClient.CopyMode.Overwrite));
|
||||
|
||||
String[] files = new File("remote-test-dir").list();
|
||||
Vector<LsEntry> sftpEntries = new Vector<>();
|
||||
for (String fileName : files) {
|
||||
LsEntry lsEntry = mock(LsEntry.class);
|
||||
SftpATTRS attributes = mock(SftpATTRS.class);
|
||||
when(lsEntry.getAttrs()).thenReturn(attributes);
|
||||
when(lsEntry.getFilename()).thenReturn(fileName);
|
||||
sftpEntries.add(lsEntry);
|
||||
}
|
||||
when(channel.ls("remote-test-dir/")).thenReturn(sftpEntries);
|
||||
List<SftpClient.DirEntry> dirEntries =
|
||||
Arrays.stream(files)
|
||||
.map((file) -> new SftpClient.DirEntry(file, file, new SftpClient.Attributes()))
|
||||
.toList();
|
||||
when(sftpClient.readDir("remote-test-dir")).thenReturn(dirEntries);
|
||||
|
||||
when(jschSession.openChannel("sftp")).thenReturn(channel);
|
||||
return SftpTestSessionFactory.createSftpSession(jschSession);
|
||||
return SftpTestSessionFactory.createSftpSession(sftpClient);
|
||||
}
|
||||
catch (Exception e) {
|
||||
throw new RuntimeException("Failed to create mock sftp session", e);
|
||||
|
||||
@@ -19,6 +19,10 @@ package org.springframework.integration.sftp.outbound;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@@ -40,6 +44,7 @@ import java.util.regex.Matcher;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -78,9 +83,6 @@ import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* @author Artem Bilan
|
||||
* @author Gary Russell
|
||||
@@ -128,7 +130,7 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
private DirectChannel inboundMPutRecursiveFiltered;
|
||||
|
||||
@Autowired
|
||||
private SessionFactory<LsEntry> sessionFactory;
|
||||
private SessionFactory<SftpClient.DirEntry> sessionFactory;
|
||||
|
||||
@Autowired
|
||||
private DirectChannel appending;
|
||||
@@ -180,8 +182,6 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(localFile.getPath().replaceAll(Matcher.quoteReplacement(File.separator), "/"))
|
||||
.contains(dir.toUpperCase());
|
||||
Session<?> session2 = this.sessionFactory.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(session2, "targetSession.jschSession"))
|
||||
.isSameAs(TestUtils.getPropertyValue(session, "targetSession.jschSession"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -284,10 +284,10 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(files.stream()
|
||||
.map(fi -> fi.getFilename())
|
||||
.collect(Collectors.toList())).contains(
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -302,14 +302,14 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(files.stream()
|
||||
.map(fi -> fi.getFilename())
|
||||
.collect(Collectors.toList())).contains(
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt",
|
||||
".",
|
||||
"..",
|
||||
"subSftpSource/.",
|
||||
"subSftpSource/..");
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource",
|
||||
"subSftpSource/subSftpSource1.txt",
|
||||
".",
|
||||
"..",
|
||||
"subSftpSource/.",
|
||||
"subSftpSource/..");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -324,9 +324,9 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThat(files.stream()
|
||||
.map(fi -> fi.getFilename())
|
||||
.collect(Collectors.toList())).contains(
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
" sftpSource1.txt",
|
||||
"sftpSource2.txt",
|
||||
"subSftpSource/subSftpSource1.txt");
|
||||
File newDeepFile = new File(this.sourceRemoteDirectory + "/subSftpSource/subSftpSource2.txt");
|
||||
OutputStream fos = new FileOutputStream(newDeepFile);
|
||||
fos.write("test".getBytes());
|
||||
@@ -474,8 +474,9 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
Session<?> session = sessionFactory.getSession();
|
||||
session.close();
|
||||
session = TestUtils.getPropertyValue(session, "targetSession", Session.class);
|
||||
ChannelSftp channel = spy(TestUtils.getPropertyValue(session, "channel", ChannelSftp.class));
|
||||
new DirectFieldAccessor(session).setPropertyValue("channel", channel);
|
||||
SftpClient sftpClient = spy(TestUtils.getPropertyValue(session, "sftpClient", SftpClient.class));
|
||||
doNothing().when(sftpClient).setStat(anyString(), any(SftpClient.Attributes.class));
|
||||
new DirectFieldAccessor(session).setPropertyValue("sftpClient", sftpClient);
|
||||
|
||||
String dir = "sftpSource/";
|
||||
this.inboundMGetRecursive.send(new GenericMessage<Object>(dir + "*"));
|
||||
@@ -492,8 +493,8 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
.isIn("sftpTarget/localSource1.txt", "sftpTarget/localSource2.txt");
|
||||
assertThat(out.getPayload().get(1))
|
||||
.isIn("sftpTarget/localSource1.txt", "sftpTarget/localSource2.txt");
|
||||
verify(channel).chmod(0600, "sftpTarget/localSource1.txt");
|
||||
verify(channel).chmod(0600, "sftpTarget/localSource2.txt");
|
||||
verify(sftpClient).setStat(eq("sftpTarget/localSource1.txt"), any(SftpClient.Attributes.class));
|
||||
verify(sftpClient).setStat(eq("sftpTarget/localSource2.txt"), any(SftpClient.Attributes.class));
|
||||
resetSessionCache();
|
||||
assertThat(this.config.latch.await(10, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(this.config.events).hasSize(6);
|
||||
@@ -676,9 +677,10 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
}
|
||||
|
||||
private void assertLength6(SftpRemoteFileTemplate template) {
|
||||
LsEntry[] files = template.execute(session -> session.list("sftpTarget/appending.txt"));
|
||||
assertThat(files.length).isEqualTo(1);
|
||||
assertThat(files[0].getAttrs().getSize()).isEqualTo(6);
|
||||
SftpClient.DirEntry[] files = template.execute(session -> session.list("sftpTarget"));
|
||||
assertThat(files.length).isEqualTo(3);
|
||||
assertThat(files[2].getFilename()).isEqualTo("appending.txt");
|
||||
assertThat(files[2].getAttributes().getSize()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -689,7 +691,7 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
sessionFactory.setUser("foo");
|
||||
sessionFactory.setPassword("foo");
|
||||
sessionFactory.setAllowUnknownKeys(true);
|
||||
Session<LsEntry> session = sessionFactory.getSession();
|
||||
Session<SftpClient.DirEntry> session = sessionFactory.getSession();
|
||||
|
||||
assertThat(session.exists("sftpSource")).isTrue();
|
||||
assertThat(session.exists("notExist")).isFalse();
|
||||
@@ -699,15 +701,15 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
assertThatExceptionOfType(UncheckedIOException.class)
|
||||
.isThrownBy(() -> session.exists("any"))
|
||||
.withRootCauseInstanceOf(IOException.class)
|
||||
.withStackTraceContaining("Pipe closed");
|
||||
.withStackTraceContaining("lstat(any) client is closed");
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final class TestMessageSessionCallback
|
||||
implements MessageSessionCallback<LsEntry, Object> {
|
||||
implements MessageSessionCallback<SftpClient.DirEntry, Object> {
|
||||
|
||||
@Override
|
||||
public Object doInSession(Session<ChannelSftp.LsEntry> session, Message<?> requestMessage) {
|
||||
public Object doInSession(Session<SftpClient.DirEntry> session, Message<?> requestMessage) {
|
||||
return ((String) requestMessage.getPayload()).toUpperCase();
|
||||
}
|
||||
|
||||
@@ -722,13 +724,13 @@ public class SftpServerOutboundTests extends SftpTestSupport {
|
||||
private volatile CountDownLatch latch;
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> sftpSessionFactory(ApplicationContext context) {
|
||||
public SessionFactory<SftpClient.DirEntry> sftpSessionFactory(ApplicationContext context) {
|
||||
SftpServerOutboundTests.eventListener().setApplicationEventPublisher(context);
|
||||
return SftpServerOutboundTests.sessionFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SftpRemoteFileTemplate template(SessionFactory<LsEntry> sf) {
|
||||
public SftpRemoteFileTemplate template(SessionFactory<SftpClient.DirEntry> sf) {
|
||||
return new SftpRemoteFileTemplate(sf);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016-2020 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
import com.jcraft.jsch.Proxy;
|
||||
import com.jcraft.jsch.ProxyHTTP;
|
||||
import com.jcraft.jsch.ProxySOCKS4;
|
||||
import com.jcraft.jsch.ProxySOCKS5;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @since 4.3
|
||||
*
|
||||
*/
|
||||
public class ProxyTests {
|
||||
|
||||
@Test
|
||||
@Disabled // TODO Use SftpTestSupport
|
||||
/*
|
||||
* Needs host and account
|
||||
*/
|
||||
public void testSimpleConnect() {
|
||||
DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
|
||||
sf.setHost("10.0.0.3");
|
||||
sf.setPort(22);
|
||||
sf.setUser("ftptest");
|
||||
sf.setPassword("ftptest");
|
||||
sf.setAllowUnknownKeys(true);
|
||||
sf.getSession().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
/*
|
||||
* Needs host and account and...
|
||||
* $ ssh -D 1080 -f -N gpr@10.0.0.3
|
||||
*/
|
||||
public void testProxyConnect() throws Exception {
|
||||
DefaultSftpSessionFactory sf = new DefaultSftpSessionFactory();
|
||||
JschProxyFactoryBean proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.SOCKS5, "localhost",
|
||||
1080, "ftptest", "ftptest");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
sf.setHost("10.0.0.3");
|
||||
sf.setPort(22);
|
||||
sf.setUser("ftptest");
|
||||
sf.setPassword("ftptest");
|
||||
sf.setProxy(proxyFactoryBean.getObject());
|
||||
sf.setAllowUnknownKeys(true);
|
||||
sf.getSession().close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFactoryBean() throws Exception {
|
||||
JschProxyFactoryBean proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.SOCKS5, "localhost",
|
||||
1080, "ftptest", "pass");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
Proxy proxy = proxyFactoryBean.getObject();
|
||||
assertProxy(proxy, ProxySOCKS5.class);
|
||||
|
||||
proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.SOCKS4, "localhost",
|
||||
1080, "ftptest", "pass");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
proxy = proxyFactoryBean.getObject();
|
||||
assertProxy(proxy, ProxySOCKS4.class);
|
||||
|
||||
proxyFactoryBean = new JschProxyFactoryBean(JschProxyFactoryBean.Type.HTTP, "localhost",
|
||||
1080, "ftptest", "pass");
|
||||
proxyFactoryBean.afterPropertiesSet();
|
||||
proxy = proxyFactoryBean.getObject();
|
||||
assertProxy(proxy, ProxyHTTP.class);
|
||||
}
|
||||
|
||||
private void assertProxy(Proxy proxy, Class<? extends Proxy> clazz) {
|
||||
assertThat(proxy).isInstanceOf(clazz);
|
||||
assertThat(TestUtils.getPropertyValue(proxy, "user")).isEqualTo("ftptest");
|
||||
assertThat(TestUtils.getPropertyValue(proxy, "passwd")).isEqualTo("pass");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright 2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.apache.sshd.client.ClientFactoryManager;
|
||||
import org.apache.sshd.client.config.hosts.HostPatternsHolder;
|
||||
import org.apache.sshd.client.config.hosts.KnownHostEntry;
|
||||
import org.apache.sshd.client.session.ClientSession;
|
||||
import org.apache.sshd.common.Factory;
|
||||
import org.apache.sshd.common.config.keys.AuthorizedKeyEntry;
|
||||
import org.apache.sshd.common.config.keys.PublicKeyEntryResolver;
|
||||
import org.apache.sshd.common.random.JceRandomFactory;
|
||||
import org.apache.sshd.common.util.GenericUtils;
|
||||
import org.apache.sshd.common.util.net.SshdSocketAddress;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.condition.EnabledIf;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Artem Bilan
|
||||
*
|
||||
* @since 6.0
|
||||
*/
|
||||
@EnabledIf("isDefaultKnownHostsFilePresent")
|
||||
public class ResourceKnownHostsServerKeyVerifierTests {
|
||||
|
||||
private static final Map<SshdSocketAddress, PublicKey> HOST_KEYS = new TreeMap<>(SshdSocketAddress.BY_HOST_AND_PORT);
|
||||
|
||||
@BeforeAll
|
||||
static void loadHostKeys() throws GeneralSecurityException, IOException {
|
||||
Map<SshdSocketAddress, KnownHostEntry> hostsEntries = loadEntries(KnownHostEntry.getDefaultKnownHostsFile());
|
||||
for (Map.Entry<SshdSocketAddress, KnownHostEntry> ke : hostsEntries.entrySet()) {
|
||||
SshdSocketAddress hostIdentity = ke.getKey();
|
||||
KnownHostEntry entry = ke.getValue();
|
||||
AuthorizedKeyEntry authEntry = entry.getKeyEntry();
|
||||
PublicKey key = authEntry.resolvePublicKey(null, Collections.emptyMap(), PublicKeyEntryResolver.FAILING);
|
||||
HOST_KEYS.put(hostIdentity, key);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
void testServerKeys() {
|
||||
ResourceKnownHostsServerKeyVerifier verifier
|
||||
= new ResourceKnownHostsServerKeyVerifier(
|
||||
new FileSystemResource(KnownHostEntry.getDefaultKnownHostsFile()));
|
||||
|
||||
ClientFactoryManager manager = Mockito.mock(ClientFactoryManager.class);
|
||||
Mockito.when(manager.getRandomFactory()).thenReturn((Factory) JceRandomFactory.INSTANCE);
|
||||
|
||||
HOST_KEYS.forEach((key, value) -> {
|
||||
ClientSession session = Mockito.mock(ClientSession.class);
|
||||
Mockito.when(session.getFactoryManager()).thenReturn(manager);
|
||||
|
||||
Mockito.when(session.getConnectAddress()).thenReturn(key);
|
||||
assertThat(verifier.verifyServerKey(session, key, value)).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
private static Map<SshdSocketAddress, KnownHostEntry> loadEntries(Path file) throws IOException {
|
||||
Collection<KnownHostEntry> entries = KnownHostEntry.readKnownHostEntries(file);
|
||||
if (GenericUtils.isEmpty(entries)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<SshdSocketAddress, KnownHostEntry> hostsMap = new TreeMap<>(SshdSocketAddress.BY_HOST_AND_PORT);
|
||||
for (KnownHostEntry entry : entries) {
|
||||
String line = entry.getConfigLine();
|
||||
// extract hosts
|
||||
int pos = line.indexOf(' ');
|
||||
String patterns = line.substring(0, pos);
|
||||
if (entry.getHashedEntry() != null) {
|
||||
hostsMap.put(new SshdSocketAddress("localhost", 0), entry);
|
||||
}
|
||||
else {
|
||||
String[] addrs = GenericUtils.split(patterns, ',');
|
||||
for (String a : addrs) {
|
||||
int port = 0;
|
||||
if (a.charAt(0) == HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_START_DELIM) {
|
||||
pos = a.indexOf(HostPatternsHolder.NON_STANDARD_PORT_PATTERN_ENCLOSURE_END_DELIM, 1);
|
||||
|
||||
port = Integer.parseInt(a.substring(pos + 2));
|
||||
a = a.substring(1, pos);
|
||||
}
|
||||
hostsMap.put(new SshdSocketAddress(a, port), entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hostsMap;
|
||||
}
|
||||
|
||||
static boolean isDefaultKnownHostsFilePresent() {
|
||||
return KnownHostEntry.getDefaultKnownHostsFile().toFile().exists();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2020 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,10 +20,11 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
@@ -44,11 +45,6 @@ import org.springframework.messaging.support.GenericMessage;
|
||||
import org.springframework.test.annotation.DirtiesContext;
|
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp;
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
import com.jcraft.jsch.SftpATTRS;
|
||||
import com.jcraft.jsch.SftpException;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
@@ -61,7 +57,7 @@ import com.jcraft.jsch.SftpException;
|
||||
public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
|
||||
@Autowired
|
||||
private CachingSessionFactory<LsEntry> sessionFactory;
|
||||
private CachingSessionFactory<SftpClient.DirEntry> sessionFactory;
|
||||
|
||||
@Test
|
||||
public void testINT3412AppendStatRmdir() {
|
||||
@@ -82,24 +78,24 @@ public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
template.append(new GenericMessage<>("foo"));
|
||||
template.append(new GenericMessage<>("bar"));
|
||||
assertThat(template.exists("foo/foobar.txt")).isTrue();
|
||||
template.executeWithClient((ClientCallbackWithoutResult<ChannelSftp>) client -> {
|
||||
template.executeWithClient((ClientCallbackWithoutResult<SftpClient>) client -> {
|
||||
try {
|
||||
SftpATTRS file = client.lstat("foo/foobar.txt");
|
||||
SftpClient.Attributes file = client.lstat("foo/foobar.txt");
|
||||
assertThat(file.getSize()).isEqualTo(6);
|
||||
}
|
||||
catch (SftpException e) {
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
template.execute((SessionCallbackWithoutResult<LsEntry>) session -> {
|
||||
LsEntry[] files = session.list("foo/");
|
||||
template.execute((SessionCallbackWithoutResult<SftpClient.DirEntry>) session -> {
|
||||
SftpClient.DirEntry[] files = session.list("foo/");
|
||||
assertThat(files.length).isEqualTo(4);
|
||||
assertThat(session.remove("foo/foobar.txt")).isTrue();
|
||||
assertThat(session.rmdir("foo/bar/")).isTrue();
|
||||
files = session.list("foo/");
|
||||
assertThat(files.length).isEqualTo(2);
|
||||
List<LsEntry> list = Arrays.asList(files);
|
||||
assertThat(list.stream().map(l -> l.getFilename()).collect(Collectors.toList())).contains(".", "..");
|
||||
List<String> fileNames = Arrays.stream(files).map(SftpClient.DirEntry::getFilename).toList();
|
||||
assertThat(fileNames).contains(".", "..");
|
||||
assertThat(session.rmdir("foo/")).isTrue();
|
||||
});
|
||||
assertThat(template.exists("foo")).isFalse();
|
||||
@@ -107,8 +103,8 @@ public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
|
||||
@Test
|
||||
public void testNoDeadLockOnSend() {
|
||||
CachingSessionFactory<LsEntry> cachingSessionFactory = new CachingSessionFactory<>(sessionFactory(), 1);
|
||||
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(cachingSessionFactory);
|
||||
CachingSessionFactory<SftpClient.DirEntry> sessionFactory = new CachingSessionFactory<>(sessionFactory(), 1);
|
||||
SftpRemoteFileTemplate template = new SftpRemoteFileTemplate(sessionFactory);
|
||||
template.setRemoteDirectoryExpression(new LiteralExpression(""));
|
||||
template.setBeanFactory(mock(BeanFactory.class));
|
||||
template.setUseTemporaryFileName(false);
|
||||
@@ -125,14 +121,14 @@ public class SftpRemoteFileTemplateTests extends SftpTestSupport {
|
||||
.withCauseInstanceOf(MessagingException.class)
|
||||
.withStackTraceContaining("he destination file already exists at 'test.file'.");
|
||||
|
||||
cachingSessionFactory.destroy();
|
||||
sessionFactory.destroy();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class Config {
|
||||
|
||||
@Bean
|
||||
public SessionFactory<LsEntry> ftpSessionFactory() {
|
||||
public SessionFactory<SftpClient.DirEntry> ftpSessionFactory() {
|
||||
return SftpRemoteFileTemplateTests.sessionFactory();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -35,6 +35,7 @@ import java.util.Collections;
|
||||
import org.apache.sshd.common.file.virtualfs.VirtualFileSystemFactory;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
@@ -44,8 +45,6 @@ import org.springframework.integration.file.remote.session.Session;
|
||||
import org.springframework.util.Base64Utils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import com.jcraft.jsch.ChannelSftp.LsEntry;
|
||||
|
||||
/**
|
||||
* *
|
||||
* @author Gary Russell
|
||||
@@ -59,8 +58,7 @@ public class SftpServerTests {
|
||||
|
||||
@Test
|
||||
public void testUcPw() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPasswordAuthenticator((arg0, arg1, arg2) -> true);
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
@@ -76,12 +74,9 @@ public class SftpServerTests {
|
||||
f.setUser("user");
|
||||
f.setPassword("pass");
|
||||
f.setAllowUnknownKeys(true);
|
||||
Session<LsEntry> session = f.getSession();
|
||||
Session<SftpClient.DirEntry> session = f.getSession();
|
||||
doTest(server, session);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -94,11 +89,9 @@ public class SftpServerTests {
|
||||
testKeyExchange("id_rsa_pp.pub", "id_rsa_pp", "secret");
|
||||
}
|
||||
|
||||
private void testKeyExchange(String pubKey, String privKey, String passphrase)
|
||||
throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
private void testKeyExchange(String pubKey, String privKey, String passphrase) throws Exception {
|
||||
final PublicKey allowedKey = decodePublicKey(pubKey);
|
||||
try {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPublickeyAuthenticator((username, key, session) -> key.equals(allowedKey));
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
@@ -116,12 +109,9 @@ public class SftpServerTests {
|
||||
InputStream stream = new ClassPathResource(privKey).getInputStream();
|
||||
f.setPrivateKey(new ByteArrayResource(FileCopyUtils.copyToByteArray(stream)));
|
||||
f.setPrivateKeyPassphrase(passphrase);
|
||||
Session<LsEntry> session = f.getSession();
|
||||
Session<SftpClient.DirEntry> session = f.getSession();
|
||||
doTest(server, session);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
private PublicKey decodePublicKey(String key) throws Exception {
|
||||
@@ -155,12 +145,15 @@ public class SftpServerTests {
|
||||
return new BigInteger(bytes);
|
||||
}
|
||||
|
||||
protected void doTest(SshServer server, Session<LsEntry> session) throws IOException {
|
||||
protected void doTest(SshServer server, Session<SftpClient.DirEntry> session) throws IOException {
|
||||
assertThat(server.getActiveSessions().size()).isEqualTo(1);
|
||||
LsEntry[] list = session.list(".");
|
||||
if (list.length > 0) {
|
||||
session.remove("*");
|
||||
SftpClient.DirEntry[] list = session.list(".");
|
||||
for (SftpClient.DirEntry entry : list) {
|
||||
if (entry.getAttributes().isRegularFile()) {
|
||||
session.remove(entry.getFilename());
|
||||
}
|
||||
}
|
||||
|
||||
session.write(new ByteArrayInputStream("foo".getBytes()), "bar");
|
||||
list = session.list(".");
|
||||
assertThat(list[1].getFilename()).isEqualTo("bar");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2014-2021 the original author or authors.
|
||||
* Copyright 2014-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,28 +18,15 @@ package org.springframework.integration.sftp.session;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.time.Duration;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.sshd.common.SshException;
|
||||
import org.apache.sshd.server.SshServer;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.sftp.server.SftpSubsystemFactory;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.integration.test.util.TestUtils;
|
||||
|
||||
import com.jcraft.jsch.JSchException;
|
||||
import com.jcraft.jsch.UserInfo;
|
||||
|
||||
/**
|
||||
* @author Gary Russell
|
||||
* @author Artem Bilan
|
||||
@@ -54,8 +41,7 @@ public class SftpSessionFactoryTests {
|
||||
*/
|
||||
@Test
|
||||
public void testConnectFailSocketOpen() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
try (SshServer server = SshServer.setUpDefaultServer()) {
|
||||
server.setPasswordAuthenticator((arg0, arg1, arg2) -> true);
|
||||
server.setPort(0);
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
@@ -69,24 +55,22 @@ public class SftpSessionFactoryTests {
|
||||
int n = 0;
|
||||
while (true) {
|
||||
try {
|
||||
f.getSession();
|
||||
f.getSession().connect();
|
||||
fail("Expected Exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (e instanceof IllegalStateException && "failed to create SFTP Session".equals(e.getMessage())) {
|
||||
if (e.getCause() instanceof IllegalStateException) {
|
||||
if (e.getCause().getCause() instanceof JSchException) {
|
||||
if (e.getCause().getCause().getCause() instanceof ConnectException) {
|
||||
assertThat(n++ < 100).as("Server failed to start in 10 seconds").isTrue();
|
||||
Thread.sleep(100);
|
||||
continue;
|
||||
}
|
||||
if (e.getCause().getCause() instanceof ConnectException) {
|
||||
assertThat(n++ < 100).as("Server failed to start in 10 seconds").isTrue();
|
||||
Thread.sleep(100);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
assertThat(e).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause().getMessage()).isEqualTo("failed to connect");
|
||||
assertThat(e.getCause()).isInstanceOf(SshException.class);
|
||||
assertThat(e.getCause().getMessage()).isEqualTo("Server key did not validate");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -98,130 +82,6 @@ public class SftpSessionFactoryTests {
|
||||
|
||||
assertThat(server.getActiveSessions().size()).isEqualTo(0);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPasswordPassPhraseViaUserInfo() {
|
||||
DefaultSftpSessionFactory f = new DefaultSftpSessionFactory();
|
||||
f.setUser("user");
|
||||
f.setAllowUnknownKeys(true);
|
||||
UserInfo ui = mock(UserInfo.class);
|
||||
when(ui.getPassword()).thenReturn("pass");
|
||||
when(ui.getPassphrase()).thenReturn("pp").thenReturn(null);
|
||||
f.setUserInfo(ui);
|
||||
UserInfo userInfo = TestUtils.getPropertyValue(f, "userInfoWrapper", UserInfo.class);
|
||||
assertThat(userInfo.getPassword()).isEqualTo("pass");
|
||||
f.setPassword("foo");
|
||||
try {
|
||||
userInfo.getPassword();
|
||||
fail("expected Exception");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertThat(e.getMessage()).startsWith("When a 'UserInfo' is provided, 'password' is not allowed");
|
||||
}
|
||||
assertThat(userInfo.getPassphrase()).isEqualTo("pp");
|
||||
f.setPrivateKeyPassphrase("bar");
|
||||
try {
|
||||
userInfo.getPassphrase();
|
||||
fail("expected Exception");
|
||||
}
|
||||
catch (IllegalStateException e) {
|
||||
assertThat(e
|
||||
.getMessage()).startsWith("When a 'UserInfo' is provided, 'privateKeyPassphrase' is not allowed");
|
||||
}
|
||||
f.setUserInfo(null);
|
||||
assertThat(userInfo.getPassword()).isEqualTo("foo");
|
||||
assertThat(userInfo.getPassphrase()).isEqualTo("bar");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultUserInfoFalse() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
expectReject(f);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultUserInfoTrue() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
f.setChannelConnectTimeout(Duration.ofSeconds(6));
|
||||
f.setAllowUnknownKeys(true);
|
||||
SftpSession session = f.getSession();
|
||||
assertThat(TestUtils.getPropertyValue(session, "channelConnectTimeout", Integer.class)).isEqualTo(6_000);
|
||||
session.close();
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomUserInfoFalse() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
UserInfo userInfo = mock(UserInfo.class);
|
||||
when(userInfo.promptYesNo(anyString())).thenReturn(false);
|
||||
f.setUserInfo(userInfo);
|
||||
expectReject(f);
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void expectReject(DefaultSftpSessionFactory f) {
|
||||
try {
|
||||
f.getSession().close();
|
||||
fail("Expected Exception");
|
||||
}
|
||||
catch (Exception e) {
|
||||
assertThat(e).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause()).isInstanceOf(IllegalStateException.class);
|
||||
assertThat(e.getCause().getCause()).isInstanceOf(JSchException.class);
|
||||
assertThat(e.getCause().getCause().getMessage()).contains("reject HostKey");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCustomUserInfoTrue() throws Exception {
|
||||
SshServer server = SshServer.setUpDefaultServer();
|
||||
try {
|
||||
DefaultSftpSessionFactory f = createServerAndClient(server);
|
||||
UserInfo userInfo = mock(UserInfo.class);
|
||||
when(userInfo.promptYesNo(anyString())).thenReturn(true);
|
||||
f.setUserInfo(userInfo);
|
||||
f.getSession().close();
|
||||
}
|
||||
finally {
|
||||
server.stop(true);
|
||||
}
|
||||
}
|
||||
|
||||
private DefaultSftpSessionFactory createServerAndClient(SshServer server) throws IOException {
|
||||
server.setPublickeyAuthenticator((username, key, session) -> true);
|
||||
server.setPort(0);
|
||||
server.setSubsystemFactories(Collections.singletonList(new SftpSubsystemFactory()));
|
||||
server.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(new File("hostkey.ser").toPath()));
|
||||
server.start();
|
||||
|
||||
DefaultSftpSessionFactory f = new DefaultSftpSessionFactory();
|
||||
f.setHost("localhost");
|
||||
f.setPort(server.getPort());
|
||||
f.setUser("user");
|
||||
Resource privateKey = new ClassPathResource("id_rsa");
|
||||
f.setPrivateKey(privateKey);
|
||||
return f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,6 +17,11 @@
|
||||
package org.springframework.integration.sftp.session;
|
||||
|
||||
|
||||
import static org.mockito.BDDMockito.willReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import org.apache.sshd.sftp.client.SftpClient;
|
||||
|
||||
/**
|
||||
* @author Oleg Zhurakousky
|
||||
* @author Gary Russell
|
||||
@@ -28,9 +33,12 @@ public class SftpTestSessionFactory {
|
||||
super();
|
||||
}
|
||||
|
||||
public static SftpSession createSftpSession(com.jcraft.jsch.Session jschSession) {
|
||||
SftpSession sftpSession = new SftpSession(jschSession);
|
||||
sftpSession.connect();
|
||||
public static SftpSession createSftpSession(SftpClient sftpClient) {
|
||||
SftpSession sftpSession = spy(new SftpSession(sftpClient));
|
||||
willReturn("mock.sftp.host:22")
|
||||
.given(sftpSession)
|
||||
.getHostPort();
|
||||
return sftpSession;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</Console>
|
||||
</Appenders>
|
||||
<Loggers>
|
||||
<Logger name="com.jcraft.jsch" level="warn"/>
|
||||
<Logger name="org.apache.sshd" level="error"/>
|
||||
<Logger name="org.springframework.integration" level="warn"/>
|
||||
<Logger name="org.springframework.integration.sftp" level="warn"/>
|
||||
<Root level="warn">
|
||||
|
||||
Reference in New Issue
Block a user